@hasna/skills 0.9.6 → 0.9.8

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/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.9.6",
36871
+ version: "0.9.8",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -108033,7 +108033,7 @@ var init_codex_hook_rpc = __esm(() => {
108033
108033
 
108034
108034
  // src/lib/codex-hook-trust-layout.ts
108035
108035
  import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
108036
- function codexTrustTextWitness(text3, keys3) {
108036
+ function codexTrustTextWitness(text3, keys3, canonicalize = true) {
108037
108037
  const parsed = Bun.TOML.parse(text3), wanted = new Set(keys3);
108038
108038
  const headers = [...text3.matchAll(/^[ \t]*(\[[^\r\n]+\])[ \t]*(#[^\r\n]*)?(?:\r?\n|$)/gm)];
108039
108039
  if (headers.length > 512)
@@ -108105,9 +108105,19 @@ function codexTrustTextWitness(text3, keys3) {
108105
108105
  let result2 = text3;
108106
108106
  for (const span of replacements2.reverse())
108107
108107
  result2 = result2.slice(0, span.start) + span.comments + result2.slice(span.end);
108108
- return canonicalizeTableOrder(result2).split(/\r?\n/).filter((line) => line.trim()).join(`
108108
+ return (canonicalize ? canonicalizeTableOrder(result2) : result2).split(/\r?\n/).filter((line) => line.trim()).join(`
108109
108109
  `);
108110
108110
  }
108111
+ function codexTrustReconcileWitness(text3, keys3) {
108112
+ return codexTrustTextWitness(text3, keys3, false).replace(/[ \t]+$/gm, "");
108113
+ }
108114
+ function codexTrustUnmanagedSemantic(text3, keys3) {
108115
+ const parsed = structuredClone(Bun.TOML.parse(text3));
108116
+ for (const key of keys3)
108117
+ if (parsed.hooks?.state?.[key] !== undefined)
108118
+ delete parsed.hooks.state[key];
108119
+ return JSON.stringify(stableSemantic(prune(parsed)));
108120
+ }
108111
108121
  var fail4 = () => new Error('CODEX_HOOK_TRUST_UNSUPPORTED_LAYOUT: preserve and convert hook trust to ordinary [hooks.state."<key>"] tables before enrollment'), prune = (value) => {
108112
108122
  if (value.hooks?.state && !Object.keys(value.hooks.state).length)
108113
108123
  delete value.hooks.state;
@@ -108146,6 +108156,12 @@ var fail4 = () => new Error('CODEX_HOOK_TRUST_UNSUPPORTED_LAYOUT: preserve and c
108146
108156
  const simple = blocks.filter((block) => !block.key.startsWith("[["));
108147
108157
  const arrays = blocks.filter((block) => block.key.startsWith("[["));
108148
108158
  return prefix + [...simple, ...arrays].map((block) => block.text).join("");
108159
+ }, stableSemantic = (value) => {
108160
+ if (Array.isArray(value))
108161
+ return value.map(stableSemantic);
108162
+ if (value && typeof value === "object")
108163
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableSemantic(value[key])]));
108164
+ return value;
108149
108165
  };
108150
108166
  var init_codex_hook_trust_layout = () => {};
108151
108167
 
@@ -108240,11 +108256,33 @@ var init_codex_hook_trust_identity = __esm(() => {
108240
108256
  });
108241
108257
 
108242
108258
  // src/lib/agent-codex-trust.ts
108243
- import { lstatSync as lstatSync28, mkdirSync as mkdirSync25, readdirSync as readdirSync20 } from "fs";
108259
+ import { lstatSync as lstatSync28, mkdirSync as mkdirSync25, readdirSync as readdirSync20, readFileSync as readFileSync37, realpathSync as realpathSync13, statSync as statSync21 } from "fs";
108244
108260
  import { createHash as createHash26, randomUUID as randomUUID10 } from "crypto";
108245
- import { join as join56, resolve as resolve22 } from "path";
108261
+ import { execFileSync } from "child_process";
108262
+ import { dirname as dirname30, isAbsolute as isAbsolute25, join as join56, resolve as resolve22 } from "path";
108246
108263
  import { homedir as homedir11 } from "os";
108247
108264
  import { isDeepStrictEqual as isDeepStrictEqual6 } from "util";
108265
+ function verifyReleaseArtifact(proof, skillsCli) {
108266
+ need4(isAbsolute25(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
108267
+ const tarPath = realpathSync13(proof.packageTarPath);
108268
+ need4(statSync21(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
108269
+ const tarBytes = readFileSync37(tarPath);
108270
+ need4(tarBytes.byteLength === proof.packageTarBytes && createHash26("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
108271
+ const packageRoot = dirname30(dirname30(realpathSync13(skillsCli.receipt.path)));
108272
+ const manifest = readFileSync37(join56(packageRoot, "package.json"));
108273
+ need4(createHash26("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
108274
+ need4(createHash26("sha256").update(readFileSync37(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
108275
+ const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync37(skillsCli.receipt.path)]];
108276
+ for (const [member, expected] of members2) {
108277
+ let actual;
108278
+ try {
108279
+ actual = execFileSync("tar", ["-xOf", tarPath, member], { maxBuffer: 32 * 1024 * 1024 });
108280
+ } catch {
108281
+ throw new Error("RECONCILE_RELEASE_ARTIFACT_MEMBER_MISSING");
108282
+ }
108283
+ need4(Buffer.compare(actual, expected) === 0, "RECONCILE_RELEASE_ARTIFACT_MEMBER_CHANGED");
108284
+ }
108285
+ }
108248
108286
  async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
108249
108287
  let rpc;
108250
108288
  try {
@@ -108267,24 +108305,54 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
108267
108305
  need4(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
108268
108306
  need4(isDeepStrictEqual6(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
108269
108307
  need4(configBefore.sha256 === intent.beforeSha256 && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
108308
+ const policyCurrent = snapshot(join56(dataDir3, "agent-policy.json"), true);
108309
+ let supersededBinding = false;
108310
+ if (policyCurrent.sha256 !== policyBefore.sha256) {
108311
+ need4(options.supersedeBinding === true, "RECONCILE_POLICY_CHANGED");
108312
+ const oldPolicy = JSON.parse(policyBefore.text), currentPolicy = JSON.parse(policyCurrent.text);
108313
+ need4(isDeepStrictEqual6(withoutCodexCommand(oldPolicy), withoutCodexCommand(currentPolicy)), "RECONCILE_POLICY_CHANGED");
108314
+ supersededBinding = true;
108315
+ }
108316
+ if (supersededBinding) {
108317
+ need4(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
108318
+ const proof = options.releaseProof;
108319
+ need4(!!proof && isAbsolute25(proof.packageTarPath) && /^[a-f0-9]{64}$/.test(proof.packageTarSha256) && Number.isInteger(proof.packageTarBytes) && proof.packageTarBytes > 0 && /^[a-f0-9]{64}$/.test(proof.manifestSha256) && /^[a-f0-9]{64}$/.test(proof.executableSha256), "RECONCILE_RELEASE_PROVENANCE_REQUIRED");
108320
+ }
108270
108321
  const currentConfig = snapshot(intent.configPath, true);
108271
- need4(codexTrustTextWitness(configBefore.text, intent.hooks.map((h) => h.key)) === codexTrustTextWitness(currentConfig.text, intent.hooks.map((h) => h.key)), "RECONCILE_UNRELATED_CONFIG_CHANGED");
108322
+ const managedKeys = intent.hooks.map((h) => h.key);
108323
+ const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
108324
+ const unmanagedSame = codexTrustUnmanagedSemantic(configBefore.text, managedKeys) === codexTrustUnmanagedSemantic(currentConfig.text, managedKeys);
108325
+ need4(unmanagedSame && (witnessSame || supersededBinding), "RECONCILE_UNRELATED_CONFIG_CHANGED");
108272
108326
  const before2 = structure(configBefore.text), current = structure(currentConfig.text), expectedConfig = structuredClone(before2);
108273
108327
  expectedConfig.hooks ??= {};
108274
108328
  expectedConfig.hooks.state ??= {};
108275
108329
  for (const hook of intent.hooks)
108276
108330
  expectedConfig.hooks.state[hook.key] = { ...expectedConfig.hooks.state[hook.key], enabled: true, trusted_hash: hook.currentHash };
108277
- need4(isDeepStrictEqual6(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
108278
- const policyCurrent = snapshot(join56(dataDir3, "agent-policy.json"), true);
108279
- need4(policyCurrent.sha256 === policyBefore.sha256, "RECONCILE_POLICY_CHANGED");
108280
- const policy = parseManagedSkillPolicy(policyBefore.text), binding = policy.bridge;
108331
+ if (supersededBinding) {
108332
+ for (const hook of intent.hooks) {
108333
+ const state = current.hooks?.state?.[hook.key];
108334
+ need4(state?.enabled === true && typeof state.trusted_hash === "string" && /^sha256:[a-f0-9]{64}$/.test(state.trusted_hash), "RECONCILE_CONFIG_DRIFT");
108335
+ }
108336
+ } else
108337
+ need4(isDeepStrictEqual6(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
108338
+ const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
108281
108339
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
108282
108340
  const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
108283
- need4(isDeepStrictEqual6(skillsCli.receipt, intent.skillsCli), "RECONCILE_SKILLS_BINDING_CHANGED");
108341
+ if (supersededBinding)
108342
+ verifyReleaseArtifact(options.releaseProof, skillsCli);
108343
+ const skillsBindingChanged = !isDeepStrictEqual6(skillsCli.receipt, intent.skillsCli);
108344
+ need4(!skillsBindingChanged || supersededBinding, "RECONCILE_SKILLS_BINDING_CHANGED");
108284
108345
  const alias = binding.rootAliases?.find((item) => item.agent === "codex"), codexHome = alias?.target ?? join56(home, ".codex"), hooksPath = join56(codexHome, "hooks.json");
108285
108346
  need4(resolve22(intent.configPath) === resolve22(join56(codexHome, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
108286
108347
  const hooksCurrent = snapshot(hooksPath);
108287
- need4(hooksCurrent.sha256 === intent.hooksSha256 || hooksCurrent.sha256 === hooksBefore.sha256, "RECONCILE_HOOKS_CHANGED");
108348
+ if (hooksCurrent.sha256 !== intent.hooksSha256 && hooksCurrent.sha256 !== hooksBefore.sha256) {
108349
+ need4(supersededBinding, "RECONCILE_HOOKS_CHANGED");
108350
+ const currentDeclarations = JSON.parse(hooksCurrent.text).hooks;
108351
+ for (const [event, [nativeEvent]] of Object.entries(eventNames)) {
108352
+ const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
108353
+ need4(isDeepStrictEqual6(currentDeclarations?.[event], [{ hooks: [{ type: "command", command, timeout: 15 }] }]), "RECONCILE_BINDING_HOOK_CHANGED");
108354
+ }
108355
+ }
108288
108356
  rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
108289
108357
  need4(rpc.version === intent.nativeVersion, "RECONCILE_NATIVE_VERSION_CHANGED");
108290
108358
  const discovered = await rpc.request("hooks/list", { cwds: [home] });
@@ -108294,12 +108362,15 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
108294
108362
  if (recordedNativeHooks)
108295
108363
  need4(nativeHooks.length === recordedNativeHooks.length, "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
108296
108364
  const plannedKeys = new Set(intent.hooks.map((hook) => hook.key));
108365
+ let nativeTrustPending = false;
108297
108366
  for (const expected of recordedNativeHooks ?? []) {
108298
108367
  const matches2 = nativeHooks.filter((hook) => hook.key === expected.key);
108299
108368
  need4(matches2.length === 1, "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
108300
108369
  const actual = safeNativeHook(matches2[0]);
108301
108370
  const stable = (hook) => {
108302
108371
  const { enabled, trustStatus, ...identity4 } = hook;
108372
+ if (supersededBinding)
108373
+ delete identity4.command;
108303
108374
  return identity4;
108304
108375
  };
108305
108376
  need4(isDeepStrictEqual6(stable(actual), stable(expected)), "RECONCILE_NATIVE_IDENTITY_CHANGED");
@@ -108310,7 +108381,14 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
108310
108381
  const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join56(home, ".codex/hooks.json"), hooksPath));
108311
108382
  need4(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
108312
108383
  const h = matches2[0];
108313
- need4(h.enabled === true && h.trustStatus === "trusted" && h.currentHash === wanted.currentHash, "RECONCILE_NATIVE_STATE_INCOMPLETE");
108384
+ const trusted = h.trustStatus === "trusted";
108385
+ need4(h.enabled === true && (trusted || supersededBinding && h.trustStatus === "modified") && (h.currentHash === wanted.currentHash || supersededBinding && /^sha256:[a-f0-9]{64}$/.test(h.currentHash)), "RECONCILE_NATIVE_STATE_INCOMPLETE");
108386
+ if (!trusted)
108387
+ nativeTrustPending = true;
108388
+ if (supersededBinding) {
108389
+ const nativeToCliEvent = { userPromptSubmit: "UserPromptSubmit", sessionStart: "SessionStart", subagentStart: "SubagentStart" };
108390
+ need4(h.command === renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, nativeToCliEvent[wanted.event] ?? ""), "RECONCILE_BINDING_HOOK_CHANGED");
108391
+ }
108314
108392
  }
108315
108393
  const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
108316
108394
  const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === intent.configPath && !layer.name.profile);
@@ -108330,7 +108408,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
108330
108408
  unchanged2(currentConfig);
108331
108409
  skillsCli.recheck();
108332
108410
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
108333
- const receipt = { version: 1, status: "reconciled", reconciled: true, automaticRollback: false, journal, planDigest: intent.planDigest, nativeVersion: rpc.version, nativeStateVerified: true, nativeExecutionVerified: false, configSha256: currentConfig.sha256, hooksSha256: hooksCurrent.sha256, policySha256: policyBefore.sha256, skillsCli: skillsCli.receipt, nativeInventoryScope: recordedNativeHooks ? "complete" : "declared-managed-hooks-only", unrelatedNativeHooksPreserved: !!recordedNativeHooks, unrelatedSettingsAndCommentsPreserved: true };
108411
+ const receipt = { version: 1, status: supersededBinding ? "superseded" : "reconciled", reconciled: true, supersededBinding, automaticRollback: false, journal, planDigest: intent.planDigest, nativeVersion: rpc.version, nativeStateVerified: !nativeTrustPending, nativeTrustPending, nativeExecutionVerified: false, configSha256: currentConfig.sha256, hooksSha256: hooksCurrent.sha256, policySha256: policyBefore.sha256, policyBeforeSha256: policyBefore.sha256, policyCurrentSha256: policyCurrent.sha256, previousSkillsCli: intent.skillsCli, skillsCli: skillsCli.receipt, releaseProof: options.releaseProof, nativeInventoryScope: recordedNativeHooks ? "complete" : "declared-managed-hooks-only", unrelatedNativeHooksPreserved: !!recordedNativeHooks, unrelatedSettingsAndCommentsPreserved: true };
108334
108412
  save2(join56(journal, "receipt.json"), json2(receipt));
108335
108413
  return receipt;
108336
108414
  } catch (error61) {
@@ -108460,7 +108538,12 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
108460
108538
  }
108461
108539
  }
108462
108540
  var json2 = (value) => JSON.stringify(value, null, 2) + `
108463
- `, structure = (text3) => JSON.parse(JSON.stringify(Bun.TOML.parse(text3))), eventNames, makePlanDigest = (value) => createHash26("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus });
108541
+ `, structure = (text3) => JSON.parse(JSON.stringify(Bun.TOML.parse(text3))), eventNames, makePlanDigest = (value) => createHash26("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus }), withoutCodexCommand = (value) => {
108542
+ const copy = structuredClone(value);
108543
+ if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
108544
+ copy.bridge.commands.codex = "<reviewed-codex-binding>";
108545
+ return copy;
108546
+ };
108464
108547
  var init_agent_codex_trust = __esm(() => {
108465
108548
  init_config();
108466
108549
  init_managed_policy();
@@ -108553,8 +108636,8 @@ var exports_agent_integration = {};
108553
108636
  __export(exports_agent_integration, {
108554
108637
  registerAgentIntegration: () => registerAgentIntegration
108555
108638
  });
108556
- import { readFileSync as readFileSync37 } from "fs";
108557
- import { isAbsolute as isAbsolute25 } from "path";
108639
+ import { readFileSync as readFileSync38 } from "fs";
108640
+ import { isAbsolute as isAbsolute26 } from "path";
108558
108641
  function pinnedHookProfile(input3, configuredProfile) {
108559
108642
  try {
108560
108643
  const parsed = parseSkillContextInput(JSON.stringify(input3));
@@ -108625,7 +108708,7 @@ function registerAgentIntegration(parent) {
108625
108708
  });
108626
108709
  hook.command("install").option("--agent <agent>", `Agent to configure: ${INTEGRATION_AGENTS.join(", ")}, all`, "all").option("--command <path>", "Skills executable used by the hook (preserves existing binding; new agents use skills)").option("--selection-profile <id>", "Selection profile (preserves existing binding; new agents use default)").option("--include-vendor", "Retained for compatibility; vendor system skills are always inventoried and disabled", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes for unsupported registrations").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Apply the plan, preserving prior configuration in private backups", false).option("--json", "Output a receipt as JSON", false).description("Plan or install one Skills CLI bridge plus native prompt hooks").action(async (options) => {
108627
108710
  try {
108628
- const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync37(options.discoveryInputs, "utf8")) : undefined;
108711
+ const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync38(options.discoveryInputs, "utf8")) : undefined;
108629
108712
  const plan = planAgentIntegration({ agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
108630
108713
  const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
108631
108714
  const receipt = { applied: options.apply, planned: plan.changes.map((change) => change.path), ...result2, rootAliases: plan.rootAliases ?? [], discovery: plan.discoveryAfter, nativeSkills: plan.nativeSkills.map((entry) => ({ agent: entry.agent, path: entry.path, managed: entry.managed, vendor: entry.vendor, system: entry.system === true, bridge: entry.bridge === true })), requiresNativeRetirement: plan.nativeSkills.some((entry) => !entry.bridge && !entry.system) };
@@ -108638,8 +108721,10 @@ function registerAgentIntegration(parent) {
108638
108721
  process.exitCode = 1;
108639
108722
  }
108640
108723
  });
108641
- const trust = hook.command("trust").requiredOption("--agent <agent>", "Native trust adapter (codex)").option("--codex-command <path>", "Installed Codex executable used for its native configuration API", "codex").option("--apply", "Enable and trust only the exact managed Skills hook identities", false).option("--plan-digest <sha256>", "Exact reviewed dry-run digest required with --apply").option("--json", "Output the native trust plan or receipt", false).description("Plan or enroll exact Skills hooks through Codex native trust controls").action(async (options) => {
108724
+ const trust = hook.command("trust").option("--agent <agent>", "Native trust adapter (codex)").option("--codex-command <path>", "Installed Codex executable used for its native configuration API", "codex").option("--apply", "Enable and trust only the exact managed Skills hook identities", false).option("--plan-digest <sha256>", "Exact reviewed dry-run digest required with --apply").option("--json", "Output the native trust plan or receipt", false).description("Plan or enroll exact Skills hooks through Codex native trust controls").action(async (options) => {
108642
108725
  try {
108726
+ if (!options.agent)
108727
+ throw new Error("Native trust enrollment requires --agent codex");
108643
108728
  if (options.agent !== "codex")
108644
108729
  throw new Error("Native trust enrollment currently supports --agent codex only");
108645
108730
  const result2 = await enrollCodexNativeHooks({ codexCommand: options.codexCommand, apply: options.apply, reviewedPlanDigest: options.planDigest });
@@ -108652,11 +108737,15 @@ function registerAgentIntegration(parent) {
108652
108737
  process.exitCode = 1;
108653
108738
  }
108654
108739
  });
108655
- trust.command("reconcile").requiredOption("--agent <agent>", "Native trust adapter (codex)").requiredOption("--journal <path>", "Private incomplete native trust journal to inspect").option("--codex-command <path>", "Installed Codex executable used for its native configuration API", "codex").option("--json", "Output the reconciliation receipt", false).description("Verify an interrupted Codex native trust write and resolve its private journal").action(async (options) => {
108740
+ trust.command("reconcile").requiredOption("--agent <agent>", "Native trust adapter (codex)").requiredOption("--journal <path>", "Private incomplete native trust journal to inspect").option("--codex-command <path>", "Installed Codex executable used for its native configuration API", "codex").option("--supersede-binding", "Resolve a stale journal only when current policy differs solely by the reviewed Codex CLI binding", false).option("--skills-package-tar <path>", "Exact package tarball consumed by the reviewed executable").option("--skills-package-tar-sha256 <sha256>", "SHA-256 of the reviewed Skills package tarball").option("--skills-package-tar-bytes <bytes>", "Byte length of the reviewed Skills package tarball").option("--skills-package-manifest-sha256 <sha256>", "SHA-256 of the reviewed Skills package manifest").option("--skills-executable-sha256 <sha256>", "SHA-256 of the reviewed running Skills executable").option("--json", "Output the reconciliation receipt", false).description("Verify an interrupted Codex native trust write and resolve its private journal").action(async (options) => {
108656
108741
  try {
108657
108742
  if (options.agent !== "codex")
108658
108743
  throw new Error("Native trust reconciliation currently supports --agent codex only");
108659
- const result2 = await reconcileCodexNativeHooks({ journal: options.journal, codexCommand: options.codexCommand });
108744
+ const proofFields = [options.skillsPackageTar, options.skillsPackageTarSha256, options.skillsPackageTarBytes, options.skillsPackageManifestSha256, options.skillsExecutableSha256];
108745
+ if (proofFields.some((value) => value !== undefined) && proofFields.some((value) => value === undefined))
108746
+ throw new Error("Release provenance requires package tar path, tar hash/bytes, manifest hash, and executable hash together");
108747
+ const releaseProof = proofFields.every((value) => value !== undefined) ? { packageTarPath: options.skillsPackageTar, packageTarSha256: options.skillsPackageTarSha256, packageTarBytes: Number(options.skillsPackageTarBytes), manifestSha256: options.skillsPackageManifestSha256, executableSha256: options.skillsExecutableSha256 } : undefined;
108748
+ const result2 = await reconcileCodexNativeHooks({ journal: options.journal, codexCommand: options.codexCommand, supersedeBinding: options.supersedeBinding, releaseProof });
108660
108749
  if (options.json)
108661
108750
  await writeCliOutput(JSON.stringify(result2));
108662
108751
  else
@@ -108673,7 +108762,7 @@ function registerAgentIntegration(parent) {
108673
108762
  try {
108674
108763
  if (agents(options.agent).length !== 1)
108675
108764
  throw new Error("A hook invocation requires one agent");
108676
- const inputText = readFileSync37(0, "utf8");
108765
+ const inputText = readFileSync38(0, "utf8");
108677
108766
  if (inputText.length > 1024 * 1024)
108678
108767
  throw new Error("Hook input is too large");
108679
108768
  let input3 = JSON.parse(inputText);
@@ -108686,12 +108775,12 @@ function registerAgentIntegration(parent) {
108686
108775
  input3.hook_event_name = event;
108687
108776
  const projects = [process.cwd()];
108688
108777
  if (input3.cwd !== undefined) {
108689
- if (typeof input3.cwd !== "string" || !isAbsolute25(input3.cwd) || input3.cwd.includes("\x00"))
108778
+ if (typeof input3.cwd !== "string" || !isAbsolute26(input3.cwd) || input3.cwd.includes("\x00"))
108690
108779
  throw new Error("Invalid native hook working directory");
108691
108780
  projects.push(input3.cwd);
108692
108781
  }
108693
108782
  if (options.agent === "cursor") {
108694
- if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !isAbsolute25(path) || path.includes("\x00")))
108783
+ if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !isAbsolute26(path) || path.includes("\x00")))
108695
108784
  throw new Error("Invalid Cursor workspace roots");
108696
108785
  projects.push(...input3.workspace_roots);
108697
108786
  input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
@@ -108769,7 +108858,7 @@ function registerAgentIntegration(parent) {
108769
108858
  const migrate = parent.command("migrate").description("Preserve and retire native agent skill copies");
108770
108859
  migrate.command("native").option("--project <directory>", "Also inventory a project and its ancestors (the current directory and its ancestors are always included)").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--include-vendor", "Retire vendor SKILL.md discovery files while preserving plugin scripts and assets", false).option("--target-manifest <file>", "Reviewed exact native migration target manifest").option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action(async (options) => {
108771
108860
  try {
108772
- const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync37(options.discoveryInputs, "utf8")) : undefined;
108861
+ const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync38(options.discoveryInputs, "utf8")) : undefined;
108773
108862
  if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
108774
108863
  throw new Error("--target-manifest cannot be combined with broad native migration selectors");
108775
108864
  const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
@@ -109074,11 +109163,11 @@ import {
109074
109163
  copyFileSync as copyFileSync2,
109075
109164
  mkdirSync as mkdirSync26,
109076
109165
  readdirSync as readdirSync21,
109077
- readFileSync as readFileSync38,
109078
- statSync as statSync21,
109166
+ readFileSync as readFileSync39,
109167
+ statSync as statSync22,
109079
109168
  writeFileSync as writeFileSync26
109080
109169
  } from "fs";
109081
- import { dirname as dirname30, join as join57, resolve as resolve23, sep as sep10 } from "path";
109170
+ import { dirname as dirname31, join as join57, resolve as resolve23, sep as sep10 } from "path";
109082
109171
  function fail5(code2, message, detail = []) {
109083
109172
  throw new StationSnapshotError(code2, message, detail);
109084
109173
  }
@@ -109090,7 +109179,7 @@ function readSnapshotManifest(repoRoot, stationId) {
109090
109179
  const manifestPath = join57(snapshotRoot, "sync-manifest.json");
109091
109180
  let manifest;
109092
109181
  try {
109093
- manifest = JSON.parse(readFileSync38(manifestPath, "utf8"));
109182
+ manifest = JSON.parse(readFileSync39(manifestPath, "utf8"));
109094
109183
  } catch (error61) {
109095
109184
  fail5("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
109096
109185
  }
@@ -109172,7 +109261,7 @@ function planStationHydration(stationId, repoRoot) {
109172
109261
  });
109173
109262
  continue;
109174
109263
  }
109175
- const info = statSync21(entry.fullPath);
109264
+ const info = statSync22(entry.fullPath);
109176
109265
  const manifestHash = manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null;
109177
109266
  let verified = false;
109178
109267
  if (manifestHash !== null) {
@@ -109226,7 +109315,7 @@ function planStationHydration(stationId, repoRoot) {
109226
109315
  for (const copy of copies) {
109227
109316
  let isStub = false;
109228
109317
  try {
109229
- isStub = isPointerSkillMd(readFileSync38(copy.fullPath, "utf8"));
109318
+ isStub = isPointerSkillMd(readFileSync39(copy.fullPath, "utf8"));
109230
109319
  } catch {
109231
109320
  isStub = false;
109232
109321
  }
@@ -109331,7 +109420,7 @@ function writeStationHydration(options) {
109331
109420
  }
109332
109421
  let written = 0;
109333
109422
  for (const entry of toWrite) {
109334
- mkdirSync26(dirname30(entry.destination), { recursive: true });
109423
+ mkdirSync26(dirname31(entry.destination), { recursive: true });
109335
109424
  copyFileSync2(entry.fullPath, entry.destination);
109336
109425
  written += 1;
109337
109426
  }
@@ -109352,8 +109441,8 @@ function writeStationHydration(options) {
109352
109441
  },
109353
109442
  skills: resultSkills
109354
109443
  };
109355
- const hydrationManifestPath = join57(dirname30(cacheRoot), `hydration-${options.stationId}.json`);
109356
- mkdirSync26(dirname30(hydrationManifestPath), { recursive: true });
109444
+ const hydrationManifestPath = join57(dirname31(cacheRoot), `hydration-${options.stationId}.json`);
109445
+ mkdirSync26(dirname31(hydrationManifestPath), { recursive: true });
109357
109446
  writeFileSync26(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
109358
109447
  `);
109359
109448
  return {
@@ -109430,9 +109519,9 @@ var init_hydrate = __esm(() => {
109430
109519
 
109431
109520
  // src/lib/prepare-skill.ts
109432
109521
  import { createHash as createHash28, randomUUID as randomUUID11 } from "crypto";
109433
- import { chmodSync as chmodSync3, existsSync as existsSync34, lstatSync as lstatSync29, mkdirSync as mkdirSync27, mkdtempSync as mkdtempSync6, readFileSync as readFileSync39, readdirSync as readdirSync22, renameSync as renameSync12, rmSync as rmSync9, writeFileSync as writeFileSync27 } from "fs";
109522
+ import { chmodSync as chmodSync3, existsSync as existsSync34, lstatSync as lstatSync29, mkdirSync as mkdirSync27, mkdtempSync as mkdtempSync6, readFileSync as readFileSync40, readdirSync as readdirSync22, renameSync as renameSync12, rmSync as rmSync9, writeFileSync as writeFileSync27 } from "fs";
109434
109523
  import { tmpdir as tmpdir2 } from "os";
109435
- import { dirname as dirname31, isAbsolute as isAbsolute26, join as join58, relative as relative9, resolve as resolve24, sep as sep11 } from "path";
109524
+ import { dirname as dirname32, isAbsolute as isAbsolute27, join as join58, relative as relative9, resolve as resolve24, sep as sep11 } from "path";
109436
109525
  function prepareSkill(name, options) {
109437
109526
  const normalized = normalizePortableSkillName(name);
109438
109527
  if (!new RegExp(SEMVER_PATTERN).test(options.version))
@@ -109477,7 +109566,7 @@ function prepareSkill(name, options) {
109477
109566
  mkdirSync27(join58(staging, directory2), { recursive: true });
109478
109567
  for (const file2 of before2.files) {
109479
109568
  const destination = join58(staging, file2.path);
109480
- mkdirSync27(dirname31(destination), { recursive: true });
109569
+ mkdirSync27(dirname32(destination), { recursive: true });
109481
109570
  writeFileSync27(destination, file2.bytes, { mode: file2.mode });
109482
109571
  }
109483
109572
  const stagedManifest = join58(staging, "skill.json");
@@ -109538,7 +109627,7 @@ function validateRuntimeEntrypoint(candidate2, root2) {
109538
109627
  if (typeof entry !== "string")
109539
109628
  return;
109540
109629
  const target = resolve24(root2, entry), rel = relative9(root2, target);
109541
- if (isAbsolute26(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep11}`) || isAbsolute26(rel) || !existsSync34(target) || !lstatSync29(target).isFile()) {
109630
+ if (isAbsolute27(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep11}`) || isAbsolute27(rel) || !existsSync34(target) || !lstatSync29(target).isFile()) {
109542
109631
  throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
109543
109632
  }
109544
109633
  }
@@ -109649,7 +109738,7 @@ function snapshot2(root2) {
109649
109738
  bytes += stat3.size;
109650
109739
  if (bytes > 50000000)
109651
109740
  throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
109652
- const content = readFileSync39(absolute);
109741
+ const content = readFileSync40(absolute);
109653
109742
  if (content.length !== stat3.size)
109654
109743
  throw new Error("Skill changed during preparation.");
109655
109744
  files.push({ path, bytes: content, mode: stat3.mode & 511 });
@@ -110051,7 +110140,7 @@ var init_schedule = __esm(() => {
110051
110140
 
110052
110141
  // src/lib/registry-sync.ts
110053
110142
  import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync28 } from "fs";
110054
- import { dirname as dirname32, relative as relative10 } from "path";
110143
+ import { dirname as dirname33, relative as relative10 } from "path";
110055
110144
  function createRegistrySyncArtifact(options = {}) {
110056
110145
  const profile = options.profile ?? "all";
110057
110146
  const includeDocs = options.includeDocs ?? true;
@@ -110108,7 +110197,7 @@ function createRegistrySyncArtifact(options = {}) {
110108
110197
  };
110109
110198
  }
110110
110199
  function writeRegistrySyncArtifact(path, artifact) {
110111
- mkdirSync28(dirname32(path), { recursive: true });
110200
+ mkdirSync28(dirname33(path), { recursive: true });
110112
110201
  writeFileSync28(path, `${JSON.stringify(artifact, null, 2)}
110113
110202
  `);
110114
110203
  }
@@ -110329,8 +110418,8 @@ __export(exports_publish, {
110329
110418
  buildVersionManifest: () => buildVersionManifest,
110330
110419
  PushSkillError: () => PushSkillError
110331
110420
  });
110332
- import { execFileSync } from "child_process";
110333
- import { existsSync as existsSync35, readFileSync as readFileSync40 } from "fs";
110421
+ import { execFileSync as execFileSync2 } from "child_process";
110422
+ import { existsSync as existsSync35, readFileSync as readFileSync41 } from "fs";
110334
110423
  import { hostname as hostname4 } from "os";
110335
110424
  import { join as join59 } from "path";
110336
110425
  function registerPublish(parent) {
@@ -110385,7 +110474,7 @@ async function pushSkill(name, options = {}) {
110385
110474
  await verifyPackedCanonicalHash(skill.name, packed);
110386
110475
  const versionManifest = buildVersionManifest(skill.path, packed);
110387
110476
  const skillMdPath = join59(skill.path, "SKILL.md");
110388
- const skillMd = existsSync35(skillMdPath) ? readFileSync40(skillMdPath, "utf-8") : undefined;
110477
+ const skillMd = existsSync35(skillMdPath) ? readFileSync41(skillMdPath, "utf-8") : undefined;
110389
110478
  const base3 = {
110390
110479
  slug: skill.name,
110391
110480
  path: skill.path,
@@ -110542,7 +110631,7 @@ function sanitizeGitRemote(url2) {
110542
110631
  }
110543
110632
  function gitValue(dir, args2) {
110544
110633
  try {
110545
- const out = execFileSync("git", ["-C", dir, ...args2], { stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
110634
+ const out = execFileSync2("git", ["-C", dir, ...args2], { stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
110546
110635
  return out || null;
110547
110636
  } catch {
110548
110637
  return null;
@@ -112006,11 +112095,11 @@ var init_storage2 = __esm(() => {
112006
112095
  });
112007
112096
 
112008
112097
  // src/lib/registry-reconcile.ts
112009
- import { existsSync as existsSync36, readFileSync as readFileSync41, statSync as statSync22, writeFileSync as writeFileSync29 } from "fs";
112098
+ import { existsSync as existsSync36, readFileSync as readFileSync42, statSync as statSync23, writeFileSync as writeFileSync29 } from "fs";
112010
112099
  import { join as join60 } from "path";
112011
112100
  function isDirectory2(path) {
112012
112101
  try {
112013
- return statSync22(path).isDirectory();
112102
+ return statSync23(path).isDirectory();
112014
112103
  } catch {
112015
112104
  return false;
112016
112105
  }
@@ -112026,7 +112115,7 @@ function readBaseline(skillDir) {
112026
112115
  if (!existsSync36(markerPath))
112027
112116
  return;
112028
112117
  try {
112029
- const marker = JSON.parse(readFileSync41(markerPath, "utf-8"));
112118
+ const marker = JSON.parse(readFileSync42(markerPath, "utf-8"));
112030
112119
  if (!isSkillsOwnershipMarker(marker))
112031
112120
  return;
112032
112121
  return {
@@ -112042,7 +112131,7 @@ function readCursor(root2) {
112042
112131
  if (!existsSync36(path))
112043
112132
  return { runCount: 0 };
112044
112133
  try {
112045
- const cursor2 = JSON.parse(readFileSync41(path, "utf-8"));
112134
+ const cursor2 = JSON.parse(readFileSync42(path, "utf-8"));
112046
112135
  return { runCount: typeof cursor2.runCount === "number" ? cursor2.runCount : 0 };
112047
112136
  } catch {
112048
112137
  return { runCount: 0 };
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.6",
20022
+ version: "0.9.8",
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.9.6",
10
+ version: "0.9.8",
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.6",
24125
+ version: "0.9.8",
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.6",
24128
+ version: "0.9.8",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
package/dist/index.js CHANGED
@@ -11448,7 +11448,7 @@ import { isAbsolute as isAbsolute10, join as join20, resolve as resolve10 } from
11448
11448
  // package.json
11449
11449
  var package_default = {
11450
11450
  name: "@hasna/skills",
11451
- version: "0.9.6",
11451
+ version: "0.9.8",
11452
11452
  description: "Skills library for AI coding agents",
11453
11453
  type: "module",
11454
11454
  bin: {
@@ -8,12 +8,21 @@ export interface CodexNativeHookTrustOptions {
8
8
  reviewedPlanDigest?: string;
9
9
  reviewedSkillsCli?: ReviewedSkillsCli;
10
10
  }
11
+ export interface SkillsCliReleaseProof {
12
+ packageTarPath: string;
13
+ packageTarSha256: string;
14
+ packageTarBytes: number;
15
+ manifestSha256: string;
16
+ executableSha256: string;
17
+ }
11
18
  export interface CodexNativeHookReconcileOptions {
12
19
  home?: string;
13
20
  dataDir?: string;
14
21
  codexCommand?: string;
15
22
  journal: string;
16
23
  reviewedSkillsCli?: ReviewedSkillsCli;
24
+ releaseProof?: SkillsCliReleaseProof;
25
+ supersedeBinding?: boolean;
17
26
  }
18
27
  type Connect = typeof connectCodexHookRpc;
19
28
  /** Resolve an interrupted native write only after the native consumer and every
@@ -24,21 +33,27 @@ export declare function reconcileCodexNativeHooks(options: CodexNativeHookReconc
24
33
  version: number;
25
34
  status: string;
26
35
  reconciled: boolean;
36
+ supersededBinding: boolean;
27
37
  automaticRollback: boolean;
28
38
  journal: string;
29
39
  planDigest: any;
30
40
  nativeVersion: string;
31
41
  nativeStateVerified: boolean;
42
+ nativeTrustPending: boolean;
32
43
  nativeExecutionVerified: boolean;
33
44
  configSha256: string;
34
45
  hooksSha256: string;
35
46
  policySha256: string;
47
+ policyBeforeSha256: string;
48
+ policyCurrentSha256: string;
49
+ previousSkillsCli: any;
36
50
  skillsCli: {
37
51
  path: string;
38
52
  version: string;
39
53
  sha256: string;
40
54
  manifestSha256: string;
41
55
  };
56
+ releaseProof: SkillsCliReleaseProof | undefined;
42
57
  nativeInventoryScope: string;
43
58
  unrelatedNativeHooksPreserved: boolean;
44
59
  unrelatedSettingsAndCommentsPreserved: boolean;
@@ -1,3 +1,9 @@
1
1
  /** Conservative admission and text witness, never a TOML writer. Native Codex
2
2
  * performs the versioned write; unrecognized inline/dotted trust shapes refuse. */
3
- export declare function codexTrustTextWitness(text: string, keys: string[]): string;
3
+ export declare function codexTrustTextWitness(text: string, keys: string[], canonicalize?: boolean): string;
4
+ /** Reconciliation witness: retain table/array order while tolerating only
5
+ * blank-line and line-ending formatting emitted by the native writer. */
6
+ export declare function codexTrustReconcileWitness(text: string, keys: string[]): string;
7
+ /** Parsed unmanaged TOML semantics. Arrays retain order; managed trust entries
8
+ * are excluded by exact journal key and all other keys/values remain bound. */
9
+ export declare function codexTrustUnmanagedSemantic(text: string, keys: string[]): string;
package/dist/sdk/index.js CHANGED
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
32373
32373
  // package.json
32374
32374
  var package_default = {
32375
32375
  name: "@hasna/skills",
32376
- version: "0.9.6",
32376
+ version: "0.9.8",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {