@hasna/skills 0.10.19 → 0.10.20

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
@@ -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.19",
20022
+ version: "0.10.20",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.10.19",
10
+ version: "0.10.20",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
package/bin/server.js CHANGED
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
24122
24122
  // package.json
24123
24123
  var package_default = {
24124
24124
  name: "@hasna/skills",
24125
- version: "0.10.19",
24125
+ version: "0.10.20",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
package/bin/worker.js CHANGED
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24125
24125
  // package.json
24126
24126
  var package_default = {
24127
24127
  name: "@hasna/skills",
24128
- version: "0.10.19",
24128
+ version: "0.10.20",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
package/dist/index.d.ts CHANGED
@@ -71,4 +71,4 @@ export type { RemoteCreditCheckoutOptions, RemoteCreditCheckout, RemoteCreditChe
71
71
  export { assertPortableAuthoringPath, type PortableAuthoringPathOptions } from "./lib/authoring-path.js";
72
72
  export { CLAUDE_COORDINATED_HOOK_EVENTS, type ClaudeCoordinatedHookEvent } from "./lib/claude-settings-witness.js";
73
73
  export { captureCodexSettings, upgradeCodexSettingsWitness } from "./lib/codex-settings-witness.js";
74
- export { projectCodexNativeSkillCatalog, captureCodexNativeSkillCatalog, isCodexNativeSkillDisabled, type CodexNativeSkillCatalog, type CodexNativeSkill } from "./lib/codex-native-skill-catalog.js";
74
+ export { projectCodexNativeSkillCatalog, projectCodexInstalledPlugins, projectCodexInstalledPluginEntries, captureCodexNativeSkillCatalog, isCodexNativeSkillDisabled, type CodexNativeSkillCatalog, type CodexNativeSkill, type CodexInstalledPlugin } from "./lib/codex-native-skill-catalog.js";
package/dist/index.js CHANGED
@@ -7262,7 +7262,7 @@ import {
7262
7262
  statSync as statSync10,
7263
7263
  writeFileSync as writeFileSync8
7264
7264
  } from "fs";
7265
- import { basename as basename10, dirname as dirname19, isAbsolute as isAbsolute17, join as join29, normalize as normalize3, relative as relative5, resolve as resolve17, sep as sep8 } from "path";
7265
+ import { basename as basename11, dirname as dirname19, isAbsolute as isAbsolute17, join as join29, normalize as normalize3, relative as relative6, resolve as resolve17, sep as sep8 } from "path";
7266
7266
 
7267
7267
  // src/lib/registry-data/index.ts
7268
7268
  var SKILLS = [];
@@ -8920,7 +8920,7 @@ var NATIVE_SKILL_ROOTS = [
8920
8920
  // src/lib/authoring-path.ts
8921
8921
  import { lstatSync as lstatSync22, realpathSync as realpathSync8 } from "fs";
8922
8922
  import { homedir as homedir6 } from "os";
8923
- import { basename as basename9, dirname as dirname18, join as join28, resolve as resolve16, sep as sep7 } from "path";
8923
+ import { basename as basename10, dirname as dirname18, join as join28, resolve as resolve16, sep as sep7 } from "path";
8924
8924
 
8925
8925
  // src/lib/agent-adapters.ts
8926
8926
  var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes"];
@@ -9105,7 +9105,7 @@ function assertAgentPolicyCollections(policy) {
9105
9105
  // src/lib/agent-discovery.ts
9106
9106
  import { createHash as createHash17 } from "crypto";
9107
9107
  import { lstatSync as lstatSync21, readFileSync as readFileSync17, readdirSync as readdirSync10, realpathSync as realpathSync7 } from "fs";
9108
- import { basename as basename8, dirname as dirname17, isAbsolute as isAbsolute16, join as join27, resolve as resolve15, sep as sep6 } from "path";
9108
+ import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as join27, resolve as resolve15, sep as sep6 } from "path";
9109
9109
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
9110
9110
 
9111
9111
  // node_modules/yaml/dist/index.js
@@ -9616,7 +9616,7 @@ function hashDiscoveryPathFile(path, budget, changes) {
9616
9616
 
9617
9617
  // src/lib/plugin-discovery.ts
9618
9618
  import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as lstatSync18, openSync as openSync10, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
9619
- import { basename as basename6, dirname as dirname14, isAbsolute as isAbsolute14, join as join26, resolve as resolve13 } from "path";
9619
+ import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as join26, resolve as resolve13 } from "path";
9620
9620
 
9621
9621
  // src/lib/plugin-admission.ts
9622
9622
  import { homedir as homedir5 } from "os";
@@ -10877,7 +10877,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
10877
10877
  // package.json
10878
10878
  var package_default = {
10879
10879
  name: "@hasna/skills",
10880
- version: "0.10.19",
10880
+ version: "0.10.20",
10881
10881
  description: "Skills library for AI coding agents",
10882
10882
  type: "module",
10883
10883
  bin: {
@@ -12710,7 +12710,7 @@ import {
12710
12710
  writeFileSync as writeFileSync5
12711
12711
  } from "fs";
12712
12712
  import { homedir as homedir3 } from "os";
12713
- import { basename as basename5, dirname as dirname12, join as join18 } from "path";
12713
+ import { basename as basename6, dirname as dirname12, join as join18 } from "path";
12714
12714
 
12715
12715
  // src/lib/claude-settings-witness.ts
12716
12716
  import { createHash as createHash8 } from "crypto";
@@ -13024,7 +13024,7 @@ function hashNativeJsonControls(text2, excludedMetadata) {
13024
13024
  }
13025
13025
 
13026
13026
  // src/lib/codex-plugin-skill-controls.ts
13027
- import { dirname as dirname10, join as join14, resolve as resolve9, sep as sep4 } from "path";
13027
+ import { basename as basename5, dirname as dirname10, join as join14, relative as relative4, resolve as resolve9, sep as sep4 } from "path";
13028
13028
  import { lstatSync as lstatSync12, readdirSync as readdirSync6, readlinkSync as readlinkSync3, realpathSync as realpathSync5 } from "fs";
13029
13029
 
13030
13030
  // src/lib/codex-native-skill-catalog.ts
@@ -13178,6 +13178,7 @@ async function connectCodexHookRpc(options) {
13178
13178
  var MAX_SKILLS = 4096;
13179
13179
  var MAX_NAME_BYTES = 1024;
13180
13180
  var MAX_PATH_BYTES = 16384;
13181
+ var MAX_PLUGIN_ID_BYTES = 1024;
13181
13182
  var SUPPORTED_NAME_CONTROL_VERSIONS = new Set(["codex-cli 0.159.2"]);
13182
13183
  function refuse() {
13183
13184
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_INVALID");
@@ -13201,6 +13202,35 @@ function projectCodexNativeSkillCatalog(response, cwd) {
13201
13202
  return { name: value.name, path: value.path, enabled: value.enabled, pluginId: value.pluginId ?? null };
13202
13203
  });
13203
13204
  }
13205
+ function projectCodexInstalledPlugins(response) {
13206
+ if (!object4(response) || !Array.isArray(response.marketplaces) || !Array.isArray(response.marketplaceLoadErrors) || response.marketplaceLoadErrors.length !== 0 || response.marketplaces.length > MAX_SKILLS)
13207
+ refuse();
13208
+ const plugins = [];
13209
+ for (const marketplace of response.marketplaces) {
13210
+ if (!object4(marketplace) || !scalar(marketplace.name, MAX_NAME_BYTES) || !Array.isArray(marketplace.plugins) || marketplace.plugins.length > MAX_SKILLS)
13211
+ refuse();
13212
+ for (const value of marketplace.plugins) {
13213
+ if (!object4(value) || !scalar(value.id, MAX_PLUGIN_ID_BYTES) || !scalar(value.name, MAX_NAME_BYTES) || value.id !== `${value.name}@${marketplace.name}`)
13214
+ refuse();
13215
+ plugins.push({ ...value, marketplace: marketplace.name });
13216
+ }
13217
+ }
13218
+ return projectCodexInstalledPluginEntries(plugins);
13219
+ }
13220
+ function projectCodexInstalledPluginEntries(value) {
13221
+ if (!Array.isArray(value) || value.length > MAX_SKILLS)
13222
+ refuse();
13223
+ const ids = new Set;
13224
+ return value.map((item) => {
13225
+ if (!object4(item) || !scalar(item.id, MAX_PLUGIN_ID_BYTES) || !scalar(item.name, MAX_NAME_BYTES) || typeof item.installed !== "boolean" || typeof item.enabled !== "boolean" || item.localVersion !== null && !scalar(item.localVersion, MAX_NAME_BYTES))
13226
+ refuse();
13227
+ const at = item.id.lastIndexOf("@");
13228
+ if (at < 1 || item.id.slice(0, at) !== item.name || at === item.id.length - 1 || ids.has(item.id))
13229
+ refuse();
13230
+ ids.add(item.id);
13231
+ return { id: item.id, name: item.name, installed: item.installed, enabled: item.enabled, localVersion: item.localVersion ?? null };
13232
+ });
13233
+ }
13204
13234
  async function captureCodexNativeSkillCatalog(options, connect = connectCodexHookRpc) {
13205
13235
  if (!absolutePath2(options.cwd) || !absolutePath2(options.home))
13206
13236
  refuse();
@@ -13214,7 +13244,13 @@ async function captureCodexNativeSkillCatalog(options, connect = connectCodexHoo
13214
13244
  if (!SUPPORTED_NAME_CONTROL_VERSIONS.has(rpc.version))
13215
13245
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_UNSUPPORTED_VERSION");
13216
13246
  const response = await rpc.request("skills/list", { cwds: [options.cwd], forceReload: true });
13217
- return { version: rpc.version, cwd: options.cwd, skills: projectCodexNativeSkillCatalog(response, options.cwd) };
13247
+ const installed = await rpc.request("plugin/installed", { cwds: [options.cwd] });
13248
+ return {
13249
+ version: rpc.version,
13250
+ cwd: options.cwd,
13251
+ skills: projectCodexNativeSkillCatalog(response, options.cwd),
13252
+ plugins: projectCodexInstalledPlugins(installed)
13253
+ };
13218
13254
  } finally {
13219
13255
  await rpc.close();
13220
13256
  }
@@ -13259,6 +13295,7 @@ function isCodexNativeSkillDisabled(skill, rules) {
13259
13295
 
13260
13296
  // src/lib/codex-plugin-skill-controls.ts
13261
13297
  var identifier2 = (v) => typeof v === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(v);
13298
+ var CODEX_DEFAULT_AGENT_PLUGIN_VERSION = "1.0.0";
13262
13299
  function refuse2() {
13263
13300
  throw new Error("CODEX_NATIVE_SKILL_IDENTITY_UNSUPPORTED: review native plugin names and hook controls");
13264
13301
  }
@@ -13297,6 +13334,9 @@ function rootControls(root, read2) {
13297
13334
  }
13298
13335
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
13299
13336
  refuse2();
13337
+ const cacheVersion = basename5(root), declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
13338
+ if (declaredVersion === null || (declaredVersion ? declaredVersion !== cacheVersion : cacheVersion !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
13339
+ refuse2();
13300
13340
  if (manifest.hooks !== undefined || manifest.mcpServers !== undefined || manifest.commands !== undefined || manifest.apps !== undefined && manifest.apps !== "./.app.json" || ["hooks", ".mcp.json"].some((path) => lstatSync12(join14(root, path), { throwIfNoEntry: false })))
13301
13341
  refuse2();
13302
13342
  const app = join14(root, ".app.json"), present = lstatSync12(app, { throwIfNoEntry: false });
@@ -13373,22 +13413,48 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
13373
13413
  return false;
13374
13414
  }
13375
13415
  }
13376
- function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2) {
13416
+ function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, rules) {
13377
13417
  if (catalog?.version !== "codex-cli 0.159.2" || catalog.cwd !== cwd)
13378
13418
  refuse2();
13379
13419
  const skills = projectCodexNativeSkillCatalog({ data: [{ cwd, errors: [], skills: catalog.skills }] }, cwd);
13380
13420
  const allowed = new Set(documents), result = [];
13381
- for (const skill of skills) {
13382
- if (!allowed.has(skill.path) || !skill.path.startsWith(cache + sep4))
13421
+ let installedPlugins;
13422
+ try {
13423
+ installedPlugins = projectCodexInstalledPluginEntries(catalog.plugins ?? []);
13424
+ } catch {
13425
+ refuse2();
13426
+ }
13427
+ const candidates = catalog.plugins === undefined ? skills.map((skill) => skill.path).filter((path) => allowed.has(path)) : allowed;
13428
+ for (const document2 of candidates) {
13429
+ if (!document2.startsWith(cache + sep4))
13383
13430
  continue;
13384
- if (skills.filter((other) => other.name === skill.name).length !== 1)
13431
+ const parsed = identity3(document2, cache, read2);
13432
+ if (parsed.name === "skills-cli" || parsed.name.endsWith(":skills-cli"))
13433
+ refuse2();
13434
+ const cacheParts = relative4(cache, parsed.pluginParent).split(sep4);
13435
+ if (cacheParts.length !== 2 || cacheParts.some((part) => !identifier2(part)))
13385
13436
  refuse2();
13386
- if (!skill.pluginId || skill.name === "skills-cli" || skill.name.endsWith(":skills-cli"))
13437
+ const [marketplace, pluginName] = cacheParts;
13438
+ if (pluginName !== parsed.namespace)
13387
13439
  refuse2();
13388
- const parsed = identity3(skill.path, cache, read2);
13389
- if (parsed.name !== skill.name)
13440
+ const expectedPluginId = `${pluginName}@${marketplace}`;
13441
+ const listed = skills.filter((skill) => skill.path === document2);
13442
+ if (listed.length > 1)
13390
13443
  refuse2();
13391
- result.push({ ...parsed, pluginId: skill.pluginId });
13444
+ if (listed.length === 1) {
13445
+ if (listed[0].name !== parsed.name || listed[0].pluginId !== expectedPluginId)
13446
+ refuse2();
13447
+ } else {
13448
+ const installed = installedPlugins.filter((plugin) => plugin.id === expectedPluginId && plugin.name === pluginName);
13449
+ if (installed.length !== 1 || !installed[0].installed || !installed[0].enabled || !installed[0].localVersion)
13450
+ refuse2();
13451
+ if (installed[0].localVersion !== basename5(dirname10(dirname10(dirname10(document2)))))
13452
+ continue;
13453
+ const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document2 && rule?.enabled === false);
13454
+ if (!exactPathDeny || !isCodexNativeSkillDisabled({ name: parsed.name, path: document2, pluginId: expectedPluginId, enabled: true }, rules))
13455
+ refuse2();
13456
+ }
13457
+ result.push({ ...parsed, pluginId: expectedPluginId });
13392
13458
  }
13393
13459
  if (!result.length || new Set(result.map((item) => item.name)).size !== result.length)
13394
13460
  refuse2();
@@ -13531,7 +13597,7 @@ function upgradeCodexSettingsWitness(previous, reviewedSettingsPath) {
13531
13597
  // src/lib/agent-integration.ts
13532
13598
  import { createHash as createHash11, randomUUID as randomUUID2 } from "crypto";
13533
13599
  import { existsSync as existsSync8, lstatSync as lstatSync15, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync6, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync8, closeSync as closeSync8, fsyncSync as fsyncSync2, fstatSync as fstatSync8, readSync as readSync6, constants as constants7, linkSync } from "fs";
13534
- import { dirname as dirname11, join as join17, resolve as resolve10, relative as relative4, isAbsolute as isAbsolute11, sep as sep5 } from "path";
13600
+ import { dirname as dirname11, join as join17, resolve as resolve10, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
13535
13601
  import { homedir as homedir2 } from "os";
13536
13602
 
13537
13603
  // src/lib/managed-policy.ts
@@ -13950,7 +14016,7 @@ function readRootAlias(home, agent) {
13950
14016
  const targetStat = lstatSync15(targetPath, { throwIfNoEntry: false });
13951
14017
  if (!targetStat?.isDirectory())
13952
14018
  throw new Error(`Agent root alias must target an existing directory: ${alias}`);
13953
- const target = realpathSync6(targetPath), inside = relative4(realHome, target);
14019
+ const target = realpathSync6(targetPath), inside = relative5(realHome, target);
13954
14020
  if (!inside || inside === ".." || inside.startsWith(`..${sep5}`) || isAbsolute11(inside))
13955
14021
  throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
13956
14022
  return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
@@ -13968,7 +14034,7 @@ function rootAliases(home, allowed = false) {
13968
14034
  }
13969
14035
  function canonicalAgentPath(path, aliases) {
13970
14036
  const absolute = resolve10(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
13971
- return binding ? join17(binding.target, relative4(binding.alias, absolute)) : absolute;
14037
+ return binding ? join17(binding.target, relative5(binding.alias, absolute)) : absolute;
13972
14038
  }
13973
14039
  function disabledCodexSkillPaths(config, aliases) {
13974
14040
  const skills = config.skills;
@@ -14039,7 +14105,7 @@ function treeHash(root) {
14039
14105
  const stat = lstatSync15(path);
14040
14106
  if (stat.isSymbolicLink())
14041
14107
  throw new Error(`Refusing symlink in skill: ${path}`);
14042
- const rel = relative4(root, path);
14108
+ const rel = relative5(root, path);
14043
14109
  if (stat.isDirectory()) {
14044
14110
  hash2.update(`d\x00${rel}\x00`);
14045
14111
  for (const name of readdirSync8(path).sort())
@@ -14065,7 +14131,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
14065
14131
  const identities = new Set;
14066
14132
  const selected = [];
14067
14133
  for (const target of manifest.targets) {
14068
- const projectRoot = resolve10(target.projectRoot), normalized = relative4(projectRoot, resolve10(projectRoot, target.path));
14134
+ const projectRoot = resolve10(target.projectRoot), normalized = relative5(projectRoot, resolve10(projectRoot, target.path));
14069
14135
  if (!isAbsolute11(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync8(projectRoot) || !lstatSync15(projectRoot).isDirectory())
14070
14136
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
14071
14137
  assertSafePath(projectRoot);
@@ -14420,7 +14486,7 @@ function planAgentIntegration(options) {
14420
14486
  const bytes = readNativeBytes(path, 1024 * 1024);
14421
14487
  observedNativeSources.push({ path, sha256: sha2(bytes) });
14422
14488
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
14423
- }) : policy.bridge?.codexPluginSkills ?? [];
14489
+ }, codexConfig.skills?.config ?? []) : policy.bridge?.codexPluginSkills ?? [];
14424
14490
  const changes = [];
14425
14491
  for (const agent of [...new Set(options.agents)]) {
14426
14492
  if (!INTEGRATION_AGENTS.includes(agent))
@@ -14498,7 +14564,7 @@ function planAgentIntegration(options) {
14498
14564
  profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
14499
14565
  disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join17(home, ".codex", "skills", ".system"), aliases) + sep5)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
14500
14566
  rootAliases: aliases,
14501
- ...codexPluginSkills.length ? { codexPluginSkills, codexPluginSkillReview: options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha2(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd) })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join17(home, ".codex/config.toml"), aliases))?.before === null ? null : sha2(readOptional(canonicalAgentPath(join17(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview } : {},
14567
+ ...codexPluginSkills.length ? { codexPluginSkills, codexPluginSkillReview: options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha2(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join17(home, ".codex/config.toml"), aliases))?.before === null ? null : sha2(readOptional(canonicalAgentPath(join17(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview } : {},
14502
14568
  discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
14503
14569
  } };
14504
14570
  const serializedPolicy = serializeManagedSkillPolicy(nextPolicy);
@@ -14516,7 +14582,7 @@ function planClaudePreToolUseHookUpdate(options) {
14516
14582
  function claudeSettingsConsumers(home, path, discoveries, aliases) {
14517
14583
  const canonical4 = (value) => canonicalAgentPath(value, aliases);
14518
14584
  const containsSettings = (root) => {
14519
- const distance = relative4(canonical4(root), path);
14585
+ const distance = relative5(canonical4(root), path);
14520
14586
  return distance === "" || distance !== ".." && !distance.startsWith(`..${sep5}`) && !isAbsolute11(distance);
14521
14587
  };
14522
14588
  return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical4(join17(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical4(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
@@ -15261,7 +15327,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
15261
15327
  return { action, path: skillMdPath };
15262
15328
  const parentDir = dirname12(dir);
15263
15329
  mkdirSync6(parentDir, { recursive: true });
15264
- const transactionDir = mkdtempSync(join18(parentDir, `.hasna-skills-write-${basename5(dir)}-`));
15330
+ const transactionDir = mkdtempSync(join18(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
15265
15331
  const candidateDir = join18(transactionDir, "candidate");
15266
15332
  const backupDir = join18(transactionDir, "backup");
15267
15333
  const candidateSkillMdPath = join18(candidateDir, "SKILL.md");
@@ -16566,7 +16632,7 @@ function directoryNames(path) {
16566
16632
  function snapshotPluginTree(root, options = {}) {
16567
16633
  const chain = ancestors(root), entries = [], files = [], directories = [];
16568
16634
  let bytes = 0, visited = 0;
16569
- const visit2 = (directory, relative5) => {
16635
+ const visit2 = (directory, relative6) => {
16570
16636
  const before = stat(directory);
16571
16637
  pluginNeed(before && before.isDirectory() && !before.isSymbolicLink(), "Plugin tree must contain real directories");
16572
16638
  const names = directoryNames(directory);
@@ -16574,18 +16640,18 @@ function snapshotPluginTree(root, options = {}) {
16574
16640
  directories.push({ path: directory, stat: before, names });
16575
16641
  for (const name of names) {
16576
16642
  pluginNeed(++visited <= PLUGIN_PROJECTION_LIMITS.files * 50, "Plugin tree membership exceeds its limit");
16577
- const path = relative5 ? `${relative5}/${name}` : name;
16643
+ const path = relative6 ? `${relative6}/${name}` : name;
16578
16644
  pluginNeed(new TextEncoder().encode(path).byteLength <= 100 && !/[\\:\x00-\x1f\x7f]/u.test(path), "Plugin entry path exceeds its limit or is unsafe");
16579
16645
  const absolute = join23(directory, name), entryStat = stat(absolute);
16580
16646
  pluginNeed(entryStat, "Plugin tree member disappeared");
16581
- if (relative5 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16647
+ if (relative6 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16582
16648
  const bytes2 = readBytes(absolute, 32, "native-pin");
16583
16649
  pluginNeed(/^[1-9][0-9]{12}$/.test(new TextDecoder().decode(bytes2)), "Malformed native orphan timestamp");
16584
16650
  same3(entryStat, stat(absolute));
16585
16651
  files.push({ path: absolute, stat: entryStat });
16586
16652
  continue;
16587
16653
  }
16588
- if (relative5 === "" && name === ".in_use" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16654
+ if (relative6 === "" && name === ".in_use" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16589
16655
  pluginNeed(entryStat.isDirectory() && !entryStat.isSymbolicLink(), "Native plugin pins require a real directory");
16590
16656
  owner(entryStat);
16591
16657
  const pins = directoryNames(absolute);
@@ -17268,7 +17334,7 @@ function hashManagedPluginRegistry(path, managed) {
17268
17334
  pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join26(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
17269
17335
  const approval = (producer) => {
17270
17336
  pluginText(producer);
17271
- const digest = `sha256:${basename6(producer)}`;
17337
+ const digest = `sha256:${basename7(producer)}`;
17272
17338
  const receipt2 = readPluginAdmissionReceipt(witness.storeRoot, witness.bindingId, digest);
17273
17339
  pluginNeed(receipt2.materializedPath === producer, "Managed producer path is not an approved immutable projection");
17274
17340
  if (!verified.has(producer)) {
@@ -17332,7 +17398,7 @@ function captureManagedPluginRegistry(path, managedPlugins) {
17332
17398
  // src/lib/claude-marketplace-registry.ts
17333
17399
  import { createHash as createHash14 } from "crypto";
17334
17400
  import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync19, openSync as openSync11, readSync as readSync9 } from "fs";
17335
- import { basename as basename7, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve14 } from "path";
17401
+ import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve14 } from "path";
17336
17402
  var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
17337
17403
  function need4(value, reason) {
17338
17404
  if (!value)
@@ -17354,7 +17420,7 @@ function parents2(path) {
17354
17420
  }
17355
17421
  function readRegistry2(path, budget) {
17356
17422
  absolutePath3(path);
17357
- need4(basename7(path) === "known_marketplaces.json", "requires known_marketplaces.json");
17423
+ need4(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
17358
17424
  need4(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
17359
17425
  const ancestors2 = parents2(path), initial = lstatSync19(path, { bigint: true });
17360
17426
  need4(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
@@ -17694,9 +17760,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
17694
17760
  safe4(root2);
17695
17761
  }
17696
17762
  function assertMarketplaceBinding(binding, source) {
17697
- if (source.hashMode === "codex-settings-v1" && (binding.agent !== "codex" || binding.method !== "reviewed" || basename8(source.path) !== "config.toml"))
17763
+ if (source.hashMode === "codex-settings-v1" && (binding.agent !== "codex" || binding.method !== "reviewed" || basename9(source.path) !== "config.toml"))
17698
17764
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
17699
- if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
17765
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename9(source.path) !== "settings.json"))
17700
17766
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
17701
17767
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
17702
17768
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -17993,7 +18059,7 @@ function physicalPath(path) {
17993
18059
  let cursor = resolve16(path);
17994
18060
  const suffix = [];
17995
18061
  while (!lstatSync22(cursor, { throwIfNoEntry: false })) {
17996
- suffix.unshift(basename9(cursor));
18062
+ suffix.unshift(basename10(cursor));
17997
18063
  const parent = dirname18(cursor);
17998
18064
  if (parent === cursor)
17999
18065
  return resolve16(path);
@@ -18212,7 +18278,7 @@ function physicalImportPath(path) {
18212
18278
  let ancestor = resolve17(path);
18213
18279
  const suffix = [];
18214
18280
  while (!existsSync13(ancestor)) {
18215
- suffix.unshift(basename10(ancestor));
18281
+ suffix.unshift(basename11(ancestor));
18216
18282
  ancestor = dirname19(ancestor);
18217
18283
  }
18218
18284
  return join29(realpathSync9(ancestor), ...suffix);
@@ -18221,7 +18287,7 @@ function assertDisjointImportPaths(source, destination) {
18221
18287
  const sourcePath = realpathSync9(source);
18222
18288
  const destinationPath = physicalImportPath(destination);
18223
18289
  const contains2 = (parent, child) => {
18224
- const path = relative5(parent, child);
18290
+ const path = relative6(parent, child);
18225
18291
  return path === "" || !isAbsolute17(path) && path !== ".." && !path.startsWith(`..${sep8}`);
18226
18292
  };
18227
18293
  if (contains2(sourcePath, destinationPath) || contains2(destinationPath, sourcePath)) {
@@ -18237,8 +18303,8 @@ function portPortableSkill(sourcePath, options = {}) {
18237
18303
  const explicitName = options.name != null;
18238
18304
  const skillName = normalizeNewPortableSkillName(options.name ?? inferred.name);
18239
18305
  if (isOfficialSkillName(skillName) && !options.allowShadow) {
18240
- const sourceSlug = safeNormalizeName(basename10(absoluteSource));
18241
- const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename10(absoluteSource)}')` : ""} matches a bundled official skill.`;
18306
+ const sourceSlug = safeNormalizeName(basename11(absoluteSource));
18307
+ const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename11(absoluteSource)}')` : ""} matches a bundled official skill.`;
18242
18308
  throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
18243
18309
  }
18244
18310
  const readOnlyDestination = join29(resolvePortableSkillsRoot(options, false), skillName);
@@ -18821,7 +18887,7 @@ function getAllTags() {
18821
18887
  // src/lib/run-state.ts
18822
18888
  import { createHash as createHash18, randomBytes } from "crypto";
18823
18889
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync19, readdirSync as readdirSync13, statSync as statSync11, writeFileSync as writeFileSync9 } from "fs";
18824
- import { extname, join as join31, relative as relative6 } from "path";
18890
+ import { extname, join as join31, relative as relative7 } from "path";
18825
18891
  function createSkillRun(params, targetDir = process.cwd()) {
18826
18892
  const now = new Date;
18827
18893
  const id = createRunId(now);
@@ -18977,7 +19043,7 @@ function createRunId(now) {
18977
19043
  return `run_${now.getTime().toString(36)}_${randomBytes(4).toString("hex")}`;
18978
19044
  }
18979
19045
  function toProjectRelative(targetDir, path) {
18980
- const rel = relative6(targetDir, path).split(/[\\/]/).join("/");
19046
+ const rel = relative7(targetDir, path).split(/[\\/]/).join("/");
18981
19047
  return rel.startsWith(".") ? rel : `./${rel}`;
18982
19048
  }
18983
19049
  function mimeForPath(path) {
@@ -41951,7 +42017,7 @@ function findSkillsParityForMcpTool(tool) {
41951
42017
  }
41952
42018
  // src/lib/registry-sync.ts
41953
42019
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync12 } from "fs";
41954
- import { dirname as dirname21, relative as relative7 } from "path";
42020
+ import { dirname as dirname21, relative as relative8 } from "path";
41955
42021
  function createRegistrySyncArtifact(options = {}) {
41956
42022
  const profile = options.profile ?? "all";
41957
42023
  const includeDocs = options.includeDocs ?? true;
@@ -41963,7 +42029,7 @@ function createRegistrySyncArtifact(options = {}) {
41963
42029
  const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
41964
42030
  const skills = registry2.map((skill) => {
41965
42031
  const skillPath = getSkillPath(skill.name);
41966
- const directory2 = relative7(process.cwd(), skillPath) || skillPath;
42032
+ const directory2 = relative8(process.cwd(), skillPath) || skillPath;
41967
42033
  const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
41968
42034
  const docs = includeDocs ? buildDocs(skill.name) : undefined;
41969
42035
  return {
@@ -43161,7 +43227,7 @@ import {
43161
43227
  statSync as statSync12,
43162
43228
  writeFileSync as writeFileSync13
43163
43229
  } from "fs";
43164
- import { dirname as dirname23, join as join35, normalize as normalize4, relative as relative8, sep as sep9 } from "path";
43230
+ import { dirname as dirname23, join as join35, normalize as normalize4, relative as relative9, sep as sep9 } from "path";
43165
43231
  var SKILLS_STORAGE_TABLES = [
43166
43232
  "skills_sync_records",
43167
43233
  "skills_sync_cursors"
@@ -43324,7 +43390,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
43324
43390
  if (existsSync19(projectStateDir)) {
43325
43391
  for (const filePath of walkFiles2(projectStateDir)) {
43326
43392
  const bytes = readFileSync23(filePath);
43327
- const relativePath = toPosix(relative8(targetDir, filePath));
43393
+ const relativePath = toPosix(relative9(targetDir, filePath));
43328
43394
  files.push({
43329
43395
  path: relativePath,
43330
43396
  sizeBytes: bytes.byteLength,
@@ -43745,7 +43811,7 @@ import {
43745
43811
  statSync as statSync14,
43746
43812
  writeFileSync as writeFileSync14
43747
43813
  } from "fs";
43748
- import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative9, resolve as resolve18, sep as sep11 } from "path";
43814
+ import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative10, resolve as resolve18, sep as sep11 } from "path";
43749
43815
 
43750
43816
  // src/lib/portable-snapshot-filter.ts
43751
43817
  import { readdirSync as readdirSync15, statSync as statSync13 } from "fs";
@@ -44010,7 +44076,7 @@ function writeStationSnapshot(options) {
44010
44076
  const untouched = [];
44011
44077
  for (const plan of plans) {
44012
44078
  const destination = resolve18(repoRoot, plan.destination);
44013
- const destinationRelative = relative9(repoRoot, destination);
44079
+ const destinationRelative = relative10(repoRoot, destination);
44014
44080
  if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep11) || isAbsolute19(destinationRelative)) {
44015
44081
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
44016
44082
  }
@@ -44613,9 +44679,9 @@ async function buildSkillContext(input2, options = {}) {
44613
44679
  if (prompt.length > 128 * 1024 || (input2.paths?.length ?? 0) > 100 || (input2.skills?.length ?? 0) > 100) {
44614
44680
  throw new SkillSelectionError("CONTEXT_INPUT_TOO_LARGE", "The Skills context input exceeds its size limit.");
44615
44681
  }
44616
- const maxChars = options.maxChars ?? 8000;
44682
+ const maxChars = options.maxChars;
44617
44683
  const maxSkills = options.maxSkills ?? 3;
44618
- if (!Number.isInteger(maxChars) || maxChars < 512 || maxChars > 64000 || !Number.isInteger(maxSkills) || maxSkills < 1 || maxSkills > 20) {
44684
+ if (maxChars !== undefined && (!Number.isInteger(maxChars) || maxChars < 512 || maxChars > 64000) || !Number.isInteger(maxSkills) || maxSkills < 1 || maxSkills > 20) {
44619
44685
  throw new SkillSelectionError("INVALID_CONTEXT_BUDGET", "Context limits must be 512\u201364000 characters and 1\u201320 skills.");
44620
44686
  }
44621
44687
  const profileId = input2.profileId ?? "default";
@@ -44672,7 +44738,7 @@ async function buildSkillContext(input2, options = {}) {
44672
44738
  const { content } = readSelectedDocument(entries);
44673
44739
  const section = `Skill ${selection.slug}@${selection.version} (${selection.bundleDigest}; ${reason})
44674
44740
  ${content}`;
44675
- if (chars + section.length + (sections.length ? 2 : 0) > maxChars) {
44741
+ if (maxChars !== undefined && chars + section.length + (sections.length ? 2 : 0) > maxChars) {
44676
44742
  omitted.push({ slug: selection.slug, version: selection.version, reason: "context-budget", loadCommand });
44677
44743
  continue;
44678
44744
  }
@@ -44871,6 +44937,8 @@ export {
44871
44937
  publicDiscoveryDocumentation,
44872
44938
  publicDiscoveryDependencies,
44873
44939
  projectCodexNativeSkillCatalog,
44940
+ projectCodexInstalledPlugins,
44941
+ projectCodexInstalledPluginEntries,
44874
44942
  preparePrivatePublication,
44875
44943
  portPortableSkillDirectory,
44876
44944
  portPortableSkill,
@@ -10,10 +10,26 @@ export interface CodexNativeSkillCatalog {
10
10
  version: string;
11
11
  cwd: string;
12
12
  skills: CodexNativeSkill[];
13
+ /** Added in 0.10.20; older reviewed skills/list receipts remain valid for listed skills. */
14
+ plugins?: CodexInstalledPlugin[];
15
+ }
16
+ /** Safe installed-plugin identity needed when Codex hides a path-disabled skill. */
17
+ export interface CodexInstalledPlugin {
18
+ id: string;
19
+ name: string;
20
+ installed: boolean;
21
+ enabled: boolean;
22
+ localVersion: string | null;
13
23
  }
14
24
  /** Project the measured native skills/list contract without retaining descriptions,
15
25
  * links, interfaces, errors, or any other untrusted native text. */
16
26
  export declare function projectCodexNativeSkillCatalog(response: unknown, cwd: string): CodexNativeSkill[];
27
+ /** Project only the installed plugin identity needed to bind cache inventory.
28
+ * Marketplace errors fail closed because an incomplete inventory cannot prove
29
+ * that an absent skill belongs to the one measured installed plugin. */
30
+ export declare function projectCodexInstalledPlugins(response: unknown): CodexInstalledPlugin[];
31
+ /** Validate the bounded stored projection when it is consumed by a review. */
32
+ export declare function projectCodexInstalledPluginEntries(value: unknown): CodexInstalledPlugin[];
17
33
  /** Read one consumer's catalog through an owned, bounded native client. The RPC
18
34
  * transport owns request deadlines and child shutdown. Native discovery can
19
35
  * maintain its own caches; callers must constrain writes when requiring isolation. */
@@ -10,7 +10,7 @@ export interface CodexPluginSkillControl {
10
10
  type Read = (path: string) => string;
11
11
  /** Capability controls remain bound even after every native Skill body disappears. */
12
12
  export declare function reviewedCodexPluginCapabilitiesUnchanged(cache: string, controls: CodexPluginSkillControl[], read: Read): boolean;
13
- export declare function reviewCodexPluginSkillControls(catalog: CodexNativeSkillCatalog, documents: string[], cache: string, cwd: string, read: Read): CodexPluginSkillControl[];
13
+ export declare function reviewCodexPluginSkillControls(catalog: CodexNativeSkillCatalog, documents: string[], cache: string, cwd: string, read: Read, rules: unknown): CodexPluginSkillControl[];
14
14
  export declare function isReviewedCodexPluginSkillDisabled(document: string, cache: string, controls: CodexPluginSkillControl[], rules: unknown, read: Read): boolean;
15
15
  export declare function disableReviewedCodexPluginNames(text: string, controls: CodexPluginSkillControl[]): string;
16
16
  export {};