@hasna/skills 0.9.14 → 0.9.16

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.9.14",
20022
+ version: "0.9.16",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -48796,13 +48796,24 @@ var init_codex_hook_trust_files = () => {};
48796
48796
  // src/lib/codex-hook-trust-layout.ts
48797
48797
  var init_codex_hook_trust_layout = () => {};
48798
48798
 
48799
+ // src/lib/codex-hook-additions-review.ts
48800
+ var events, matchedEvents, proofs;
48801
+ var init_codex_hook_additions_review = __esm(() => {
48802
+ init_codex_hook_trust_files();
48803
+ init_codex_hook_trust_layout();
48804
+ events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
48805
+ matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
48806
+ proofs = new WeakMap;
48807
+ });
48808
+
48799
48809
  // src/lib/codex-hook-discovery-recovery.ts
48800
48810
  import { createHash as createHash13 } from "crypto";
48801
48811
  function assertCodexHookDiscoveryRecovery(binding, witness) {
48802
- const proof = proofs.get(witness);
48812
+ const proof = proofs2.get(witness);
48803
48813
  need4(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
48804
48814
  unchanged2(proof.before);
48805
48815
  unchanged2(proof.current);
48816
+ proof.recheckAdditions();
48806
48817
  }
48807
48818
  function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witness) {
48808
48819
  if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
@@ -48811,17 +48822,19 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
48811
48822
  need4(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
48812
48823
  return true;
48813
48824
  }
48814
- var sha = (text2) => createHash13("sha256").update(text2).digest("hex"), proofs;
48825
+ var sha = (text2) => createHash13("sha256").update(text2).digest("hex"), proofs2;
48815
48826
  var init_codex_hook_discovery_recovery = __esm(() => {
48816
48827
  init_codex_hook_trust_files();
48817
48828
  init_codex_hook_trust_layout();
48818
- proofs = new WeakMap;
48829
+ init_codex_hook_additions_review();
48830
+ proofs2 = new WeakMap;
48819
48831
  });
48820
48832
 
48821
48833
  // src/lib/agent-discovery.ts
48822
48834
  import { createHash as createHash14 } from "crypto";
48823
48835
  import { lstatSync as lstatSync16, readFileSync as readFileSync14, readdirSync as readdirSync6, realpathSync as realpathSync3 } from "fs";
48824
48836
  import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute13, join as join23, resolve as resolve13, sep as sep4 } from "path";
48837
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
48825
48838
  function parseConfig(text2, path, toml = false) {
48826
48839
  try {
48827
48840
  const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
@@ -48996,6 +49009,19 @@ function resolveAgentDiscovery(options) {
48996
49009
  }
48997
49010
  }
48998
49011
  }
49012
+ if (options.reviewed === undefined && options.retainedReview !== undefined) {
49013
+ const retained = options.retainedReview;
49014
+ if (retained.agent !== agent || retained.method !== "reviewed")
49015
+ unresolved("invalid retained review");
49016
+ verifyAgentDiscovery(retained);
49017
+ if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
49018
+ unresolved("retained review is missing the full agent configuration source");
49019
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
49020
+ unresolved("retained settings witness names another configuration source");
49021
+ if (sources.some((source) => !retained.sources.some((saved) => isDeepStrictEqual2(saved, source))) || [...roots].some((root) => !retained.roots.includes(root)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
49022
+ unresolved("retained review is missing current runtime discovery coverage");
49023
+ return retained;
49024
+ }
48999
49025
  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))
49000
49026
  throw new Error("Invalid --discovery-inputs version or agents");
49001
49027
  const review = options.reviewed?.agents.find((item) => item.agent === agent);
@@ -57332,7 +57358,7 @@ var init_execution_grants = __esm(() => {
57332
57358
  });
57333
57359
 
57334
57360
  // src/lib/execution-grant-client.ts
57335
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
57361
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
57336
57362
  function id(value) {
57337
57363
  if (!grantIdentifier(value))
57338
57364
  throw new Error("Invalid execution grant profile or revision identifier.");
@@ -57375,7 +57401,7 @@ async function resolveExecutionGrant(input2) {
57375
57401
  if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
57376
57402
  throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
57377
57403
  }
57378
- if (!isDeepStrictEqual2(value.bindings.selection, snapshot2.selection) || !isDeepStrictEqual2(value.bindings.consumer, snapshot2.consumer)) {
57404
+ if (!isDeepStrictEqual3(value.bindings.selection, snapshot2.selection) || !isDeepStrictEqual3(value.bindings.consumer, snapshot2.consumer)) {
57379
57405
  throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
57380
57406
  }
57381
57407
  return value;
@@ -59783,17 +59809,17 @@ var WSContext = class {
59783
59809
  if (typeof args[0] === "function") {
59784
59810
  const [createEvents, options] = args;
59785
59811
  return async function upgradeWebSocket(c, next) {
59786
- const events = await createEvents(c);
59787
- const result = await handler(c, events, options);
59812
+ const events2 = await createEvents(c);
59813
+ const result = await handler(c, events2, options);
59788
59814
  if (result) {
59789
59815
  return result;
59790
59816
  }
59791
59817
  await next();
59792
59818
  };
59793
59819
  } else {
59794
- const [c, events, options] = args;
59820
+ const [c, events2, options] = args;
59795
59821
  return (async () => {
59796
- const upgraded = await handler(c, events, options);
59822
+ const upgraded = await handler(c, events2, options);
59797
59823
  if (!upgraded) {
59798
59824
  throw new Error("Failed to upgrade WebSocket");
59799
59825
  }
@@ -60990,7 +61016,7 @@ var init_dist2 = __esm(() => {
60990
61016
  "sec-websocket-extensions",
60991
61017
  "sec-websocket-protocol"
60992
61018
  ]);
60993
- upgradeWebSocket = defineWebSocketHelper(async (c, events, options) => {
61019
+ upgradeWebSocket = defineWebSocketHelper(async (c, events2, options) => {
60994
61020
  if (c.req.header("upgrade")?.toLowerCase() !== "websocket")
60995
61021
  return;
60996
61022
  const env = c.env;
@@ -61027,7 +61053,7 @@ var init_dist2 = __esm(() => {
61027
61053
  url: new URL(c.req.url)
61028
61054
  };
61029
61055
  try {
61030
- events?.onOpen?.(new Event("open"), ctx);
61056
+ events2?.onOpen?.(new Event("open"), ctx);
61031
61057
  } catch (e2) {
61032
61058
  (options?.onError ?? console.error)(e2);
61033
61059
  }
@@ -61035,7 +61061,7 @@ var init_dist2 = __esm(() => {
61035
61061
  const datas = Array.isArray(data) ? data : [data];
61036
61062
  for (const data2 of datas)
61037
61063
  try {
61038
- events?.onMessage?.(new MessageEvent("message", { data: isBinary ? data2 instanceof ArrayBuffer ? data2 : data2.buffer.slice(data2.byteOffset, data2.byteOffset + data2.byteLength) : typeof data2 === "string" ? data2 : Buffer.from(data2).toString("utf-8") }), ctx);
61064
+ events2?.onMessage?.(new MessageEvent("message", { data: isBinary ? data2 instanceof ArrayBuffer ? data2 : data2.buffer.slice(data2.byteOffset, data2.byteOffset + data2.byteLength) : typeof data2 === "string" ? data2 : Buffer.from(data2).toString("utf-8") }), ctx);
61039
61065
  } catch (e2) {
61040
61066
  (options?.onError ?? console.error)(e2);
61041
61067
  }
@@ -61048,7 +61074,7 @@ var init_dist2 = __esm(() => {
61048
61074
  });
61049
61075
  ws.on("close", (code2, reason) => {
61050
61076
  try {
61051
- events?.onClose?.(new CloseEvent("close", {
61077
+ events2?.onClose?.(new CloseEvent("close", {
61052
61078
  code: code2,
61053
61079
  reason: reason.toString()
61054
61080
  }), ctx);
@@ -61058,7 +61084,7 @@ var init_dist2 = __esm(() => {
61058
61084
  });
61059
61085
  ws.on("error", (error61) => {
61060
61086
  try {
61061
- events?.onError?.(new ErrorEvent("error", { error: error61 }), ctx);
61087
+ events2?.onError?.(new ErrorEvent("error", { error: error61 }), ctx);
61062
61088
  } catch (e2) {
61063
61089
  (options?.onError ?? console.error)(e2);
61064
61090
  }
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.9.14",
10
+ version: "0.9.16",
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.9.14",
24125
+ version: "0.9.16",
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.9.14",
24128
+ version: "0.9.16",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
package/dist/index.js CHANGED
@@ -9093,6 +9093,7 @@ function assertAgentPolicyCollections(policy) {
9093
9093
  import { createHash as createHash16 } from "crypto";
9094
9094
  import { lstatSync as lstatSync20, readFileSync as readFileSync16, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
9095
9095
  import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute14, join as join26, resolve as resolve14, sep as sep5 } from "path";
9096
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
9096
9097
 
9097
9098
  // node_modules/yaml/dist/index.js
9098
9099
  var composer = require_composer();
@@ -9610,7 +9611,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
9610
9611
  // package.json
9611
9612
  var package_default = {
9612
9613
  name: "@hasna/skills",
9613
- version: "0.9.14",
9614
+ version: "0.9.16",
9614
9615
  description: "Skills library for AI coding agents",
9615
9616
  type: "module",
9616
9617
  bin: {
@@ -13369,7 +13370,15 @@ function planAgentIntegration(options) {
13369
13370
  validateProfile(profileId2);
13370
13371
  return [agent, { command, profileId: profileId2 }];
13371
13372
  }));
13372
- const discoveries = [...new Set(options.agents)].map((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }));
13373
+ const retainedAgents = [];
13374
+ const discoveries = [...new Set(options.agents)].map((agent) => {
13375
+ const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? policy.bridge.discovery[agent] : undefined;
13376
+ if (retainedReview) {
13377
+ assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
13378
+ retainedAgents.push(agent);
13379
+ }
13380
+ return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
13381
+ });
13373
13382
  const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases });
13374
13383
  const changes = [];
13375
13384
  for (const agent of [...new Set(options.agents)]) {
@@ -13454,7 +13463,7 @@ function planAgentIntegration(options) {
13454
13463
  if (JSON.stringify(policy) !== JSON.stringify(nextPolicy))
13455
13464
  changes.push({ path: policyPath, before: previousPolicy, after: serializedPolicy });
13456
13465
  recheckRootAliases(aliases);
13457
- return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
13466
+ return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
13458
13467
  }
13459
13468
  function planClaudeStopHookUpdate(options) {
13460
13469
  return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
@@ -13573,6 +13582,8 @@ function applyAgentIntegration(plan) {
13573
13582
  parseManagedSkillPolicy(change.after);
13574
13583
  const aliases = plan.rootAliases ?? [];
13575
13584
  recheckRootAliases(aliases);
13585
+ for (const agent of plan.retainedReviewChecks?.agents ?? [])
13586
+ assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
13576
13587
  for (const agent of plan.managedAgentChecks?.agents ?? [])
13577
13588
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
13578
13589
  for (const binding of plan.discoveryBefore ?? []) {
@@ -16439,14 +16450,20 @@ function unchanged2(before) {
16439
16450
  need4(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
16440
16451
  }
16441
16452
 
16453
+ // src/lib/codex-hook-additions-review.ts
16454
+ var events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
16455
+ var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
16456
+ var proofs = new WeakMap;
16457
+
16442
16458
  // src/lib/codex-hook-discovery-recovery.ts
16443
16459
  var sha2 = (text2) => createHash15("sha256").update(text2).digest("hex");
16444
- var proofs = new WeakMap;
16460
+ var proofs2 = new WeakMap;
16445
16461
  function assertCodexHookDiscoveryRecovery(binding, witness) {
16446
- const proof = proofs.get(witness);
16462
+ const proof = proofs2.get(witness);
16447
16463
  need4(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha2(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
16448
16464
  unchanged2(proof.before);
16449
16465
  unchanged2(proof.current);
16466
+ proof.recheckAdditions();
16450
16467
  }
16451
16468
  function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witness) {
16452
16469
  if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
@@ -16567,7 +16584,10 @@ function rebindAgentDiscovery(binding, changes) {
16567
16584
  const budget = discoveryByteBudget();
16568
16585
  return { ...binding, sources: binding.sources.map((source) => {
16569
16586
  assertMarketplaceBinding(binding, source);
16570
- return { ...source, sha256: projected(source, changes, budget) };
16587
+ const sha2562 = projected(source, changes, budget);
16588
+ if (!changes.has(source.path) && sha2562 !== source.sha256)
16589
+ throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
16590
+ return { ...source, sha256: sha2562 };
16571
16591
  }) };
16572
16592
  }
16573
16593
  function captureDiscoveryByteSources(paths) {
@@ -16691,6 +16711,19 @@ function resolveAgentDiscovery(options) {
16691
16711
  }
16692
16712
  }
16693
16713
  }
16714
+ if (options.reviewed === undefined && options.retainedReview !== undefined) {
16715
+ const retained = options.retainedReview;
16716
+ if (retained.agent !== agent || retained.method !== "reviewed")
16717
+ unresolved("invalid retained review");
16718
+ verifyAgentDiscovery(retained);
16719
+ if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
16720
+ unresolved("retained review is missing the full agent configuration source");
16721
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
16722
+ unresolved("retained settings witness names another configuration source");
16723
+ if (sources.some((source) => !retained.sources.some((saved) => isDeepStrictEqual3(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
16724
+ unresolved("retained review is missing current runtime discovery coverage");
16725
+ return retained;
16726
+ }
16694
16727
  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))
16695
16728
  throw new Error("Invalid --discovery-inputs version or agents");
16696
16729
  const review = options.reviewed?.agents.find((item) => item.agent === agent);
@@ -71,7 +71,8 @@ export declare function reconcileCodexNativeHooks(options: CodexNativeHookReconc
71
71
  hooksPath: string;
72
72
  hooksSha256: string;
73
73
  };
74
- additions: any[];
74
+ additions: any;
75
+ version?: number | undefined;
75
76
  } | undefined;
76
77
  configDiscoveryRecovery?: import("./codex-hook-discovery-recovery.js").CodexHookDiscoveryRecovery | undefined;
77
78
  version: number;
@@ -46,5 +46,6 @@ export declare function resolveAgentDiscovery(options: {
46
46
  home: string;
47
47
  agent: IntegrationAgent;
48
48
  reviewed?: ReviewedDiscoveryInputs;
49
+ retainedReview?: AgentDiscoveryBinding;
49
50
  canonical?: (path: string) => string;
50
51
  }): AgentDiscoveryBinding;
@@ -56,6 +56,11 @@ export interface AgentIntegrationPlan {
56
56
  home: string;
57
57
  agents: IntegrationAgent[];
58
58
  };
59
+ retainedReviewChecks?: {
60
+ home: string;
61
+ projectDir: string;
62
+ agents: IntegrationAgent[];
63
+ };
59
64
  }
60
65
  /** Parse the reviewed exact-target binding used by native migration. Paths are
61
66
  * deliberately project-relative so a manifest cannot silently select another
@@ -4,6 +4,25 @@ export interface NativeHookAdditionsRecovery {
4
4
  path: string;
5
5
  reviewedPlanDigest?: string;
6
6
  }
7
+ /** Internal capability, not a caller-supplied config override. */
8
+ export interface ReviewedNativeHookAdditions {
9
+ readonly reviewSha256: string;
10
+ }
11
+ type JournalHook = {
12
+ key: string;
13
+ currentHash: string;
14
+ };
15
+ type TrustState = Readonly<{
16
+ trusted_hash: string;
17
+ enabled?: true;
18
+ }>;
19
+ type AddedTrustState = Readonly<{
20
+ key: string;
21
+ state: TrustState;
22
+ }>;
23
+ /** Rebind the proof to this exact journal transition and recheck every source.
24
+ * A serialized/cloned receipt can never create a discovery exception. */
25
+ export declare function readReviewedAdditionTrustStates(proof: ReviewedNativeHookAdditions, before: Snapshot, current: Snapshot, journalHooks: JournalHook[]): readonly AddedTrustState[];
7
26
  /** An explicit, private review of a later producer's append-only hook change.
8
27
  * The producer backups must themselves describe exactly the completed journal
9
28
  * operation. Neither a claimed receipt nor a matching raw hash grants authority
@@ -25,6 +44,7 @@ export declare function reviewNativeHookAdditions(options: {
25
44
  }): {
26
45
  hooks: any[];
27
46
  recheck: () => void;
47
+ trustProof: ReviewedNativeHookAdditions;
28
48
  receipt: {
29
49
  reviewPath: string;
30
50
  reviewSha256: string;
@@ -36,7 +56,8 @@ export declare function reviewNativeHookAdditions(options: {
36
56
  hooksPath: string;
37
57
  hooksSha256: string;
38
58
  };
39
- additions: any[];
59
+ additions: any;
60
+ version?: number | undefined;
40
61
  };
41
62
  };
42
63
  export {};
@@ -1,5 +1,6 @@
1
1
  import type { AgentDiscoveryBinding, DiscoverySource } from "./agent-discovery.js";
2
2
  import { type snapshot } from "./codex-hook-trust-files.js";
3
+ import { type ReviewedNativeHookAdditions } from "./codex-hook-additions-review.js";
3
4
  type Snapshot = ReturnType<typeof snapshot>;
4
5
  /** A receipt projection, not an override supplied by a CLI caller. Only this
5
6
  * module can create the in-memory proof used by discovery verification. */
@@ -17,7 +18,7 @@ export interface CodexHookDiscoveryRecovery {
17
18
  export declare function createCodexHookDiscoveryRecovery(binding: AgentDiscoveryBinding, before: Snapshot, current: Snapshot, hooks: Array<{
18
19
  key: string;
19
20
  currentHash: string;
20
- }>): CodexHookDiscoveryRecovery | undefined;
21
+ }>, additions?: ReviewedNativeHookAdditions): CodexHookDiscoveryRecovery | undefined;
21
22
  export declare function assertCodexHookDiscoveryRecovery(binding: AgentDiscoveryBinding, witness: CodexHookDiscoveryRecovery): void;
22
23
  /** Verify this one historical byte witness using the already-validated native
23
24
  * transition. Every other source still uses its ordinary current-file hash. */
package/dist/sdk/index.js CHANGED
@@ -31310,17 +31310,17 @@ class JsonEventsStore {
31310
31310
  }
31311
31311
  async appendEvent(event) {
31312
31312
  await this.init();
31313
- const events = await this.readJson(this.eventsPath, []);
31314
- events.push(event);
31315
- await this.writeJson(this.eventsPath, events);
31313
+ const events2 = await this.readJson(this.eventsPath, []);
31314
+ events2.push(event);
31315
+ await this.writeJson(this.eventsPath, events2);
31316
31316
  return event;
31317
31317
  }
31318
31318
  async appendEventOnce(event, options = {}) {
31319
31319
  await this.init();
31320
- const events = await this.readJson(this.eventsPath, []);
31320
+ const events2 = await this.readJson(this.eventsPath, []);
31321
31321
  const dedupe = options.dedupe !== false;
31322
31322
  if (dedupe) {
31323
- const existing = findEventByIdentity(events, { id: event.id, dedupeKey: event.dedupeKey });
31323
+ const existing = findEventByIdentity(events2, { id: event.id, dedupeKey: event.dedupeKey });
31324
31324
  if (existing) {
31325
31325
  return {
31326
31326
  event: existing,
@@ -31330,8 +31330,8 @@ class JsonEventsStore {
31330
31330
  };
31331
31331
  }
31332
31332
  }
31333
- events.push(event);
31334
- await this.writeJson(this.eventsPath, events);
31333
+ events2.push(event);
31334
+ await this.writeJson(this.eventsPath, events2);
31335
31335
  return {
31336
31336
  event,
31337
31337
  stored: true,
@@ -31341,13 +31341,13 @@ class JsonEventsStore {
31341
31341
  }
31342
31342
  async listEvents(options = {}) {
31343
31343
  await this.init();
31344
- const events = await this.readJson(this.eventsPath, []);
31345
- return queryEvents(events, options);
31344
+ const events2 = await this.readJson(this.eventsPath, []);
31345
+ return queryEvents(events2, options);
31346
31346
  }
31347
31347
  async listEventsPage(options = {}) {
31348
31348
  await this.init();
31349
- const events = await this.readJson(this.eventsPath, []);
31350
- const queried = queryEvents(events, {
31349
+ const events2 = await this.readJson(this.eventsPath, []);
31350
+ const queried = queryEvents(events2, {
31351
31351
  eventId: options.eventId,
31352
31352
  source: options.source,
31353
31353
  type: options.type
@@ -31365,8 +31365,8 @@ class JsonEventsStore {
31365
31365
  };
31366
31366
  }
31367
31367
  async findEventByIdentity(identity3) {
31368
- const events = await this.listEvents();
31369
- return findEventByIdentity(events, identity3);
31368
+ const events2 = await this.listEvents();
31369
+ return findEventByIdentity(events2, identity3);
31370
31370
  }
31371
31371
  async appendDelivery(result) {
31372
31372
  await this.init();
@@ -31471,8 +31471,8 @@ function normalizeEventPageLimit(limit) {
31471
31471
  throw new Error(`Event page limit must be a positive integer, got ${limit}`);
31472
31472
  return Math.min(limit, MAX_EVENT_PAGE_LIMIT);
31473
31473
  }
31474
- function queryEvents(events, options) {
31475
- let rows = events;
31474
+ function queryEvents(events2, options) {
31475
+ let rows = events2;
31476
31476
  if (options.eventId)
31477
31477
  rows = rows.filter((event) => event.id === options.eventId);
31478
31478
  if (options.source)
@@ -31491,8 +31491,8 @@ function assertCursorFilter(name, cursorValue, optionValue) {
31491
31491
  if (cursorValue !== optionValue)
31492
31492
  throw new Error(`Local JSON event cursor ${name} filter mismatch`);
31493
31493
  }
31494
- function findEventByIdentity(events, identity3) {
31495
- return events.find((event) => identity3.id !== undefined && event.id === identity3.id || identity3.dedupeKey !== undefined && event.dedupeKey === identity3.dedupeKey);
31494
+ function findEventByIdentity(events2, identity3) {
31495
+ return events2.find((event) => identity3.id !== undefined && event.id === identity3.id || identity3.dedupeKey !== undefined && event.dedupeKey === identity3.dedupeKey);
31496
31496
  }
31497
31497
  function buildSignatureBase(timestamp, body4) {
31498
31498
  return `${timestamp}.${body4}`;
@@ -32125,16 +32125,16 @@ class EventsClient {
32125
32125
  async listEventsPage(options = {}) {
32126
32126
  if (this.store.listEventsPage)
32127
32127
  return this.store.listEventsPage(options);
32128
- const events = queryClientEvents(await this.store.listEvents(), {
32128
+ const events2 = queryClientEvents(await this.store.listEvents(), {
32129
32129
  eventId: options.eventId,
32130
32130
  source: options.source,
32131
32131
  type: options.type
32132
32132
  });
32133
32133
  const offset = decodeLocalJsonEventCursor(options.cursor, options);
32134
32134
  const limit = normalizeEventPageLimit(options.limit);
32135
- const pageEvents = events.slice(offset, offset + limit);
32135
+ const pageEvents = events2.slice(offset, offset + limit);
32136
32136
  const nextOffset = offset + pageEvents.length;
32137
- const hasMore = nextOffset < events.length;
32137
+ const hasMore = nextOffset < events2.length;
32138
32138
  return {
32139
32139
  events: pageEvents,
32140
32140
  cursor: options.cursor,
@@ -32265,8 +32265,8 @@ class EventsClient {
32265
32265
  return createDeliveryResult(event, channel, attempts);
32266
32266
  }
32267
32267
  }
32268
- function queryClientEvents(events, options) {
32269
- let rows = events;
32268
+ function queryClientEvents(events2, options) {
32269
+ let rows = events2;
32270
32270
  if (options.eventId)
32271
32271
  rows = rows.filter((event) => event.id === options.eventId);
32272
32272
  if (options.source)
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
32373
32373
  // package.json
32374
32374
  var package_default = {
32375
32375
  name: "@hasna/skills",
32376
- version: "0.9.14",
32376
+ version: "0.9.16",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -37704,6 +37704,7 @@ function isOwnedCliBridge(path, expectedPaths) {
37704
37704
  import { createHash as createHash15 } from "crypto";
37705
37705
  import { lstatSync as lstatSync17, readFileSync as readFileSync12, readdirSync as readdirSync6, realpathSync as realpathSync3 } from "fs";
37706
37706
  import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute14, join as join20, resolve as resolve13, sep as sep3 } from "path";
37707
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
37707
37708
 
37708
37709
  // node_modules/yaml/dist/index.js
37709
37710
  var composer = require_composer();
@@ -40267,14 +40268,20 @@ function unchanged2(before) {
40267
40268
  need4(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
40268
40269
  }
40269
40270
 
40271
+ // src/lib/codex-hook-additions-review.ts
40272
+ var events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
40273
+ var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
40274
+ var proofs = new WeakMap;
40275
+
40270
40276
  // src/lib/codex-hook-discovery-recovery.ts
40271
40277
  var sha = (text2) => createHash14("sha256").update(text2).digest("hex");
40272
- var proofs = new WeakMap;
40278
+ var proofs2 = new WeakMap;
40273
40279
  function assertCodexHookDiscoveryRecovery(binding, witness) {
40274
- const proof = proofs.get(witness);
40280
+ const proof = proofs2.get(witness);
40275
40281
  need4(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
40276
40282
  unchanged2(proof.before);
40277
40283
  unchanged2(proof.current);
40284
+ proof.recheckAdditions();
40278
40285
  }
40279
40286
  function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witness) {
40280
40287
  if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
@@ -40395,7 +40402,10 @@ function rebindAgentDiscovery(binding, changes) {
40395
40402
  const budget = discoveryByteBudget();
40396
40403
  return { ...binding, sources: binding.sources.map((source) => {
40397
40404
  assertMarketplaceBinding(binding, source);
40398
- return { ...source, sha256: projected(source, changes, budget) };
40405
+ const sha2562 = projected(source, changes, budget);
40406
+ if (!changes.has(source.path) && sha2562 !== source.sha256)
40407
+ throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
40408
+ return { ...source, sha256: sha2562 };
40399
40409
  }) };
40400
40410
  }
40401
40411
  function captureDiscoveryByteSources(paths) {
@@ -40519,6 +40529,19 @@ function resolveAgentDiscovery(options) {
40519
40529
  }
40520
40530
  }
40521
40531
  }
40532
+ if (options.reviewed === undefined && options.retainedReview !== undefined) {
40533
+ const retained = options.retainedReview;
40534
+ if (retained.agent !== agent || retained.method !== "reviewed")
40535
+ unresolved("invalid retained review");
40536
+ verifyAgentDiscovery(retained);
40537
+ if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
40538
+ unresolved("retained review is missing the full agent configuration source");
40539
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
40540
+ unresolved("retained settings witness names another configuration source");
40541
+ if (sources.some((source) => !retained.sources.some((saved) => isDeepStrictEqual3(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
40542
+ unresolved("retained review is missing current runtime discovery coverage");
40543
+ return retained;
40544
+ }
40522
40545
  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))
40523
40546
  throw new Error("Invalid --discovery-inputs version or agents");
40524
40547
  const review = options.reviewed?.agents.find((item) => item.agent === agent);
@@ -40712,7 +40735,7 @@ export default async ({ directory }) => {
40712
40735
  }
40713
40736
 
40714
40737
  // src/lib/agent-codex.ts
40715
- import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
40738
+ import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
40716
40739
  var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
40717
40740
  function assertCodexPathConfigEditable(text2) {
40718
40741
  const before = Bun.TOML.parse(text2);
@@ -40722,7 +40745,7 @@ function assertCodexPathConfigEditable(text2) {
40722
40745
  delete expected.skills.config;
40723
40746
  try {
40724
40747
  const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
40725
- if (isDeepStrictEqual3(withoutSections, expected))
40748
+ if (isDeepStrictEqual4(withoutSections, expected))
40726
40749
  return;
40727
40750
  } catch {}
40728
40751
  throw new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables; preserve and convert inline or dotted config arrays before running skills hook install");
@@ -40760,7 +40783,7 @@ function disableCodexBundledSkills(text2) {
40760
40783
  const candidates = new Set;
40761
40784
  const consider = (candidate2) => {
40762
40785
  try {
40763
- if (isDeepStrictEqual3(Bun.TOML.parse(candidate2), expected))
40786
+ if (isDeepStrictEqual4(Bun.TOML.parse(candidate2), expected))
40764
40787
  candidates.add(candidate2);
40765
40788
  } catch {}
40766
40789
  };
@@ -41218,7 +41241,15 @@ function planAgentIntegration(options) {
41218
41241
  validateProfile(profileId2);
41219
41242
  return [agent, { command, profileId: profileId2 }];
41220
41243
  }));
41221
- const discoveries = [...new Set(options.agents)].map((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }));
41244
+ const retainedAgents = [];
41245
+ const discoveries = [...new Set(options.agents)].map((agent) => {
41246
+ const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? policy.bridge.discovery[agent] : undefined;
41247
+ if (retainedReview) {
41248
+ assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
41249
+ retainedAgents.push(agent);
41250
+ }
41251
+ return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
41252
+ });
41222
41253
  const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases });
41223
41254
  const changes = [];
41224
41255
  for (const agent of [...new Set(options.agents)]) {
@@ -41303,7 +41334,7 @@ function planAgentIntegration(options) {
41303
41334
  if (JSON.stringify(policy) !== JSON.stringify(nextPolicy))
41304
41335
  changes.push({ path: policyPath, before: previousPolicy, after: serializedPolicy });
41305
41336
  recheckRootAliases(aliases);
41306
- return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
41337
+ return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
41307
41338
  }
41308
41339
  function planClaudeStopHookUpdate(options) {
41309
41340
  return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
@@ -41422,6 +41453,8 @@ function applyAgentIntegration(plan) {
41422
41453
  parseManagedSkillPolicy(change.after);
41423
41454
  const aliases = plan.rootAliases ?? [];
41424
41455
  recheckRootAliases(aliases);
41456
+ for (const agent of plan.retainedReviewChecks?.agents ?? [])
41457
+ assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
41425
41458
  for (const agent of plan.managedAgentChecks?.agents ?? [])
41426
41459
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
41427
41460
  for (const binding of plan.discoveryBefore ?? []) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.9.14",
3
+ "version": "0.9.16",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {