@hasna/skills 0.10.88 → 0.10.90

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/mcp.js CHANGED
@@ -38,7 +38,7 @@ var package_default;
38
38
  var init_package = __esm(() => {
39
39
  package_default = {
40
40
  name: "@hasna/skills",
41
- version: "0.10.88",
41
+ version: "0.10.90",
42
42
  description: "Skills library for AI coding agents",
43
43
  type: "module",
44
44
  bin: {
@@ -22957,7 +22957,8 @@ var init_codex_native_compatibility = __esm(() => {
22957
22957
  "codex-cli 0.162.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22958
22958
  "codex-cli 0.163.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22959
22959
  "codex-cli 0.164.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22960
- "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
22960
+ "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
22961
+ "codex-cli 0.166.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
22961
22962
  };
22962
22963
  SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version2) => supportsCodexNativeCapability(version2, "hooks")));
22963
22964
  });
@@ -37171,6 +37172,7 @@ var init_selected_document = __esm(() => {
37171
37172
  });
37172
37173
 
37173
37174
  // src/lib/selection-resolver.ts
37175
+ import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
37174
37176
  async function resolveSelectionContext(profileId, options = {}) {
37175
37177
  const sessionState = options.sessionId ? readSkillSessionSnapshotIfExists(options.sessionId, options) : null;
37176
37178
  const session = sessionState?.receipt ?? null;
@@ -37255,21 +37257,70 @@ function unknownNameRefusal(slug, profile) {
37255
37257
  return `Skill '${slug}' is not selected by this Skills profile or project lock.${hint} ` + `Read the accepted names with 'skills search <term>' or 'skills list --all --json'; accept one with 'skills pin <name>'. ${ACCEPTED_FORM}`;
37256
37258
  }
37257
37259
  async function loadSelectedSkill(spec, profileId, options = {}) {
37258
- const context = await resolveSelectionContext(profileId, options);
37260
+ if (!options.cacheFirst)
37261
+ return readSelectedSkill(spec, profileId, options);
37262
+ const { cacheFirst: _cacheFirst, cached: _cached, authority, ...service } = options;
37263
+ try {
37264
+ return await readSelectedSkill(spec, profileId, { ...service, authority, cached: true });
37265
+ } catch (cacheError) {
37266
+ if (!(cacheError instanceof SkillSelectionError) || !RECOVERABLE_CACHE_ERRORS.has(cacheError.code))
37267
+ throw cacheError;
37268
+ try {
37269
+ return await readSelectedSkill(spec, profileId, service);
37270
+ } catch (serviceError) {
37271
+ if (!(serviceError instanceof SkillSelectionError) || serviceError.code !== "SKILLS_API_UNAVAILABLE")
37272
+ throw serviceError;
37273
+ throw new SkillSelectionError(cacheError.code, "The verified cache can't serve this load and the Skills service can't be reached from here, so it has to run where the network is available. `skills sync` doesn't renew a session's pin.");
37274
+ }
37275
+ }
37276
+ }
37277
+ async function readSelectedSkill(spec, profileId, options) {
37278
+ const context = await resolveSelectionContext(profileId, options).catch((error61) => {
37279
+ if (options.cached && options.sessionId && error61 instanceof SkillSelectionError && error61.code === "CACHED_PROFILE_EXPIRED" && readSkillSessionSnapshotIfExists(options.sessionId, options)) {
37280
+ throw new SkillSelectionError("CACHED_PROFILE_EXPIRED", "This session's Skills pin was verified too long ago to read from the cache. Run the same command without --cached: that keeps the pin but needs the network and sign-in. `skills sync` doesn't renew a session's pin.");
37281
+ }
37282
+ throw error61;
37283
+ });
37259
37284
  const selection = exactProfileSelection(spec, context.receipt.profile);
37260
37285
  const entries = await readSelectedEntries(selection, context, options);
37261
37286
  const { file: file2, content } = readSelectedDocument(entries, options.file);
37262
37287
  const receipt = { ...selection, file: file2, source: options.cached ? "verified-cache" : "api", verifiedAt: context.receipt.verifiedAt };
37263
- if (options.sessionId) {
37264
- const session = {
37265
- ...context.receipt,
37266
- sessionId: options.sessionId,
37267
- loaded: [...new Set([...context.session?.loaded ?? [], selectionKey(selection)])],
37268
- ...context.projectLock ? { projectLock: context.projectLock } : {}
37269
- };
37270
- writeSkillSession(session, { current: context.sessionSnapshot, parent: context.parentSnapshot }, options);
37288
+ const sessionRecorded = options.sessionId ? recordSessionLoad(options.sessionId, selection, context, options) : undefined;
37289
+ return { content, selection, receipt, ...sessionRecorded === undefined ? {} : { sessionRecorded } };
37290
+ }
37291
+ function isUnwritableStore(error61) {
37292
+ for (let cause = error61, depth = 0;cause && depth < 4; cause = cause.cause, depth++)
37293
+ if (UNWRITABLE_CODES.has(cause.code))
37294
+ return true;
37295
+ return false;
37296
+ }
37297
+ function recordSessionLoad(sessionId, selection, context, options) {
37298
+ const key2 = selectionKey(selection);
37299
+ let snapshot2 = context.sessionSnapshot;
37300
+ for (let attempt = 0;attempt < SESSION_LOAD_RECORD_ATTEMPTS; attempt++) {
37301
+ try {
37302
+ const session = {
37303
+ ...context.receipt,
37304
+ sessionId,
37305
+ loaded: [...new Set([...context.session?.loaded ?? [], key2])],
37306
+ ...context.projectLock ? { projectLock: context.projectLock } : {}
37307
+ };
37308
+ writeSkillSession(session, { current: snapshot2, parent: context.parentSnapshot }, options);
37309
+ return true;
37310
+ } catch (error61) {
37311
+ const blocked = isUnwritableStore(error61) || error61 instanceof SkillSelectionError && error61.code === "SESSION_WRITE_BUSY";
37312
+ if (!blocked && !(error61 instanceof SkillSelectionError && error61.code === "SESSION_CONTEXT_CONFLICT"))
37313
+ throw error61;
37314
+ const current = readSkillSessionSnapshotIfExists(sessionId, options);
37315
+ if (current ? !isDeepStrictEqual5(current.receipt.profile, context.receipt.profile) : !blocked) {
37316
+ throw new SkillSelectionError("SESSION_CONTEXT_CONFLICT", "The Skills session's pin changed while this skill was loading, so its content no longer belongs to that pin. Load it again.");
37317
+ }
37318
+ if (blocked)
37319
+ return false;
37320
+ snapshot2 = skillSessionSnapshotBinding(current);
37321
+ }
37271
37322
  }
37272
- return { content, selection, receipt };
37323
+ return false;
37273
37324
  }
37274
37325
  async function pinSelectedSkillVersion(spec, profileId, options) {
37275
37326
  const context = await resolveSelectionContext(profileId, options);
@@ -37298,13 +37349,15 @@ async function selectedSkillRequirements(spec, profileId, options = {}) {
37298
37349
  }
37299
37350
  return { ...getSkillRequirementsFromContent(selection.slug, texts, dependencies), selection };
37300
37351
  }
37301
- var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
37352
+ var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.", RECOVERABLE_CACHE_ERRORS, SESSION_LOAD_RECORD_ATTEMPTS = 8, UNWRITABLE_CODES;
37302
37353
  var init_selection_resolver = __esm(() => {
37303
37354
  init_profile_client();
37304
37355
  init_skillinfo();
37305
37356
  init_search();
37306
37357
  init_selected_document();
37307
37358
  init_selection_cache();
37359
+ RECOVERABLE_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
37360
+ UNWRITABLE_CODES = new Set(["EACCES", "EPERM", "EROFS", "ENOSPC", "EDQUOT"]);
37308
37361
  });
37309
37362
 
37310
37363
  // src/lib/selected-manifest.ts
@@ -37916,7 +37969,7 @@ var init_codex_hook_discovery_recovery = __esm(() => {
37916
37969
  import { createHash as createHash26 } from "crypto";
37917
37970
  import { lstatSync as lstatSync25, opendirSync as opendirSync4, readFileSync as readFileSync18, readdirSync as readdirSync9, realpathSync as realpathSync8 } from "fs";
37918
37971
  import { basename as basename14, dirname as dirname24, isAbsolute as isAbsolute22, join as join33, relative as relative7, resolve as resolve23, sep as sep11 } from "path";
37919
- import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
37972
+ import { isDeepStrictEqual as isDeepStrictEqual6 } from "util";
37920
37973
  function discoveryExecutable(agent) {
37921
37974
  const command = DISCOVERY_RUNTIME_COMMANDS[agent];
37922
37975
  const found = command ? Bun.which(command) : null;
@@ -38063,8 +38116,8 @@ function projectNativeDiscoveryFields(text2, format, fields2, path) {
38063
38116
  }
38064
38117
  function isSupersededSettingsProjection(agent, configPath, source) {
38065
38118
  if (agent === "sumi")
38066
- return source.path === configPath && source.format === "json" && isDeepStrictEqual5(source.fields, [...SUMI_DISCOVERY_PROJECTION_FIELDS]);
38067
- return agent === "codex" && source.path === configPath && source.format === "toml" && isDeepStrictEqual5(source.fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]);
38119
+ return source.path === configPath && source.format === "json" && isDeepStrictEqual6(source.fields, [...SUMI_DISCOVERY_PROJECTION_FIELDS]);
38120
+ return agent === "codex" && source.path === configPath && source.format === "toml" && isDeepStrictEqual6(source.fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]);
38068
38121
  }
38069
38122
  function supersedesSettingsProjection(agent, sources, configPath) {
38070
38123
  if (agent === "sumi")
@@ -38085,7 +38138,7 @@ function projected(source, changes, budget3 = discoveryByteBudget()) {
38085
38138
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || !Array.isArray(source.hookSources))
38086
38139
  throw new Error("Codex V6 requires exact reviewed hook sources");
38087
38140
  const current = captureCodexSettingsV6(source.path, source.hookSources, budget3);
38088
- if (current.sha256 !== source.sha256 || !isDeepStrictEqual5(current.hookSources, source.hookSources))
38141
+ if (current.sha256 !== source.sha256 || !isDeepStrictEqual6(current.hookSources, source.hookSources))
38089
38142
  throw new Error("Codex hook provider or dependency changed; explicit discovery review required");
38090
38143
  return changes ? captureCodexSettingsV6(source.path, source.hookSources, budget3, changes).sha256 : current.sha256;
38091
38144
  }
@@ -38361,7 +38414,7 @@ function verifyCodexSemanticCacheDiscovery(binding, reviewedRootAliases, narrowe
38361
38414
  if (reviewedRootAliases !== undefined) {
38362
38415
  const alias = reviewedRootAliases.find((alias2) => alias2.agent === "codex");
38363
38416
  const expected = alias ? { alias: alias.alias, target: alias.target } : null;
38364
- if (!isDeepStrictEqual5(proof.hookRootAlias, expected))
38417
+ if (!isDeepStrictEqual6(proof.hookRootAlias, expected))
38365
38418
  throw new Error("Semantic cache hook alias differs from reviewed root binding");
38366
38419
  }
38367
38420
  const controls = binding.codexDisabledPluginSkills;
@@ -38371,7 +38424,7 @@ function verifyCodexSemanticCacheDiscovery(binding, reviewedRootAliases, narrowe
38371
38424
  if (controls.some((control) => dirname24(dirname24(control.pluginParent)) !== cacheRoot))
38372
38425
  throw new Error("Codex semantic cache names another enrolled cache");
38373
38426
  const reviewedHookParents = proof.reviewedHookParents ?? [];
38374
- if (!Array.isArray(reviewedHookParents) || reviewedHookParents.length > 4096 || reviewedHookParents.some((parent) => !parent || !(parent.installation === "remote-receipt" && Object.keys(parent).sort().join(",") === "installation,namespace,pluginId,pluginParent,remotePluginId,role" && typeof parent.remotePluginId === "string" || parent.installation === "local-marketplace" && Object.keys(parent).sort().join(",") === "installation,marketplaceSha256,marketplaceSourcePath,namespace,pluginId,pluginParent,role" && typeof parent.marketplaceSourcePath === "string" && /^[a-f0-9]{64}$/.test(parent.marketplaceSha256 ?? "")) || parent.role !== "reviewed-hook" || typeof parent.pluginId !== "string" || typeof parent.namespace !== "string" || typeof parent.pluginParent !== "string") || new Set(reviewedHookParents.map((parent) => parent.pluginParent)).size !== reviewedHookParents.length || !isDeepStrictEqual5((proof.witness.hookParents ?? []).map(({ manifestSha256: _manifest, hooksSha256: _hooks, receiptSha256: _receipt, ...parent }) => parent), reviewedHookParents)) {
38427
+ if (!Array.isArray(reviewedHookParents) || reviewedHookParents.length > 4096 || reviewedHookParents.some((parent) => !parent || !(parent.installation === "remote-receipt" && Object.keys(parent).sort().join(",") === "installation,namespace,pluginId,pluginParent,remotePluginId,role" && typeof parent.remotePluginId === "string" || parent.installation === "local-marketplace" && Object.keys(parent).sort().join(",") === "installation,marketplaceSha256,marketplaceSourcePath,namespace,pluginId,pluginParent,role" && typeof parent.marketplaceSourcePath === "string" && /^[a-f0-9]{64}$/.test(parent.marketplaceSha256 ?? "")) || parent.role !== "reviewed-hook" || typeof parent.pluginId !== "string" || typeof parent.namespace !== "string" || typeof parent.pluginParent !== "string") || new Set(reviewedHookParents.map((parent) => parent.pluginParent)).size !== reviewedHookParents.length || !isDeepStrictEqual6((proof.witness.hookParents ?? []).map(({ manifestSha256: _manifest, hooksSha256: _hooks, receiptSha256: _receipt, ...parent }) => parent), reviewedHookParents)) {
38375
38428
  throw new Error("Invalid reviewed native-hook cache role");
38376
38429
  }
38377
38430
  for (const parent of reviewedHookParents)
@@ -38381,7 +38434,7 @@ function verifyCodexSemanticCacheDiscovery(binding, reviewedRootAliases, narrowe
38381
38434
  const hookParentPaths = reviewedHookParents.map((parent) => parent.pluginParent);
38382
38435
  const sources = binding.sources.filter((source) => codexSemanticCacheSourceRole(source, cacheRoot, parents3, hookParentPaths));
38383
38436
  const directories = (binding.directories ?? []).filter((directory) => codexSemanticCacheDirectoryRole(directory.path, cacheRoot));
38384
- if (!directories.length || !isDeepStrictEqual5(sources, proof.supersededSources) || !isDeepStrictEqual5(directories, proof.supersededDirectories))
38437
+ if (!directories.length || !isDeepStrictEqual6(sources, proof.supersededSources) || !isDeepStrictEqual6(directories, proof.supersededDirectories))
38385
38438
  throw new Error("Codex semantic cache provenance changed");
38386
38439
  const configPath = proof.configPath;
38387
38440
  if (!isAbsolute22(configPath) || resolve23(configPath) !== configPath || basename14(configPath) !== "config.toml" || /[\x00-\x1f\x7f]/.test(configPath))
@@ -38427,8 +38480,8 @@ function verifyDiscovery(binding, codexRecovery, reviewedRootAliases, witnessTol
38427
38480
  if (omitted.some((root) => !retiredRoots.includes(root)))
38428
38481
  throw new Error("Missing reviewed directory membership inventory; provide a fresh discovery review");
38429
38482
  const current = captureDiscoveryDirectoryProjection(ordinary.map((item) => item.path), retiredRoots).map(({ entries, ...item }) => item);
38430
- if (!isDeepStrictEqual5(current, projection)) {
38431
- const changed = projection.filter((item, index) => !isDeepStrictEqual5(current[index], item));
38483
+ if (!isDeepStrictEqual6(current, projection)) {
38484
+ const changed = projection.filter((item, index) => !isDeepStrictEqual6(current[index], item));
38432
38485
  for (const item of changed.length && current.length === projection.length ? changed : [null])
38433
38486
  witnessChanged(new NativeDiscoveryWitnessChange("membership", item?.path ?? null, "Native discovery directory membership changed outside retired Codex roots"), witnessTolerance, item !== null && witnessTolerance?.acceptsMembership(item.path) === true);
38434
38487
  }
@@ -38451,7 +38504,7 @@ function verifyDiscovery(binding, codexRecovery, reviewedRootAliases, witnessTol
38451
38504
  safe6(source.path);
38452
38505
  continue;
38453
38506
  }
38454
- if (semanticCache?.supersededSources.some((previous) => isDeepStrictEqual5(previous, source))) {
38507
+ if (semanticCache?.supersededSources.some((previous) => isDeepStrictEqual6(previous, source))) {
38455
38508
  safe6(source.path);
38456
38509
  continue;
38457
38510
  }
@@ -38950,7 +39003,7 @@ function resolveDiscovery(options, exclusion) {
38950
39003
  const parents3 = [...new Set([...(retained.codexDisabledPluginSkills ?? []).map((control) => control.pluginParent), ...proof.appOnlyParents.map((parent) => parent.pluginParent)])];
38951
39004
  covered = covered.filter((source) => !codexSemanticCacheSourceRole(source, proof.witness.cacheRoot, parents3));
38952
39005
  }
38953
- if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual5(saved, source))) || [...roots].some((root) => !retained.roots.includes(root)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
39006
+ if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual6(saved, source))) || [...roots].some((root) => !retained.roots.includes(root)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
38954
39007
  unresolved("retained review is missing current runtime discovery coverage");
38955
39008
  return retained;
38956
39009
  }
@@ -39008,7 +39061,7 @@ function resolveDiscovery(options, exclusion) {
39008
39061
  throw new Error("Retired native materialization changed during discovery review");
39009
39062
  }
39010
39063
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
39011
- const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual5(candidate, source)) === index);
39064
+ const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual6(candidate, source)) === index);
39012
39065
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
39013
39066
  }
39014
39067
  function plugin(root) {
@@ -47756,7 +47809,7 @@ var init_execution_grants = __esm(() => {
47756
47809
  });
47757
47810
 
47758
47811
  // src/lib/execution-grant-client.ts
47759
- import { isDeepStrictEqual as isDeepStrictEqual6 } from "util";
47812
+ import { isDeepStrictEqual as isDeepStrictEqual7 } from "util";
47760
47813
  function id(value) {
47761
47814
  if (!grantIdentifier(value))
47762
47815
  throw new Error("Invalid execution grant profile or revision identifier.");
@@ -47799,7 +47852,7 @@ async function resolveExecutionGrant(input2) {
47799
47852
  if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
47800
47853
  throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
47801
47854
  }
47802
- if (!isDeepStrictEqual6(value.bindings.selection, snapshot2.selection) || !isDeepStrictEqual6(value.bindings.consumer, snapshot2.consumer)) {
47855
+ if (!isDeepStrictEqual7(value.bindings.selection, snapshot2.selection) || !isDeepStrictEqual7(value.bindings.consumer, snapshot2.consumer)) {
47803
47856
  throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
47804
47857
  }
47805
47858
  return value;
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.88",
28
+ version: "0.10.90",
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.88",
24143
+ version: "0.10.90",
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.88",
24146
+ version: "0.10.90",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
@@ -16,12 +16,32 @@ export declare const MANAGED_HOOK_CONTEXT_MAX_CHARS = 12000;
16
16
  export declare const MANAGED_HOOK_CONTEXT_MAX_SKILLS = 2;
17
17
  /** Part of the cap kept for the policy, notices and up to ten load offers. */
18
18
  export declare const MANAGED_HOOK_CONTEXT_RESERVE_CHARS = 2500;
19
+ /**
20
+ * A managed hook injects an unnamed prompt match only for a phrase of at least
21
+ * this many words. A one-word trigger says little about a turn's subject, more
22
+ * so in long text ("effect", "rollback", "handoff"), and several of them don't
23
+ * add up to a phrase, so such a match is offered as a load command instead
24
+ * (FAO-00144). SKILLS_HOOK_PROMPT_MIN_PHRASE_WORDS can only raise it.
25
+ */
26
+ export declare const MANAGED_HOOK_PROMPT_MIN_PHRASE_WORDS = 2;
27
+ /** A subagent's start carries no prompt to match against, so its parent's skills are offered, not injected. */
28
+ export declare const MANAGED_HOOK_INHERITED_BODIES = 0;
19
29
  /** The effective hook limits: the defaults above, or a valid lower environment override. */
20
30
  export declare function managedHookContextLimits(): {
21
31
  maxChars: number;
22
32
  maxSkills: number;
23
33
  bodyChars: number;
24
34
  };
35
+ /** The effective phrase length: the default above, or a valid higher environment override. */
36
+ export declare function managedHookPromptMinPhraseWords(): number;
37
+ /**
38
+ * The selection flags one agent's managed hook passes to its context child.
39
+ * Sumi's plugin and Cursor's prompt hook both discard the output of a prompt
40
+ * call and only deliver what a later call restores, so a pointer could never
41
+ * reach their models: it would turn a delivered body into nothing. They keep the
42
+ * plain selection until they can carry pointers.
43
+ */
44
+ export declare function managedHookSelectionArgs(agent: IntegrationAgent): string[];
25
45
  /** Clamp assembled hook context to the cap at a line boundary, with a fixed note. */
26
46
  export declare function capManagedHookContext(context: string, maxChars?: number): string;
27
47
  /**
@@ -5,6 +5,8 @@ export declare function selectedProfileId(explicit?: string): string;
5
5
  export interface ContextCommandOptions {
6
6
  selectionProfile?: string;
7
7
  cached?: boolean;
8
+ cacheFirst?: boolean;
9
+ pointerCacheFirst?: boolean;
8
10
  autoReconcileSafe?: boolean;
9
11
  autoMigrateSessionPin?: boolean;
10
12
  backgroundRenewal?: boolean;
@@ -16,6 +18,8 @@ export interface ContextCommandOptions {
16
18
  maxChars?: string;
17
19
  maxSkills?: string;
18
20
  file?: string;
21
+ minPromptPhraseWords?: string;
22
+ inheritedBodies?: string;
19
23
  }
20
24
  export declare function contextResolverOptions(options: ContextCommandOptions): SelectionResolverOptions;
21
25
  export declare function registerContextCommands(parent: Command): void;
package/dist/index.js CHANGED
@@ -9722,7 +9722,8 @@ var MEASURED_CODEX_CAPABILITIES = {
9722
9722
  "codex-cli 0.162.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
9723
9723
  "codex-cli 0.163.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
9724
9724
  "codex-cli 0.164.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
9725
- "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
9725
+ "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
9726
+ "codex-cli 0.166.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
9726
9727
  };
9727
9728
  function supportsCodexNativeCapability(version, capability) {
9728
9729
  return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
@@ -16368,7 +16369,7 @@ import { basename as basename15, dirname as dirname27, isAbsolute as isAbsolute2
16368
16369
  import { homedir as homedir5 } from "os";
16369
16370
  import { isAbsolute as isAbsolute25, join as join39, resolve as resolve25 } from "path";
16370
16371
  // package.json
16371
- var version = "0.10.88";
16372
+ var version = "0.10.90";
16372
16373
  var package_default = {
16373
16374
  name: "@hasna/skills",
16374
16375
  version,
@@ -24994,6 +24995,8 @@ function unknownNameRefusal(slug, profile) {
24994
24995
  const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
24995
24996
  return `Skill '${slug}' is not selected by this Skills profile or project lock.${hint} ` + `Read the accepted names with 'skills search <term>' or 'skills list --all --json'; accept one with 'skills pin <name>'. ${ACCEPTED_FORM}`;
24996
24997
  }
24998
+ var RECOVERABLE_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
24999
+ var UNWRITABLE_CODES = new Set(["EACCES", "EPERM", "EROFS", "ENOSPC", "EDQUOT"]);
24997
25000
 
24998
25001
  // src/lib/selected-manifest.ts
24999
25002
  function jsonEntry(entries, path) {
@@ -54368,6 +54371,12 @@ async function buildSkillContextOnce(input2, options, mayResolveAgain) {
54368
54371
  if (maxChars !== undefined && (!Number.isInteger(maxChars) || maxChars < 512 || maxChars > 64000) || !Number.isInteger(maxSkills) || maxSkills < 1 || maxSkills > 20) {
54369
54372
  throw new SkillSelectionError("INVALID_CONTEXT_BUDGET", "Context limits must be 512\u201364000 characters and 1\u201320 skills.");
54370
54373
  }
54374
+ const minPromptPhraseWords = options.minPromptPhraseWords ?? 0;
54375
+ const inheritedBodies = options.inheritedBodies ?? HISTORY_BODY_LIMITS["subagent-inherit"];
54376
+ if (!Number.isInteger(minPromptPhraseWords) || minPromptPhraseWords < 0 || !Number.isInteger(inheritedBodies) || inheritedBodies < 0) {
54377
+ throw new SkillSelectionError("INVALID_CONTEXT_BUDGET", "The prompt phrase and inherited body limits must be whole numbers of 0 or more.");
54378
+ }
54379
+ const historyBodyLimits = { ...HISTORY_BODY_LIMITS, "subagent-inherit": inheritedBodies };
54371
54380
  const profileId = input2.profileId ?? "default";
54372
54381
  const sessionId = input2.sessionId ? input2.agentId ? `${input2.sessionId}:${input2.agentId}` : input2.sessionId : undefined;
54373
54382
  if (input2.parentSessionId !== undefined) {
@@ -54397,8 +54406,9 @@ async function buildSkillContextOnce(input2, options, mayResolveAgain) {
54397
54406
  const key2 = selectionKey(selection);
54398
54407
  if (restoredOrder.has(key2))
54399
54408
  return [{ selection, reason: "session-restore", priority: 6, score: 2000, recency: restoredOrder.get(key2) }];
54400
- if (inheritedOrder.has(key2))
54401
- return [{ selection, reason: "subagent-inherit", priority: 6, score: 2000, recency: inheritedOrder.get(key2) }];
54409
+ const inherited = inheritedOrder.has(key2) ? { selection, reason: "subagent-inherit", priority: 6, score: 2000, recency: inheritedOrder.get(key2) } : undefined;
54410
+ if (inherited && inheritedBodies > 0)
54411
+ return [inherited];
54402
54412
  if (explicit.has(key2))
54403
54413
  return [{ selection, reason: "explicit", priority: 5, score: 1000 }];
54404
54414
  if (selection.triggers?.always)
@@ -54407,7 +54417,10 @@ async function buildSkillContextOnce(input2, options, mayResolveAgain) {
54407
54417
  if (matchedPath)
54408
54418
  return [{ selection, reason: "path", priority: 3, score: 500 }];
54409
54419
  const match = relevance.get(key2);
54410
- return match ? [{ selection, reason: "prompt", priority: match.priority, score: match.score, weak: match.weak }] : [];
54420
+ const weak = match !== undefined && (match.weak || match.priority === 1 && !match.phrases.some((phrase) => phrase.split(" ").length >= minPromptPhraseWords));
54421
+ if (match && !(inherited && weak))
54422
+ return [{ selection, reason: "prompt", priority: match.priority, score: match.score, weak }];
54423
+ return inherited ? [inherited] : [];
54411
54424
  }).sort((a, b) => b.priority - a.priority || (b.recency ?? -1) - (a.recency ?? -1) || b.score - a.score || a.selection.slug.localeCompare(b.selection.slug));
54412
54425
  const historyOffered = new Map;
54413
54426
  const candidates = ranked.filter((candidate2) => {
@@ -54417,6 +54430,9 @@ async function buildSkillContextOnce(input2, options, mayResolveAgain) {
54417
54430
  historyOffered.set(candidate2.reason, count + 1);
54418
54431
  return count < HISTORY_CANDIDATE_LIMIT;
54419
54432
  });
54433
+ const pointerSession = sessionId && /^[A-Za-z0-9._:@][A-Za-z0-9._:@-]{0,255}$/.test(sessionId) ? ` --session ${sessionId}` : "";
54434
+ const pointerCache = options.cached || options.pointerCacheFirst ? " --cache-first" : "";
54435
+ const loadCommandFor = (selection) => `skills load ${selection.slug}@${selection.version} --selection-profile ${profile.profileId}${pointerCache}${pointerSession}`;
54420
54436
  const delivered = [];
54421
54437
  let selections = [];
54422
54438
  const omitted = [];
@@ -54426,7 +54442,7 @@ async function buildSkillContextOnce(input2, options, mayResolveAgain) {
54426
54442
  const { selection, reason: reason2 } = candidates[index];
54427
54443
  if (!input2.restore && !explicit.has(selectionKey(selection)) && loaded.has(selectionKey(selection)))
54428
54444
  continue;
54429
- const loadCommand = `skills load ${selection.slug}@${selection.version} --selection-profile ${profile.profileId}`;
54445
+ const loadCommand = loadCommandFor(selection);
54430
54446
  if (selections.length >= maxSkills || ambiguous && reason2 === "prompt") {
54431
54447
  omitted.push({ slug: selection.slug, version: selection.version, reason: "skill-limit", loadCommand });
54432
54448
  continue;
@@ -54435,7 +54451,7 @@ async function buildSkillContextOnce(input2, options, mayResolveAgain) {
54435
54451
  omitted.push({ slug: selection.slug, version: selection.version, reason: "weak-match", loadCommand });
54436
54452
  continue;
54437
54453
  }
54438
- const historyLimit = HISTORY_BODY_LIMITS[reason2];
54454
+ const historyLimit = historyBodyLimits[reason2];
54439
54455
  if (historyLimit !== undefined) {
54440
54456
  const delivered2 = historyBodies.get(reason2) ?? 0;
54441
54457
  if (delivered2 >= historyLimit) {
@@ -54446,12 +54462,13 @@ async function buildSkillContextOnce(input2, options, mayResolveAgain) {
54446
54462
  if (reason2 === "prompt") {
54447
54463
  const candidate2 = candidates[index];
54448
54464
  let end = index;
54449
- while (end + 1 < candidates.length && candidates[end + 1].reason === "prompt" && candidates[end + 1].priority === candidate2.priority && candidates[end + 1].score === candidate2.score && !candidates[end + 1].weak)
54465
+ while (end + 1 < candidates.length && candidates[end + 1].reason === "prompt" && candidates[end + 1].priority === candidate2.priority && candidates[end + 1].score === candidate2.score)
54450
54466
  end++;
54451
- const tied = candidates.slice(index, end + 1).filter((entry) => explicit.has(selectionKey(entry.selection)) || !loaded.has(selectionKey(entry.selection)));
54467
+ const block = candidates.slice(index, end + 1).filter((entry) => explicit.has(selectionKey(entry.selection)) || !loaded.has(selectionKey(entry.selection)));
54468
+ const tied = block.filter((entry) => !entry.weak);
54452
54469
  if (tied.length > maxSkills - selections.length) {
54453
- for (const entry of tied)
54454
- omitted.push({ slug: entry.selection.slug, version: entry.selection.version, reason: "ambiguous-match", loadCommand: `skills load ${entry.selection.slug}@${entry.selection.version} --selection-profile ${profile.profileId}` });
54470
+ for (const entry of block)
54471
+ omitted.push({ slug: entry.selection.slug, version: entry.selection.version, reason: entry.weak ? "weak-match" : "ambiguous-match", loadCommand: loadCommandFor(entry.selection) });
54455
54472
  ambiguous = true;
54456
54473
  index = end;
54457
54474
  continue;
@@ -54478,7 +54495,7 @@ ${content}`;
54478
54495
  return considered.has(key2) || !explicit.has(key2) && loaded.has(key2);
54479
54496
  });
54480
54497
  for (const offer of offers)
54481
- omitted.push({ slug: offer.selection.slug, version: offer.selection.version, reason: offer.reason, loadCommand: `skills load ${offer.selection.slug}@${offer.selection.version} --selection-profile ${profile.profileId}` });
54498
+ omitted.push({ slug: offer.selection.slug, version: offer.selection.version, reason: offer.reason, loadCommand: loadCommandFor(offer.selection) });
54482
54499
  let notice;
54483
54500
  if (sessionId) {
54484
54501
  if (!resolved.session || [...loaded].some((key2) => !previouslyLoaded.has(key2))) {
@@ -54494,7 +54511,7 @@ ${content}`;
54494
54511
  slug: selection.slug,
54495
54512
  version: selection.version,
54496
54513
  reason: "write-busy",
54497
- loadCommand: `skills load ${selection.slug}@${selection.version} --selection-profile ${profile.profileId}`
54514
+ loadCommand: loadCommandFor(selection)
54498
54515
  })));
54499
54516
  selections = selections.filter((selection) => !unrecorded.has(selectionKey(selection)));
54500
54517
  delivered.splice(0, delivered.length, ...delivered.filter((entry) => !unrecorded.has(entry.key)));
@@ -1,4 +1,3 @@
1
- /** One resolution path for API-selected content. Local drafts cannot shadow a published selection. */
2
1
  import { type ProfileClient } from "./profile-client.js";
3
2
  import type { ResolvedSkillProfile, ResolvedSkillSelection } from "../types/skill-selection.js";
4
3
  import type { SkillBundleEntry } from "./skill-bundle.js";
@@ -6,6 +5,8 @@ import { type CachedSelectionProfile, type SelectionCacheOptions, type SkillSess
6
5
  export interface SelectionResolverOptions extends SelectionCacheOptions {
7
6
  client?: ProfileClient;
8
7
  cached?: boolean;
8
+ /** Read the verified cache while it can serve a load, and the service after that. */
9
+ cacheFirst?: boolean;
9
10
  /** Explicit cached authority binding, checked even when no credential is read. */
10
11
  authority?: string;
11
12
  workspaceId?: string;
@@ -42,9 +43,18 @@ export interface ResolvedSelectionContext {
42
43
  export declare function resolveSelectionContext(profileId: string, options?: SelectionResolverOptions): Promise<ResolvedSelectionContext>;
43
44
  export declare function readSelectedEntries(selection: ResolvedSkillSelection, context: ResolvedSelectionContext, options?: SelectionResolverOptions): Promise<SkillBundleEntry[]>;
44
45
  export declare function exactProfileSelection(spec: string, profile: ResolvedSkillProfile): ResolvedSkillSelection;
46
+ /** Refusals that mean the verified cache can't serve a read, not that the read is wrong. Identity and integrity failures never fall back. */
47
+ export declare const RECOVERABLE_CACHE_ERRORS: ReadonlySet<string>;
48
+ /**
49
+ * Load one selected skill. With cacheFirst it reads the verified cache while that
50
+ * can serve this load, which works with no network, and the Skills service after
51
+ * that, as the managed hook does. A subagent inherits its parent's verifiedAt and
52
+ * gets no renewal of its own, so a pointer it holds outlives the cache's limit.
53
+ */
45
54
  export declare function loadSelectedSkill(spec: string, profileId: string, options?: SelectionResolverOptions & {
46
55
  file?: string;
47
56
  }): Promise<{
57
+ sessionRecorded?: boolean | undefined;
48
58
  content: string;
49
59
  selection: ResolvedSkillSelection;
50
60
  receipt: {
@@ -66,6 +76,8 @@ export declare function loadSelectedSkill(spec: string, profileId: string, optio
66
76
  };
67
77
  };
68
78
  }>;
79
+ /** A session store that this process can't write: a sandbox, or a full disk or quota. */
80
+ export declare function isUnwritableStore(error: unknown): boolean;
69
81
  /** Pin the resolved profile metadata without copying a definition into the project. */
70
82
  export declare function pinSelectedSkillVersion(spec: string, profileId: string, options: SelectionResolverOptions & {
71
83
  projectDir: string;
@@ -15,6 +15,17 @@ export interface SkillContextInput {
15
15
  export interface SkillContextOptions extends SelectionResolverOptions {
16
16
  maxChars?: number;
17
17
  maxSkills?: number;
18
+ /**
19
+ * An unnamed prompt match whose longest matched phrase has fewer words than
20
+ * this is offered as a load command instead of being injected, however many
21
+ * such words matched. Named, explicit, path and required selections are not
22
+ * affected. Unset, every counted match may be injected.
23
+ */
24
+ minPromptPhraseWords?: number;
25
+ /** Print load pointers with --cache-first even though this context didn't come from the cache: a managed hook that fell back to the service. */
26
+ pointerCacheFirst?: boolean;
27
+ /** Bodies a child session receives from its parent's loaded skills; the rest are offered. Unset, HISTORY_BODY_LIMITS applies. */
28
+ inheritedBodies?: number;
18
29
  /** Called with the resolved pin before any payload is read (again if a new child is resolved again); metadata only. */
19
30
  onResolved?: (resolved: ResolvedSelectionContext) => void;
20
31
  /**
@@ -49,7 +49,7 @@ var __require = import.meta.require;
49
49
  // src/runtime-prerequisites.ts
50
50
  import { isAbsolute as isAbsolute7 } from "path";
51
51
  // package.json
52
- var version = "0.10.88";
52
+ var version = "0.10.90";
53
53
  var package_default = {
54
54
  name: "@hasna/skills",
55
55
  version,
@@ -2276,7 +2276,8 @@ var MEASURED_CODEX_CAPABILITIES = {
2276
2276
  "codex-cli 0.162.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
2277
2277
  "codex-cli 0.163.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
2278
2278
  "codex-cli 0.164.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
2279
- "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
2279
+ "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
2280
+ "codex-cli 0.166.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
2280
2281
  };
2281
2282
  function supportsCodexNativeCapability(version2, capability) {
2282
2283
  return typeof version2 === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version2) && MEASURED_CODEX_CAPABILITIES[version2].includes(capability);
package/dist/sdk/index.js CHANGED
@@ -32371,7 +32371,7 @@ var init_event_sink = __esm(() => {
32371
32371
  init_dist();
32372
32372
  });
32373
32373
  // package.json
32374
- var version = "0.10.88";
32374
+ var version = "0.10.90";
32375
32375
  var package_default = {
32376
32376
  name: "@hasna/skills",
32377
32377
  version,
@@ -39270,7 +39270,8 @@ var MEASURED_CODEX_CAPABILITIES = {
39270
39270
  "codex-cli 0.162.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
39271
39271
  "codex-cli 0.163.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
39272
39272
  "codex-cli 0.164.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
39273
- "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
39273
+ "codex-cli 0.165.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
39274
+ "codex-cli 0.166.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
39274
39275
  };
39275
39276
  function supportsCodexNativeCapability(version2, capability) {
39276
39277
  return typeof version2 === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version2) && MEASURED_CODEX_CAPABILITIES[version2].includes(capability);
@@ -55419,6 +55420,8 @@ function unknownNameRefusal(slug, profile) {
55419
55420
  const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
55420
55421
  return `Skill '${slug}' is not selected by this Skills profile or project lock.${hint} ` + `Read the accepted names with 'skills search <term>' or 'skills list --all --json'; accept one with 'skills pin <name>'. ${ACCEPTED_FORM}`;
55421
55422
  }
55423
+ var RECOVERABLE_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
55424
+ var UNWRITABLE_CODES = new Set(["EACCES", "EPERM", "EROFS", "ENOSPC", "EDQUOT"]);
55422
55425
 
55423
55426
  // src/lib/selected-run.ts
55424
55427
  async function resolveSelectedRun(spec, profileId, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.88",
3
+ "version": "0.10.90",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {