@hasna/skills 0.10.91 → 0.10.93

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.91",
41
+ version: "0.10.93",
42
42
  description: "Skills library for AI coding agents",
43
43
  type: "module",
44
44
  bin: {
@@ -22526,6 +22526,11 @@ var init_agent_opencode = __esm(() => {
22526
22526
  });
22527
22527
 
22528
22528
  // src/lib/agent-adapters.ts
22529
+ function renderAgentHookCommand(command, agent, profileId, event) {
22530
+ if (typeof profileId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) || profileId.includes(".."))
22531
+ throw new Error("Invalid selection profile id");
22532
+ return `'${command.replace(/'/g, `'\\''`)}' hook user-prompt --agent ${agent} --selection-profile ${profileId} --event ${event}`;
22533
+ }
22529
22534
  var INTEGRATION_AGENTS;
22530
22535
  var init_agent_adapters = __esm(() => {
22531
22536
  init_agent_opencode();
@@ -30556,6 +30561,17 @@ function parseHermesConfig(text2) {
30556
30561
  throw new Error(`Expected Hermes ${name} object`);
30557
30562
  return value;
30558
30563
  }
30564
+ function quoted(value) {
30565
+ if (!value || /[\0\r\n]/.test(value))
30566
+ throw new Error("Invalid Hermes Skills command");
30567
+ return `'${value.replace(/'/g, `'\\''`)}'`;
30568
+ }
30569
+ function hermesHookDefinitions(supervisor) {
30570
+ if (!isAbsolute7(supervisor.runtime) || !isAbsolute7(supervisor.path))
30571
+ throw new Error("Expected absolute Hermes supervisor paths");
30572
+ const entry = (event) => ({ command: `${quoted(supervisor.runtime)} ${quoted(supervisor.path)} --event ${event}`, timeout: 15, ...event === "pre_tool_call" ? { fail_closed: true } : {} });
30573
+ return { pre_llm_call: entry("pre_llm_call"), pre_tool_call: entry("pre_tool_call") };
30574
+ }
30559
30575
  function assertHermesEnvironment(home) {
30560
30576
  if (process.env.TERMINAL_CWD && process.env.TERMINAL_CWD !== process.cwd())
30561
30577
  throw new NativeBridgeIntegrityRefusal("NATIVE_SKILL_DRIFT: Hermes TERMINAL_CWD must match the checked process working directory; review the effective project before using this adapter");
@@ -36387,6 +36403,17 @@ var init_runtime_launcher = __esm(() => {
36387
36403
  PINNED_LAUNCHER_MAX_BYTES = 16 * 1024;
36388
36404
  });
36389
36405
 
36406
+ // src/lib/linux-access-acl.ts
36407
+ var LINUX_ACL_FREE_WHEN_UNSUPPORTED;
36408
+ var init_linux_access_acl = __esm(() => {
36409
+ LINUX_ACL_FREE_WHEN_UNSUPPORTED = new Set([
36410
+ 0xef53n,
36411
+ 0x58465342n,
36412
+ 0x9123683en,
36413
+ 0x01021994n
36414
+ ]);
36415
+ });
36416
+
36390
36417
  // src/lib/retained-review-artifacts.ts
36391
36418
  import { createHash as createHash23 } from "crypto";
36392
36419
  import { constants as constants15, closeSync as closeSync17, fstatSync as fstatSync17, fsyncSync as fsyncSync4, lstatSync as lstatSync22, mkdirSync as mkdirSync6, openSync as openSync17, readSync as readSync11, writeSync as writeSync3 } from "fs";
@@ -36508,6 +36535,7 @@ var init_agent_integration = __esm(() => {
36508
36535
  init_claude_settings_witness();
36509
36536
  init_codex_hook_trust_files();
36510
36537
  init_runtime_launcher();
36538
+ init_linux_access_acl();
36511
36539
  init_retained_review_artifacts();
36512
36540
  });
36513
36541
 
@@ -38666,9 +38694,94 @@ function nativeDiscoveryAliasTargets(roots, home) {
38666
38694
  }
38667
38695
  return [...targets];
38668
38696
  }
38697
+ function hooksAreManagedBridge(agent, hooks, managed) {
38698
+ const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
38699
+ const exactKeys3 = (value, keys2) => Object.keys(value).sort().join("\x00") === [...keys2].sort().join("\x00");
38700
+ if (!table(hooks) || typeof managed.command !== "string" || !isAbsolute22(managed.command))
38701
+ return false;
38702
+ const events3 = agent === "gemini" ? ["BeforeAgent", "SessionStart", "BeforeTool"] : ["beforeSubmitPrompt", "sessionStart"];
38703
+ for (const [event, entries] of Object.entries(hooks)) {
38704
+ if (!events3.includes(event) || !Array.isArray(entries) || entries.length !== 1)
38705
+ return false;
38706
+ let expected;
38707
+ try {
38708
+ expected = renderAgentHookCommand(managed.command, agent, managed.profile, event);
38709
+ } catch {
38710
+ return false;
38711
+ }
38712
+ const entry = entries[0];
38713
+ if (!table(entry))
38714
+ return false;
38715
+ if (agent === "cursor") {
38716
+ if (!exactKeys3(entry, ["command", "timeout"]) || entry.timeout !== 15 || entry.command !== expected)
38717
+ return false;
38718
+ continue;
38719
+ }
38720
+ if (!exactKeys3(entry, event === "BeforeTool" ? ["hooks", "matcher"] : ["hooks"]) || event === "BeforeTool" && entry.matcher !== "activate_skill" || !Array.isArray(entry.hooks) || entry.hooks.length !== 1)
38721
+ return false;
38722
+ const hook = entry.hooks[0];
38723
+ if (!table(hook) || !exactKeys3(hook, ["command", "timeout", "type"]) || hook.type !== "command" || hook.timeout !== 15000 || hook.command !== expected)
38724
+ return false;
38725
+ }
38726
+ return true;
38727
+ }
38728
+ function sumiPluginLoadsOnlyBuiltins(text2) {
38729
+ for (const match of text2.matchAll(/\b(?:import|require)\s*\(\s*(.?)/g)) {
38730
+ const rest = text2.slice(match.index + match[0].length - match[1].length);
38731
+ if (!/^(['"])node:[A-Za-z0-9_/.-]+\1/.test(rest))
38732
+ return false;
38733
+ }
38734
+ for (const match of text2.matchAll(/\b(?:from|import)\s*(['"])([^'"]*)\1/g))
38735
+ if (!match[2].startsWith("node:"))
38736
+ return false;
38737
+ return true;
38738
+ }
38739
+ function codexRootFreeControls(config2) {
38740
+ const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date));
38741
+ const adapted = [];
38742
+ const servers = config2.mcp_servers;
38743
+ if (table(servers) && Object.keys(servers).length <= AGENT_POLICY_LIMITS.discoveryRoots && Object.entries(servers).every(([name, row]) => name !== "codex_apps" && name !== "_codex_apps" && isCodexLocalStdioMcpServer(name, row)))
38744
+ adapted.push("mcp_servers");
38745
+ const agents = config2.agents;
38746
+ if (table(agents) && Object.keys(agents).length <= AGENT_POLICY_LIMITS.fields && Object.values(agents).every((value) => typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)))
38747
+ adapted.push("agents");
38748
+ return adapted;
38749
+ }
38750
+ function codexPluginHookHandlers(value) {
38751
+ const table = (item) => Boolean(item && typeof item === "object" && !Array.isArray(item));
38752
+ const identifier5 = (item) => typeof item === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(item);
38753
+ const text2 = (item) => typeof item === "string" && item.length <= AGENT_POLICY_LIMITS.pathCharacters && !item.includes("\x00");
38754
+ if (!table(value) || !Object.keys(value).every((key2) => key2 === "hooks" || key2 === "description") || value.description !== undefined && !text2(value.description) || !table(value.hooks) || Object.keys(value.hooks).length > 32)
38755
+ return null;
38756
+ const result = { commands: 0, servers: new Set };
38757
+ for (const [event, groups] of Object.entries(value.hooks)) {
38758
+ if (!CODEX_HOOK_EVENTS.has(event) || !Array.isArray(groups) || groups.length > 32)
38759
+ return null;
38760
+ for (const group of groups) {
38761
+ if (!table(group) || !Object.keys(group).every((key2) => key2 === "hooks" || key2 === "matcher") || group.matcher !== undefined && !text2(group.matcher) || !Array.isArray(group.hooks) || group.hooks.length > 32)
38762
+ return null;
38763
+ for (const handler of group.hooks) {
38764
+ if (!table(handler) || handler.timeout !== undefined && !(Number.isSafeInteger(handler.timeout) && handler.timeout >= 0) || handler.statusMessage !== undefined && !text2(handler.statusMessage))
38765
+ return null;
38766
+ if (handler.type === "command") {
38767
+ if (!text2(handler.command) || !handler.command || !Object.keys(handler).every((key2) => ["type", "command", "commandWindows", "command_windows", "timeout", "async", "statusMessage", "additionalContextLimit"].includes(key2)))
38768
+ return null;
38769
+ result.commands++;
38770
+ } else if (handler.type === "mcp_tool") {
38771
+ if (!identifier5(handler.server) || !identifier5(handler.tool) || handler.input !== undefined && !table(handler.input) || !Object.keys(handler).every((key2) => ["type", "server", "tool", "input", "timeout", "statusMessage"].includes(key2)))
38772
+ return null;
38773
+ result.servers.add(handler.server);
38774
+ } else
38775
+ return null;
38776
+ }
38777
+ }
38778
+ }
38779
+ return result;
38780
+ }
38669
38781
  function resolveDiscovery(options, exclusion) {
38670
38782
  const canonical9 = options.canonical ?? resolve23, home = resolve23(options.home), agent = options.agent;
38671
38783
  const sources = [], roots = new Set, builtinNames = [];
38784
+ const sumiLocalPluginTexts = new Map;
38672
38785
  const budget3 = discoveryByteBudget();
38673
38786
  let remainingEntries = AGENT_POLICY_LIMITS.discoveryDirectoryEntries;
38674
38787
  const entries = (path) => {
@@ -38705,8 +38818,8 @@ function resolveDiscovery(options, exclusion) {
38705
38818
  const configPath = exclusion?.nativeHome === undefined ? agentDiscoveryConfigPath(home, agent) : join33(nativeHome, agent === "codex" ? "config.toml" : "settings.json");
38706
38819
  const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? [...SUMI_DISCOVERY_PROJECTION_FIELDS] : ["version"]);
38707
38820
  const config2 = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
38708
- const unresolved = (detail) => {
38709
- throw new Error(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
38821
+ const unresolved = (detail, source) => {
38822
+ throw new NativeDiscoveryUnresolved(agent, detail, source);
38710
38823
  };
38711
38824
  let opaqueClosureVerified = false;
38712
38825
  const verifyOpaqueClosure = (declarations) => {
@@ -38720,6 +38833,17 @@ function resolveDiscovery(options, exclusion) {
38720
38833
  verifyDiscovery(retained, undefined, undefined, undefined, exclusion?.narrowed, canonical9(configPath));
38721
38834
  opaqueClosureVerified = true;
38722
38835
  };
38836
+ const reviewRemedy = (source, check2) => {
38837
+ try {
38838
+ check2();
38839
+ } catch (error61) {
38840
+ if (error61 instanceof NativeDiscoveryUnresolved) {
38841
+ error61.reviewable = true;
38842
+ error61.source ??= source;
38843
+ }
38844
+ throw error61;
38845
+ }
38846
+ };
38723
38847
  if (exclusion) {
38724
38848
  if (configText !== null && agent !== "codex" && agent !== "hermes")
38725
38849
  parseNativeJsonObject(configText);
@@ -38745,12 +38869,38 @@ function resolveDiscovery(options, exclusion) {
38745
38869
  const controls = agent === "claude" ? claudeDiscoveryControlKeys(configText ?? "{}") : agent === "codex" ? codexDiscoveryControlKeys(config2) : Object.keys(config2).filter((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2));
38746
38870
  const fieldKey = (key2) => /^[A-Za-z_$][A-Za-z0-9_$.-]{0,22}$/.test(key2) && !/(?:sk|rk|pk)_(?:live|test)_|whsec_/i.test(key2) ? JSON.stringify(key2) : "[withheld field key]";
38747
38871
  const unsupportedFields = (names, label) => unresolved(`unknown discovery controls have no root-exclusion adapter for ${label}${names.slice(0, 8).map(fieldKey).join(", ")}${names.length > 8 ? ", additional keys withheld" : ""} in ${quotedPath(canonical9(configPath))}`);
38748
- const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2));
38872
+ const adapted = agent === "codex" ? codexRootFreeControls(config2) : agent === "opencode" && Array.isArray(config2.instructions) && config2.instructions.length === 0 ? ["instructions"] : [];
38873
+ const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2) && !adapted.includes(key2));
38749
38874
  if (unsupportedControls.length)
38750
38875
  unsupportedFields(unsupportedControls, "");
38751
38876
  if (config2.hooks !== undefined && !object6(config2.hooks))
38752
38877
  unresolved("hooks must be an object");
38753
- const hasHooks = controls.includes("hooks") && config2.hooks !== undefined && Object.keys(config2.hooks).length > 0;
38878
+ const managedSupervisorOnly = () => {
38879
+ const supervisor = exclusion.managedHook?.supervisor;
38880
+ if (agent !== "hermes" || !supervisor || typeof supervisor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(supervisor.sha256) || !object6(config2.hooks))
38881
+ return false;
38882
+ let expected;
38883
+ try {
38884
+ expected = hermesHookDefinitions(supervisor);
38885
+ } catch {
38886
+ return false;
38887
+ }
38888
+ for (const [event, entries2] of Object.entries(config2.hooks)) {
38889
+ const want = expected[event];
38890
+ if (!want || !Array.isArray(entries2) || entries2.length !== 1 || !object6(entries2[0]))
38891
+ return false;
38892
+ const entry = entries2[0];
38893
+ if (Object.keys(entry).sort().join("\x00") !== Object.keys(want).sort().join("\x00") || Object.keys(want).some((key2) => entry[key2] !== want[key2]))
38894
+ return false;
38895
+ }
38896
+ try {
38897
+ return hashRawDiscoveryFile(canonical9(supervisor.path), budget3) === supervisor.sha256;
38898
+ } catch {
38899
+ return false;
38900
+ }
38901
+ };
38902
+ const managedBridgeOnly = (agent === "gemini" || agent === "cursor") && exclusion.managedHook !== undefined && hooksAreManagedBridge(agent, config2.hooks, exclusion.managedHook) || managedSupervisorOnly();
38903
+ const hasHooks = controls.includes("hooks") && config2.hooks !== undefined && Object.keys(config2.hooks).length > 0 && !managedBridgeOnly;
38754
38904
  let hasPluginConfigs = false, hasFunctionHookEnvironment = false;
38755
38905
  const verifyCurrentOpaqueSettings = (label) => {
38756
38906
  const retained = exclusion.retained;
@@ -38765,7 +38915,7 @@ function resolveDiscovery(options, exclusion) {
38765
38915
  unresolved(`non-enumerable ${label} require a current settings review`);
38766
38916
  };
38767
38917
  if (hasHooks)
38768
- verifyCurrentOpaqueSettings("hooks");
38918
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("hooks"));
38769
38919
  if (agent === "claude") {
38770
38920
  if (config2.env !== undefined) {
38771
38921
  if (!object6(config2.env) || Object.keys(config2.env).length > 512)
@@ -38778,7 +38928,7 @@ function resolveDiscovery(options, exclusion) {
38778
38928
  if (hasFunctionHookEnvironment) {
38779
38929
  if (!["0", "1"].includes(config2.env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS))
38780
38930
  unresolved("malformed Claude env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS flag");
38781
- verifyCurrentOpaqueSettings("function-hook environment controls");
38931
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("function-hook environment controls"));
38782
38932
  }
38783
38933
  }
38784
38934
  if (config2.pluginConfigs !== undefined) {
@@ -38805,7 +38955,7 @@ function resolveDiscovery(options, exclusion) {
38805
38955
  }
38806
38956
  hasPluginConfigs = Object.keys(config2.pluginConfigs).length > 0;
38807
38957
  if (hasPluginConfigs)
38808
- verifyCurrentOpaqueSettings("plugin configurations");
38958
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("plugin configurations"));
38809
38959
  }
38810
38960
  if (config2.enabledPlugins !== undefined && !object6(config2.enabledPlugins))
38811
38961
  unresolved("enabledPlugins must be an object");
@@ -38855,7 +39005,9 @@ function resolveDiscovery(options, exclusion) {
38855
39005
  unresolved("malformed Sumi plugins");
38856
39006
  }
38857
39007
  if (agent === "hermes" && config2.skills !== undefined) {
38858
- keys2(config2.skills, ["external_dirs"], "Hermes skills");
39008
+ keys2(config2.skills, ["external_dirs", "creation_nudge_interval"], "Hermes skills");
39009
+ if (config2.skills.creation_nudge_interval !== undefined && !(Number.isSafeInteger(config2.skills.creation_nudge_interval) && config2.skills.creation_nudge_interval >= 0))
39010
+ unresolved("malformed Hermes skills creation_nudge_interval");
38859
39011
  if (config2.skills.external_dirs !== undefined)
38860
39012
  paths(typeof config2.skills.external_dirs === "string" ? [config2.skills.external_dirs] : config2.skills.external_dirs, "Hermes external directories");
38861
39013
  }
@@ -38875,10 +39027,21 @@ function resolveDiscovery(options, exclusion) {
38875
39027
  unresolved(`malformed Codex ${key2}`);
38876
39028
  for (const value of Object.values(config2.plugins ?? {}))
38877
39029
  keys2(value, ["enabled"], "Codex plugin registration");
38878
- for (const value of Object.values(config2.marketplaces ?? {}))
38879
- keys2(value, ["source_type", "source"], "Codex marketplace registration");
39030
+ for (const value of Object.values(config2.marketplaces ?? {})) {
39031
+ keys2(value, ["source_type", "source", "last_updated"], "Codex marketplace registration");
39032
+ if (value.last_updated !== undefined && (typeof value.last_updated !== "string" || !CODEX_MARKETPLACE_TIMESTAMP.test(value.last_updated)))
39033
+ unresolved("malformed Codex marketplace refresh time");
39034
+ }
38880
39035
  if (config2.skills !== undefined)
38881
- keys2(config2.skills, ["config", "bundled"], "Codex skills");
39036
+ keys2(config2.skills, ["config", "bundled", "native_allowed_paths"], "Codex skills");
39037
+ if (config2.skills?.native_allowed_paths !== undefined) {
39038
+ for (const value of paths(config2.skills.native_allowed_paths, "Codex native allowed skill paths")) {
39039
+ if (!isAbsolute22(value))
39040
+ unresolved("relative Codex native allowed skill path");
39041
+ const path = canonical9(value);
39042
+ roots.add(path.endsWith(`${sep11}SKILL.md`) ? dirname24(path) : path);
39043
+ }
39044
+ }
38882
39045
  if (config2.skills?.bundled !== undefined) {
38883
39046
  keys2(config2.skills.bundled, ["enabled"], "Codex bundled skills");
38884
39047
  if (typeof config2.skills.bundled.enabled !== "boolean")
@@ -38904,7 +39067,7 @@ function resolveDiscovery(options, exclusion) {
38904
39067
  if (Object.keys(config2.enabledPlugins ?? config2.plugins ?? config2.extensions ?? {}).length > 512)
38905
39068
  unresolved("unbounded native registrations");
38906
39069
  if (hasHooks || hasPluginConfigs || hasFunctionHookEnvironment)
38907
- verifyOpaqueClosure([]);
39070
+ reviewRemedy(canonical9(configPath), () => verifyOpaqueClosure([]));
38908
39071
  }
38909
39072
  if (options.reviewed && (options.reviewed.version !== 1 || !Array.isArray(options.reviewed.agents) || options.reviewed.agents.some((item) => !item || typeof item !== "object") || new Set(options.reviewed.agents.map((item) => item.agent)).size !== options.reviewed.agents.length))
38910
39073
  throw new Error("Invalid --discovery-inputs version or agents");
@@ -38936,7 +39099,9 @@ function resolveDiscovery(options, exclusion) {
38936
39099
  const target = join33(path, name);
38937
39100
  if (!lstatSync25(target).isFile())
38938
39101
  unresolved("local plugin packages need a dedicated discovery adapter");
38939
- witness(target);
39102
+ const text2 = witness(target);
39103
+ if (exclusion && text2 !== null)
39104
+ sumiLocalPluginTexts.set(canonical9(target), text2);
38940
39105
  }
38941
39106
  }
38942
39107
  for (const root of roots) {
@@ -39070,38 +39235,126 @@ function resolveDiscovery(options, exclusion) {
39070
39235
  const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual6(candidate, source)) === index);
39071
39236
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
39072
39237
  }
39073
- function plugin(root) {
39238
+ const reviewedManifestAbsence = (root, manifestPath) => {
39239
+ const retained = exclusion?.retained;
39240
+ if (agent !== "claude" || retained?.method !== "reviewed" || ![join33(root, "skills"), root].some((path) => retained.roots.includes(path)))
39241
+ return false;
39242
+ const saved = retained.sources.filter((source) => source.path === canonical9(manifestPath) && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined);
39243
+ return saved.length > 0 && saved.every((source) => projected(source, undefined, budget3) === source.sha256);
39244
+ };
39245
+ const reviewedInstalledCopy = (root, path, identity4) => {
39246
+ const retained = exclusion?.retained;
39247
+ if (agent !== "codex" || retained?.method !== "reviewed")
39248
+ return false;
39249
+ const inside = relative7(root, canonical9(path));
39250
+ if (!inside || inside === ".." || inside.startsWith(`..${sep11}`) || isAbsolute22(inside))
39251
+ return false;
39252
+ const parent = canonical9(join33(nativeHome, "plugins/cache", identity4.marketplace, identity4.name));
39253
+ const current = hashRawDiscoveryFile(canonical9(path), budget3);
39254
+ if (current === null)
39255
+ return false;
39256
+ return retained.sources.some((source) => {
39257
+ if (source.sha256 !== current || source.format !== undefined || source.fields !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || !source.path.startsWith(parent + sep11))
39258
+ return false;
39259
+ const [version2, ...rest] = source.path.slice(parent.length + 1).split(sep11);
39260
+ return Boolean(version2) && version2 !== "." && version2 !== ".." && rest.join(sep11) === inside;
39261
+ });
39262
+ };
39263
+ const verifyCodexPluginHookClosure = (root, identity4, marketplaceSource, servers, declarations) => {
39264
+ const retained = exclusion.retained;
39265
+ if (retained.codexSemanticCache || retained.codexInstallationInputs || retained.codexRetiredMaterializations)
39266
+ return verifyOpaqueClosure(declarations);
39267
+ const within = (base, path) => path === base || path.startsWith(base + sep11);
39268
+ const executables = [];
39269
+ for (const server of servers) {
39270
+ const row = config2.mcp_servers?.[server];
39271
+ if (server === "codex_apps" || server === "_codex_apps" || !isCodexLocalStdioMcpServer(server, row))
39272
+ unresolved("plugin hook MCP server is not a configured local stdio server");
39273
+ for (const value of [row.command, ...row.args ?? []])
39274
+ for (const candidate of [value, value.slice(value.indexOf("=") + 1)]) {
39275
+ if (!isAbsolute22(candidate) || /[\0$]/.test(candidate) || executables.length >= AGENT_POLICY_LIMITS.discoveryRoots)
39276
+ continue;
39277
+ const path = canonical9(candidate);
39278
+ if (lstatSync25(path, { throwIfNoEntry: false })?.isFile())
39279
+ executables.push(path);
39280
+ }
39281
+ }
39282
+ const cacheRoot = canonical9(join33(nativeHome, "plugins/cache"));
39283
+ const ownCache = canonical9(join33(nativeHome, "plugins/cache", identity4.marketplace, identity4.name));
39284
+ const ownCatalog = canonical9(join33(marketplaceSource, ".agents/plugins/marketplace.json"));
39285
+ const account = canonical9(home);
39286
+ const marketplaceRoots = Object.values(config2.marketplaces ?? {}).filter((market) => market?.source_type === "local" && typeof market.source === "string" && isAbsolute22(market.source)).map((market) => canonical9(market.source)).filter((path) => !within(path, account));
39287
+ const otherPlugin = (path) => !within(root, path) && !within(ownCache, path) && path !== ownCatalog && !executables.includes(path) && (within(cacheRoot, path) || marketplaceRoots.some((base) => within(base, path)));
39288
+ const sources2 = retained.sources.filter((source) => source.path !== canonical9(configPath) && !otherPlugin(source.path));
39289
+ if (sources2.some((source) => source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || ![undefined, "bytes", "path-bytes", "executable-v1"].includes(source.hashMode)))
39290
+ return verifyOpaqueClosure(declarations);
39291
+ const declared = declarations.map((path) => relative7(root, path));
39292
+ const declaration = (path) => declarations.includes(path) || path.startsWith(ownCache + sep11) && declared.includes(path.slice(ownCache.length + 1).split(sep11).slice(1).join(sep11));
39293
+ if (!sources2.some((source) => !declaration(source.path) && source.sha256 !== null))
39294
+ unresolved("opaque executable dependency closure is unavailable");
39295
+ for (const source of sources2)
39296
+ if (projected(source, undefined, budget3) !== source.sha256) {
39297
+ throw new NativeDiscoveryWitnessChange("input", source.path, `Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
39298
+ }
39299
+ verifyDiscoveryDirectories((retained.directories ?? []).filter((directory) => !otherPlugin(directory.path)));
39300
+ };
39301
+ const readCodexHookDeclaration = (text2, path) => {
39302
+ if (text2 === null)
39303
+ return null;
39304
+ try {
39305
+ return codexPluginHookHandlers(parseConfig(text2, path));
39306
+ } catch {
39307
+ return null;
39308
+ }
39309
+ };
39310
+ function plugin(root, codex) {
39074
39311
  root = canonical9(root);
39075
39312
  safe6(root);
39076
39313
  if (exclusion)
39077
39314
  roots.add(root);
39078
39315
  const manifestPath = join33(root, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
39079
39316
  const raw = witness(manifestPath, undefined, undefined, agent === "claude" ? "claude-plugin-manifest-v1" : undefined);
39080
- if (raw === null)
39317
+ if (raw === null && !reviewedManifestAbsence(root, manifestPath))
39081
39318
  unresolved("plugin manifest missing");
39082
- const manifest = parseConfig(raw, manifestPath);
39319
+ const manifest = raw === null ? {} : parseConfig(raw, manifestPath);
39083
39320
  if (exclusion && Object.keys(manifest).some((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2) && !["skills", "hooks"].includes(key2)))
39084
39321
  unresolved("unknown plugin discovery controls");
39085
39322
  const hooks = witness(join33(root, "hooks/hooks.json"));
39086
- if (manifest.hooks !== undefined || hooks !== null) {
39087
- if (!exclusion || exclusion.retained?.method !== "reviewed")
39088
- unresolved("plugin hooks require a separate no-skill-injection review");
39089
- const required2 = [manifestPath, ...hooks !== null ? [join33(root, "hooks/hooks.json")] : []];
39090
- if (manifest.hooks !== undefined) {
39091
- if (typeof manifest.hooks !== "string")
39092
- unresolved("unsupported plugin hooks target");
39093
- const hook = resolve23(root, manifest.hooks);
39094
- if (!hook.startsWith(root + sep11))
39095
- unresolved("plugin hooks target escapes its root");
39096
- required2.push(hook);
39097
- }
39098
- for (const path of required2) {
39099
- const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
39100
- if (!saved.length || saved.some((source) => projected(source, undefined, budget3) !== source.sha256))
39101
- unresolved("plugin hooks require current independently reviewed sources");
39102
- }
39103
- verifyOpaqueClosure(required2.map((path) => canonical9(path)));
39104
- }
39323
+ if (manifest.hooks !== undefined || hooks !== null)
39324
+ reviewRemedy(canonical9(hooks !== null ? join33(root, "hooks/hooks.json") : manifestPath), () => {
39325
+ if (!exclusion || exclusion.retained?.method !== "reviewed")
39326
+ unresolved("plugin hooks require a separate no-skill-injection review");
39327
+ const required2 = [manifestPath, ...hooks !== null ? [join33(root, "hooks/hooks.json")] : []];
39328
+ const scoped = agent === "codex" && codex !== undefined ? [] : undefined;
39329
+ if (manifest.hooks !== undefined) {
39330
+ if (scoped && manifest.hooks && typeof manifest.hooks === "object" && !Array.isArray(manifest.hooks)) {
39331
+ const inline = codexPluginHookHandlers(manifest.hooks);
39332
+ if (!inline)
39333
+ unresolved("unsupported inline plugin hooks");
39334
+ scoped.push(inline);
39335
+ } else {
39336
+ if (typeof manifest.hooks !== "string")
39337
+ unresolved("unsupported plugin hooks target");
39338
+ const hook = resolve23(root, manifest.hooks);
39339
+ if (!hook.startsWith(root + sep11))
39340
+ unresolved("plugin hooks target escapes its root");
39341
+ required2.push(hook);
39342
+ if (scoped)
39343
+ scoped.push(readCodexHookDeclaration(witness(hook), hook));
39344
+ }
39345
+ }
39346
+ if (scoped && hooks !== null)
39347
+ scoped.push(readCodexHookDeclaration(hooks, join33(root, "hooks/hooks.json")));
39348
+ for (const path of required2) {
39349
+ const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
39350
+ if (saved.length ? saved.some((source) => projected(source, undefined, budget3) !== source.sha256) : !(scoped && reviewedInstalledCopy(root, path, codex)))
39351
+ unresolved("plugin hooks require current independently reviewed sources", canonical9(path));
39352
+ }
39353
+ if (scoped && scoped.every((item) => item !== null && item.commands === 0)) {
39354
+ verifyCodexPluginHookClosure(root, codex, codex.source, new Set(scoped.flatMap((item) => [...item.servers])), required2.map((path) => canonical9(path)));
39355
+ } else
39356
+ verifyOpaqueClosure(required2.map((path) => canonical9(path)));
39357
+ });
39105
39358
  const commands = join33(root, "commands");
39106
39359
  safe6(commands);
39107
39360
  if (agent === "claude" && (manifest.commands !== undefined || lstatSync25(commands, { throwIfNoEntry: false })?.isDirectory() && entries(commands).length))
@@ -39156,7 +39409,7 @@ function resolveDiscovery(options, exclusion) {
39156
39409
  const catalog = parseConfig(catalogText, join33(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
39157
39410
  if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
39158
39411
  unresolved("plugin catalog source is missing or ambiguous");
39159
- plugin(resolve23(market.source, matches[0].source.path));
39412
+ plugin(resolve23(market.source, matches[0].source.path), exclusion ? { marketplace, name, source: market.source } : undefined);
39160
39413
  }
39161
39414
  } else if (agent === "opencode") {
39162
39415
  if (witness(join33(home, ".config/opencode/opencode.jsonc")) !== null || witness(join33(home, ".config/opencode/config.json")) !== null)
@@ -39173,7 +39426,11 @@ function resolveDiscovery(options, exclusion) {
39173
39426
  if (lstatSync25(localPlugins, { throwIfNoEntry: false }) && entries(localPlugins).some((name) => name !== "skills-cli.js"))
39174
39427
  unresolved("additional local plugin hooks require review");
39175
39428
  } else if (agent === "sumi") {
39176
- if ((config2.plugin?.length ?? 0) > 0 || (config2.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join33(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null))
39429
+ const pinnedLocalPlugin = (source) => {
39430
+ const retained = exclusion?.retained, text2 = sumiLocalPluginTexts.get(source.path);
39431
+ return retained?.method === "reviewed" && text2 !== undefined && sumiPluginLoadsOnlyBuiltins(text2) && retained.sources.some((saved) => saved.path === source.path && saved.sha256 === source.sha256 && saved.sha256 !== null && [undefined, "bytes"].includes(saved.hashMode) && saved.format === undefined && saved.fields === undefined && saved.managedPlugins === undefined && saved.reviewArtifact === undefined);
39432
+ };
39433
+ if ((config2.plugin?.length ?? 0) > 0 || (config2.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join33(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null && !pinnedLocalPlugin(source)))
39177
39434
  unresolved("external or foreign local plugins require review");
39178
39435
  } else if (agent === "hermes") {
39179
39436
  for (const directory of [join33(home, ".hermes/plugins"), join33(home, ".hermes/hermes-agent")]) {
@@ -39207,17 +39464,18 @@ function resolveDiscovery(options, exclusion) {
39207
39464
  unresolved("native discovery roots exceed their bound");
39208
39465
  return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join33(home, ".hermes/plugins"), join33(home, ".hermes/hermes-agent")].map((path) => canonical9(path))) } : {} };
39209
39466
  }
39210
- var DISCOVERY_RUNTIME_COMMANDS, quotedPath = (path) => JSON.stringify(path).replace(/[^\x20-\x7e]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`), realpathOrNull = (path) => {
39467
+ var DISCOVERY_RUNTIME_COMMANDS, NativeDiscoveryUnresolved, quotedPath = (path) => JSON.stringify(path).replace(/[^\x20-\x7e]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`), realpathOrNull = (path) => {
39211
39468
  try {
39212
39469
  return realpathSync8(path);
39213
39470
  } catch {
39214
39471
  return null;
39215
39472
  }
39216
- }, digest2 = (text2) => createHash26("sha256").update(text2).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS;
39473
+ }, digest2 = (text2) => createHash26("sha256").update(text2).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS, CODEX_MARKETPLACE_TIMESTAMP, CODEX_HOOK_EVENTS;
39217
39474
  var init_agent_discovery = __esm(() => {
39218
39475
  init_sumi_settings_witness();
39219
39476
  init_agent_policy_limits();
39220
39477
  init_agent_hermes();
39478
+ init_agent_adapters();
39221
39479
  init_agent_discovery_directories();
39222
39480
  init_agent_discovery_bytes();
39223
39481
  init_plugin_projection_store();
@@ -39243,10 +39501,24 @@ var init_agent_discovery = __esm(() => {
39243
39501
  init_codex_semantic_cache_witness();
39244
39502
  init_agent_discovery_directories();
39245
39503
  DISCOVERY_RUNTIME_COMMANDS = Object.freeze({ gemini: "gemini" });
39504
+ NativeDiscoveryUnresolved = class NativeDiscoveryUnresolved extends Error {
39505
+ agent;
39506
+ detail;
39507
+ source;
39508
+ reviewable = false;
39509
+ constructor(agent, detail, source) {
39510
+ super(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
39511
+ this.agent = agent;
39512
+ this.detail = detail;
39513
+ this.source = source;
39514
+ }
39515
+ };
39246
39516
  CLAUDE_CONTROL_WITNESS_MODES = new Set(["claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-settings-v5", "claude-marketplace-registry", "claude-marketplace-registry-v2", "claude-marketplace-entry-v1", "claude-plugin-registry"]);
39247
39517
  CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
39248
39518
  ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
39249
39519
  CLAUDE_PROJECT_DISCOVERY_KEYS = Object.freeze(["enabledPlugins", "extraKnownMarketplaces", "additionalMarketplaces", "skillOverrides"]);
39520
+ CODEX_MARKETPLACE_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
39521
+ CODEX_HOOK_EVENTS = new Set(["PreToolUse", "PermissionRequest", "PostToolUse", "PreCompact", "PostCompact", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "Stop", "Interrupt"]);
39250
39522
  });
39251
39523
 
39252
39524
  // src/lib/authoring-path.ts
@@ -39279,6 +39551,11 @@ function physicalPath(path) {
39279
39551
  }
39280
39552
  return resolve24(realpathSync9(cursor), ...suffix);
39281
39553
  }
39554
+ function portableAuthoringDiscoveryRefusal(error61, destination) {
39555
+ const head = `Native discovery is unresolved (${error61.agent}: ${error61.detail}).${error61.source ? ` Checked source: ${error61.source}.` : ""}`;
39556
+ const review = `skills hook install --agent ${error61.agent} --discovery-inputs <reviewed-file> --apply`;
39557
+ return error61.reviewable ? `${head} Portable Skills authoring stays refused: an unreviewed ${error61.agent} hook or plugin option could load skills written to ${destination}. This command takes no --discovery-inputs option; review the current ${error61.agent} configuration with ${review}, then retry.` : `${head} Portable Skills authoring stays refused: Skills cannot list every skill root this ${error61.agent} configuration registers, so it cannot rule out ${destination}. This command takes no --discovery-inputs option; correct that ${error61.agent} configuration entry, then retry. Where a reviewed discovery binding covers it, record one with ${review}.`;
39558
+ }
39282
39559
  function contains(root, path) {
39283
39560
  const boundary = root.endsWith(sep12) ? root : root + sep12;
39284
39561
  return path === root || path.startsWith(boundary);
@@ -39324,7 +39601,10 @@ function configuredRoots(home, explicitHome) {
39324
39601
  if (recorded && (!executable || executable.target !== recorded.target || onPath && onPath.target !== recorded.target) || !executable && (binding.builtinNames?.length || binding.agent === "gemini" && binding.sources.some((source) => basename15(source.path) === "package.json"))) {
39325
39602
  throw new Error("Native discovery runtime target is unresolved or changed");
39326
39603
  }
39327
- roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable, narrowed }));
39604
+ const managedCommand = current.commands?.[agent], managedProfile = current.profiles?.[agent];
39605
+ const supervisor = current.supervisors?.[agent];
39606
+ const managedHook = (binding.agent === "gemini" || binding.agent === "cursor") && typeof managedCommand === "string" && typeof managedProfile === "string" ? { command: managedCommand, profile: managedProfile } : binding.agent === "hermes" && typeof managedCommand === "string" && typeof managedProfile === "string" && supervisor && typeof supervisor === "object" && typeof supervisor.path === "string" && typeof supervisor.runtime === "string" && typeof supervisor.sha256 === "string" ? { command: managedCommand, profile: managedProfile, supervisor: { path: supervisor.path, runtime: supervisor.runtime, sha256: supervisor.sha256 } } : undefined;
39607
+ roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable, narrowed, ...managedHook ? { managedHook } : {} }));
39328
39608
  }
39329
39609
  };
39330
39610
  if (homePolicy)
@@ -39376,11 +39656,18 @@ function assertPortableAuthoringPath(path, options = {}) {
39376
39656
  if (native) {
39377
39657
  throw new Error(`Refusing portable Skills authoring below native discovery root '${native}': ${lexical}. Set HASNA_SKILLS_DIR to a private authoring corpus outside native roots.`);
39378
39658
  }
39379
- const roots = configuredRoots(home, options.homeDir !== undefined);
39380
- for (const value of selected) {
39381
- if (physicalPath(value.path) === physicalPath(join34(home, `.${value.agent}`)))
39382
- continue;
39383
- roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: value.agent, nativeHome: value.path, canonical: physicalPath, executable: null }));
39659
+ const roots = [];
39660
+ try {
39661
+ roots.push(...configuredRoots(home, options.homeDir !== undefined));
39662
+ for (const value of selected) {
39663
+ if (physicalPath(value.path) === physicalPath(join34(home, `.${value.agent}`)))
39664
+ continue;
39665
+ roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: value.agent, nativeHome: value.path, canonical: physicalPath, executable: null }));
39666
+ }
39667
+ } catch (error61) {
39668
+ if (error61 instanceof NativeDiscoveryUnresolved)
39669
+ throw new Error(portableAuthoringDiscoveryRefusal(error61, lexical));
39670
+ throw error61;
39384
39671
  }
39385
39672
  for (const root of roots) {
39386
39673
  if (candidates.some((candidate) => [resolve24(root), physicalPath(root)].some((nativeRoot) => contains(nativeRoot, candidate) || contains(candidate, nativeRoot)))) {
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.91",
28
+ version: "0.10.93",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {
package/bin/server.js CHANGED
@@ -83767,7 +83767,7 @@ restoreLaunchCwd();
83767
83767
  // package.json
83768
83768
  var package_default = {
83769
83769
  name: "@hasna/skills",
83770
- version: "0.10.91",
83770
+ version: "0.10.93",
83771
83771
  description: "Skills library for AI coding agents",
83772
83772
  type: "module",
83773
83773
  bin: {
package/bin/worker.js CHANGED
@@ -76186,7 +76186,7 @@ import { randomUUID as randomUUID6 } from "crypto";
76186
76186
  // package.json
76187
76187
  var package_default = {
76188
76188
  name: "@hasna/skills",
76189
- version: "0.10.91",
76189
+ version: "0.10.93",
76190
76190
  description: "Skills library for AI coding agents",
76191
76191
  type: "module",
76192
76192
  bin: {
@@ -1,2 +1,3 @@
1
1
  import type { Command } from "commander";
2
+ export declare const AUTHORING_NATIVE_HOOKS_HELP = "\nNative hooks:\n Authoring refuses while another agent's native hooks, plugin hooks or plugin\n options are not covered by a current discovery review, because they could\n load skills from the authoring corpus. The refusal names the agent and the\n file it checked; review that agent's configuration with\n skills hook install --agent <agent> --discovery-inputs <reviewed-file> --apply\n and retry. Authoring commands themselves take no --discovery-inputs option.\n";
2
3
  export declare function registerPortableSkillCommands(parent: Command): void;