@hasna/hooks 0.12.3 → 0.12.4

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/hooks-mcp.js CHANGED
@@ -15,7 +15,6 @@ var __export = (target, all) => {
15
15
  });
16
16
  };
17
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
- var __require = import.meta.require;
19
18
 
20
19
  // src/lib/app-home.ts
21
20
  import { existsSync } from "fs";
@@ -1642,14 +1641,14 @@ function assertUsableCredential(appName, source, value) {
1642
1641
  return;
1643
1642
  throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
1644
1643
  }
1645
- function sealCredential(fields) {
1646
- const { apiKey } = fields;
1644
+ function sealCredential(fields2) {
1645
+ const { apiKey } = fields2;
1647
1646
  const visible = {
1648
- tier: fields.tier,
1649
- source: fields.source,
1650
- deliberate: fields.deliberate,
1651
- diskCandidates: Object.freeze([...fields.diskCandidates]),
1652
- warning: fields.warning
1647
+ tier: fields2.tier,
1648
+ source: fields2.source,
1649
+ deliberate: fields2.deliberate,
1650
+ diskCandidates: Object.freeze([...fields2.diskCandidates]),
1651
+ warning: fields2.warning
1653
1652
  };
1654
1653
  const sealed = { ...visible };
1655
1654
  Object.defineProperty(sealed, "apiKey", {
@@ -1658,9 +1657,9 @@ function sealCredential(fields) {
1658
1657
  writable: false,
1659
1658
  configurable: false
1660
1659
  });
1661
- if (fields.pointerVaultKey !== undefined) {
1660
+ if (fields2.pointerVaultKey !== undefined) {
1662
1661
  Object.defineProperty(sealed, "pointerVaultKey", {
1663
- value: fields.pointerVaultKey,
1662
+ value: fields2.pointerVaultKey,
1664
1663
  enumerable: false,
1665
1664
  writable: false,
1666
1665
  configurable: false
@@ -2258,8 +2257,8 @@ var init_local_opt_in = __esm(() => {
2258
2257
  });
2259
2258
 
2260
2259
  // src/lib/hook-env.ts
2261
- import { dirname as dirname11, isAbsolute as isAbsolute15 } from "path";
2262
- import { existsSync as existsSync13, realpathSync as realpathSync7, statSync } from "fs";
2260
+ import { dirname as dirname12, isAbsolute as isAbsolute9 } from "path";
2261
+ import { existsSync as existsSync14, realpathSync as realpathSync10, statSync } from "fs";
2263
2262
  import { tmpdir } from "os";
2264
2263
  function isDeniedEnvName(name) {
2265
2264
  const upper = name.toUpperCase();
@@ -2278,7 +2277,7 @@ function isBashFunctionEnvName(name) {
2278
2277
  return name.startsWith("BASH_FUNC_");
2279
2278
  }
2280
2279
  function isUnsafePathEntry(entry, home) {
2281
- if (entry === "" || !isAbsolute15(entry))
2280
+ if (entry === "" || !isAbsolute9(entry))
2282
2281
  return true;
2283
2282
  if (home && (entry === home || entry.startsWith(home + "/")))
2284
2283
  return true;
@@ -2286,16 +2285,16 @@ function isUnsafePathEntry(entry, home) {
2286
2285
  return true;
2287
2286
  if (entry === "/var/tmp" || entry.startsWith("/var/tmp/"))
2288
2287
  return true;
2289
- const canonical4 = (path) => {
2288
+ const canonical6 = (path) => {
2290
2289
  try {
2291
- return realpathSync7(path);
2290
+ return realpathSync10(path);
2292
2291
  } catch {
2293
2292
  return path;
2294
2293
  }
2295
2294
  };
2296
- const resolved = canonical4(entry);
2295
+ const resolved = canonical6(entry);
2297
2296
  for (const root of [home, tmpdir(), "/tmp", "/var/tmp"].filter((value) => Boolean(value))) {
2298
- const blocked = canonical4(root);
2297
+ const blocked = canonical6(root);
2299
2298
  if (resolved === blocked || resolved.startsWith(blocked + "/"))
2300
2299
  return true;
2301
2300
  }
@@ -2307,9 +2306,9 @@ function isUnsafePathEntry(entry, home) {
2307
2306
  }
2308
2307
  function sanitizeHookPath(pathValue, home) {
2309
2308
  const baseline = PATH_BASELINE.slice();
2310
- if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
2309
+ if (process.platform === "darwin" && existsSync14("/opt/homebrew/bin"))
2311
2310
  baseline.push("/opt/homebrew/bin");
2312
- const bunDir = dirname11(process.execPath);
2311
+ const bunDir = dirname12(process.execPath);
2313
2312
  const seen = new Set;
2314
2313
  const out = [];
2315
2314
  for (const dir of baseline) {
@@ -2434,9 +2433,9 @@ __export(exports_run, {
2434
2433
  });
2435
2434
  import { randomBytes as randomBytes2 } from "crypto";
2436
2435
  import { rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
2437
- import { basename as basename5, dirname as dirname17, isAbsolute as isAbsolute17, join as join12 } from "path";
2436
+ import { basename as basename6, dirname as dirname18, isAbsolute as isAbsolute17, join as join13 } from "path";
2438
2437
  function extensionOf(scriptPath) {
2439
- const base = basename5(scriptPath);
2438
+ const base = basename6(scriptPath);
2440
2439
  const dot = base.lastIndexOf(".");
2441
2440
  return dot > 0 ? base.slice(dot + 1) : "ts";
2442
2441
  }
@@ -2448,9 +2447,9 @@ function shebangInterpreter(firstLine) {
2448
2447
  return null;
2449
2448
  const parts = rest.split(/\s+/);
2450
2449
  if (parts[0] === "env" || parts[0] === "/usr/bin/env") {
2451
- return parts[1] && parts[1] !== "-S" ? basename5(parts[1]) : parts[2] ? basename5(parts[2]) : null;
2450
+ return parts[1] && parts[1] !== "-S" ? basename6(parts[1]) : parts[2] ? basename6(parts[2]) : null;
2452
2451
  }
2453
- return basename5(parts[0]);
2452
+ return basename6(parts[0]);
2454
2453
  }
2455
2454
  function interpreterFor(name, scriptPath, content) {
2456
2455
  const ext = extensionOf(scriptPath);
@@ -2503,9 +2502,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
2503
2502
  return chunks.join("");
2504
2503
  }
2505
2504
  async function executeVerifiedScript(options) {
2506
- const scriptDir = dirname17(options.scriptPath);
2505
+ const scriptDir = dirname18(options.scriptPath);
2507
2506
  const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
2508
- const tempPath = join12(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
2507
+ const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
2509
2508
  let proc = null;
2510
2509
  let timedOut = false;
2511
2510
  const timeoutTimer = options.timeout ? setTimeout(() => {
@@ -2664,29 +2663,29 @@ function resolveEventType(inputEvent, fallbackEvent) {
2664
2663
  return fromInput;
2665
2664
  return normalizeEventType(fallbackEvent ?? null);
2666
2665
  }
2667
- function recordHookRun(record3) {
2668
- const eventType = record3.eventType ? normalizeEventType(record3.eventType) : null;
2666
+ function recordHookRun(record4) {
2667
+ const eventType = record4.eventType ? normalizeEventType(record4.eventType) : null;
2669
2668
  if (!eventType) {
2670
- process.stderr.write(`[hooks db-writer] skipped event for '${record3.hookName}': unsupported event type '${String(record3.eventType)}'
2669
+ process.stderr.write(`[hooks db-writer] skipped event for '${record4.hookName}': unsupported event type '${String(record4.eventType)}'
2671
2670
  `);
2672
2671
  return;
2673
2672
  }
2674
2673
  const metadata = {
2675
- ...record3.metadata ?? {},
2676
- version: record3.version ?? null,
2677
- sha256: record3.sha256 ?? null,
2678
- exit_code: record3.exitCode
2674
+ ...record4.metadata ?? {},
2675
+ version: record4.version ?? null,
2676
+ sha256: record4.sha256 ?? null,
2677
+ exit_code: record4.exitCode
2679
2678
  };
2680
2679
  writeHookEvent({
2681
- session_id: record3.sessionId ?? "cli",
2682
- hook_name: record3.hookName,
2680
+ session_id: record4.sessionId ?? "cli",
2681
+ hook_name: record4.hookName,
2683
2682
  event_type: eventType,
2684
- tool_name: record3.toolName ?? null,
2685
- tool_input: record3.toolInput !== undefined ? JSON.stringify(record3.toolInput) : null,
2686
- result: record3.result ?? null,
2687
- error: record3.error ?? null,
2688
- duration_ms: record3.durationMs,
2689
- project_dir: record3.projectDir ?? null,
2683
+ tool_name: record4.toolName ?? null,
2684
+ tool_input: record4.toolInput !== undefined ? JSON.stringify(record4.toolInput) : null,
2685
+ result: record4.result ?? null,
2686
+ error: record4.error ?? null,
2687
+ duration_ms: record4.durationMs,
2688
+ project_dir: record4.projectDir ?? null,
2690
2689
  metadata: JSON.stringify(metadata)
2691
2690
  });
2692
2691
  }
@@ -2713,15 +2712,15 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
2713
2712
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2714
2713
  import { z as z3 } from "zod";
2715
2714
  init_registry();
2716
- import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
2717
- import { join as join13, dirname as dirname19 } from "path";
2715
+ import { existsSync as existsSync15, readFileSync as readFileSync9 } from "fs";
2716
+ import { join as join18, dirname as dirname20 } from "path";
2718
2717
  import { fileURLToPath as fileURLToPath3 } from "url";
2719
2718
 
2720
2719
  // src/lib/installer.ts
2721
2720
  init_registry();
2722
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, realpathSync as realpathSync5, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
2723
- import { join as join10, dirname as dirname7, basename as basename3, resolve as resolve7 } from "path";
2724
- import { homedir as homedir6 } from "os";
2721
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, realpathSync as realpathSync8, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
2722
+ import { join as join11, dirname as dirname7, basename as basename3, resolve as resolve16 } from "path";
2723
+ import { homedir as homedir7 } from "os";
2725
2724
  import { fileURLToPath as fileURLToPath2 } from "url";
2726
2725
 
2727
2726
  // src/lib/readiness-process.ts
@@ -3332,16 +3331,16 @@ function assertSettingsPreimage(raw, expected) {
3332
3331
  throw new Error("Hook settings changed since planning; no settings were written.");
3333
3332
  }
3334
3333
  }
3335
- function writeCodexSettings(path, settings, expectedSha256) {
3334
+ function writeCodexSettings(path, settings, expectedSha256, beforeWrite) {
3336
3335
  const snapshot = snapshots.get(settings);
3337
3336
  if (!snapshot || snapshot.path !== path)
3338
3337
  throw new Error("Codex settings require a complete current read before writing.");
3339
3338
  assertSettingsPreimage(snapshot.raw, expectedSha256);
3340
- writeHookSettingsPreimage(path, settings, snapshot.raw);
3339
+ writeHookSettingsPreimage(path, settings, snapshot.raw, beforeWrite);
3341
3340
  snapshots.set(settings, { path, raw: JSON.stringify(settings, null, 2) + `
3342
3341
  ` });
3343
3342
  }
3344
- function writeHookSettingsPreimage(path, settings, before) {
3343
+ function writeHookSettingsPreimage(path, settings, before, beforeWrite) {
3345
3344
  mkdirSync4(dirname5(path), { recursive: true, mode: 448 });
3346
3345
  const lock = `${path}.hooks-write.lock`;
3347
3346
  let fd;
@@ -3359,6 +3358,7 @@ function writeHookSettingsPreimage(path, settings, before) {
3359
3358
  writeFileSync3(temp, next, { flag: "wx", mode: 384 });
3360
3359
  if (current(path) !== before)
3361
3360
  throw new Error("Hook settings changed since reading; re-read before retrying.");
3361
+ beforeWrite?.();
3362
3362
  renameSync2(temp, path);
3363
3363
  } finally {
3364
3364
  if (existsSync9(temp))
@@ -3370,8 +3370,11 @@ function writeHookSettingsPreimage(path, settings, before) {
3370
3370
 
3371
3371
  // src/lib/claude-skills-coordination.ts
3372
3372
  import { createHash as createHash6 } from "crypto";
3373
+ import { existsSync as existsSync11, lstatSync as lstatSync7 } from "fs";
3374
+ import { homedir as homedir6 } from "os";
3375
+ import { join as join10, resolve as resolve7 } from "path";
3373
3376
 
3374
- // ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
3377
+ // ../../node_modules/.bun/@hasna+skills@0.10.28+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
3375
3378
  import { existsSync as existsSync10 } from "fs";
3376
3379
  import { homedir as homedir5 } from "os";
3377
3380
  import { join as join9, resolve as resolve3 } from "path";
@@ -3381,10 +3384,10 @@ import { createHash as createHash5 } from "crypto";
3381
3384
  import { existsSync as existsSync52, readdirSync as readdirSync3, readFileSync as readFileSync42, statSync as statSync3 } from "fs";
3382
3385
  import { join as join52, sep as sep2 } from "path";
3383
3386
  import { basename as basename2, isAbsolute as isAbsolute22, resolve as resolve22 } from "path";
3384
- import { createHash as createHash16 } from "crypto";
3385
- import { lstatSync as lstatSync20, readFileSync as readFileSync16, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
3386
- import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute14, join as join26, resolve as resolve14, sep as sep5 } from "path";
3387
- import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
3387
+ import { createHash as createHash18 } from "crypto";
3388
+ import { lstatSync as lstatSync21, readFileSync as readFileSync17, readdirSync as readdirSync10, realpathSync as realpathSync7 } from "fs";
3389
+ import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as join27, resolve as resolve15, sep as sep6 } from "path";
3390
+ import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
3388
3391
  import { dirname as dirname32, isAbsolute as isAbsolute32, join as join72, resolve as resolve32 } from "path";
3389
3392
  import { lstatSync as lstatSync32, readFileSync as readFileSync62, opendirSync } from "fs";
3390
3393
  import { createHash as createHash22 } from "crypto";
@@ -3396,34 +3399,42 @@ import { dirname as dirname52, isAbsolute as isAbsolute5, resolve as resolve5 }
3396
3399
  import { createHash as createHash42 } from "crypto";
3397
3400
  import { lstatSync as lstatSync6, readlinkSync } from "fs";
3398
3401
  import { dirname as dirname6, isAbsolute as isAbsolute6, join as join92, parse, resolve as resolve6, sep as sep22 } from "path";
3399
- import { closeSync as closeSync12, constants as constants9, fstatSync as fstatSync12, lstatSync as lstatSync17, openSync as openSync12, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
3400
- import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute12, join as join25, resolve as resolve12 } from "path";
3401
- import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from "path";
3402
+ import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as lstatSync18, openSync as openSync10, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
3403
+ import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as join26, resolve as resolve13 } from "path";
3404
+ import { isAbsolute as isAbsolute13, join as join25, resolve as resolve12 } from "path";
3402
3405
  import { createRequire } from "module";
3403
- import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
3404
- import { existsSync as existsSync82, lstatSync as lstatSync14, statSync as statSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync7, opendirSync as opendirSync3, readlinkSync as readlinkSync2, realpathSync as realpathSync32, renameSync as renameSync22, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync10, closeSync as closeSync10, fsyncSync as fsyncSync2, fstatSync as fstatSync10, readSync as readSync6, constants as constants7, linkSync } from "fs";
3405
- import { dirname as dirname10, join as join16, resolve as resolve9, relative as relative4, isAbsolute as isAbsolute9, sep as sep4 } from "path";
3406
- import { homedir as homedir22 } from "os";
3407
- import { closeSync as closeSync8, constants as constants5, fstatSync as fstatSync8, lstatSync as lstatSync11, openSync as openSync8, readSync as readSync4 } from "fs";
3408
- import { join as join14 } from "path";
3409
3406
  import { createHash as createHash8 } from "crypto";
3410
- import { lstatSync as lstatSync12, readFileSync as readFileSync10, readdirSync as readdirSync6 } from "fs";
3411
- import { join as join15 } from "path";
3412
- import { createHash as createHash9 } from "crypto";
3413
- import { closeSync as closeSync9, constants as constants6, fstatSync as fstatSync9, lstatSync as lstatSync13, openSync as openSync9, readSync as readSync5 } from "fs";
3407
+ import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync6, lstatSync as lstatSync11, openSync as openSync6, readSync as readSync4 } from "fs";
3414
3408
  import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute8, resolve as resolve8 } from "path";
3409
+ import { basename as basename5, dirname as dirname10, join as join14, relative as relative4, resolve as resolve9, sep as sep4 } from "path";
3410
+ import { lstatSync as lstatSync12, readdirSync as readdirSync6, readlinkSync as readlinkSync3, realpathSync as realpathSync5 } from "fs";
3411
+ import { realpathSync as realpathSync4 } from "fs";
3412
+ import { isAbsolute as isAbsolute10, normalize as normalize2 } from "path";
3413
+ import { createHash as createHash9 } from "crypto";
3414
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
3415
+ import { createHash as createHash10 } from "crypto";
3416
+ import { createHash as createHash12, randomUUID as randomUUID2 } from "crypto";
3417
+ import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
3418
+ import { existsSync as existsSync82, lstatSync as lstatSync15, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync6, renameSync as renameSync22, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync8, closeSync as closeSync8, fsyncSync as fsyncSync2, fstatSync as fstatSync8, readSync as readSync6, constants as constants7, linkSync } from "fs";
3419
+ import { dirname as dirname11, join as join17, resolve as resolve10, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
3420
+ import { homedir as homedir22 } from "os";
3421
+ import { closeSync as closeSync7, constants as constants6, fstatSync as fstatSync7, lstatSync as lstatSync13, openSync as openSync7, readSync as readSync5 } from "fs";
3422
+ import { join as join15 } from "path";
3415
3423
  import { createHash as createHash11 } from "crypto";
3416
- import { constants as constants8, closeSync as closeSync11, fchmodSync, fstatSync as fstatSync11, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as lstatSync16, mkdirSync as mkdirSync8, openSync as openSync11, opendirSync as opendirSync4, readSync as readSync7, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
3417
- import { dirname as dirname12, isAbsolute as isAbsolute10, join as join21, parse as parse5, resolve as resolve10 } from "path";
3424
+ import { lstatSync as lstatSync14, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "fs";
3425
+ import { join as join16 } from "path";
3418
3426
  import { createHash as createHash13 } from "crypto";
3419
- import { closeSync as closeSync13, constants as constants10, fstatSync as fstatSync13, lstatSync as lstatSync18, openSync as openSync13, readSync as readSync9 } from "fs";
3420
- import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute13, resolve as resolve13 } from "path";
3427
+ import { constants as constants8, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as lstatSync17, mkdirSync as mkdirSync8, openSync as openSync9, opendirSync as opendirSync4, readSync as readSync7, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
3428
+ import { dirname as dirname13, isAbsolute as isAbsolute12, join as join23, parse as parse5, resolve as resolve11 } from "path";
3421
3429
  import { createHash as createHash15 } from "crypto";
3422
- import { closeSync as closeSync14, constants as constants11, fstatSync as fstatSync14, fsyncSync as fsyncSync4, lstatSync as lstatSync19, openSync as openSync14, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
3423
- import { createHash as createHash14 } from "crypto";
3424
- import { dirname as dirname15 } from "path";
3425
- import { isAbsolute as isAbsolute16 } from "path";
3426
- import { createHash as createHash20 } from "crypto";
3430
+ import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync19, openSync as openSync11, readSync as readSync9 } from "fs";
3431
+ import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve14 } from "path";
3432
+ import { createHash as createHash17 } from "crypto";
3433
+ import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync20, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
3434
+ import { createHash as createHash16 } from "crypto";
3435
+ import { dirname as dirname16 } from "path";
3436
+ import { isAbsolute as isAbsolute18 } from "path";
3437
+ import { createHash as createHash222 } from "crypto";
3427
3438
  var __defProp2 = Object.defineProperty;
3428
3439
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
3429
3440
  var __returnValue2 = (v) => v;
@@ -3439,7 +3450,7 @@ var __export2 = (target, all) => {
3439
3450
  set: __exportSetter2.bind(all, name)
3440
3451
  });
3441
3452
  };
3442
- var __require2 = import.meta.require;
3453
+ var __require = import.meta.require;
3443
3454
  var require_identity = __commonJS((exports) => {
3444
3455
  var ALIAS = Symbol.for("yaml.alias");
3445
3456
  var DOC = Symbol.for("yaml.document");
@@ -4977,7 +4988,7 @@ ${ctx.indent}`;
4977
4988
  exports.stringifyPair = stringifyPair;
4978
4989
  });
4979
4990
  var require_log = __commonJS((exports) => {
4980
- var node_process = __require2("process");
4991
+ var node_process = __require("process");
4981
4992
  function debug(logLevel, ...messages) {
4982
4993
  if (logLevel === "debug")
4983
4994
  console.log(...messages);
@@ -5778,7 +5789,7 @@ var require_schema2 = __commonJS((exports) => {
5778
5789
  exports.schema = schema;
5779
5790
  });
5780
5791
  var require_binary = __commonJS((exports) => {
5781
- var node_buffer = __require2("buffer");
5792
+ var node_buffer = __require("buffer");
5782
5793
  var Scalar = require_Scalar();
5783
5794
  var stringifyString = require_stringifyString();
5784
5795
  var binary = {
@@ -8085,7 +8096,7 @@ var require_compose_doc = __commonJS((exports) => {
8085
8096
  exports.composeDoc = composeDoc;
8086
8097
  });
8087
8098
  var require_composer = __commonJS((exports) => {
8088
- var node_process = __require2("process");
8099
+ var node_process = __require("process");
8089
8100
  var directives = require_directives();
8090
8101
  var Document = require_Document();
8091
8102
  var errors = require_errors();
@@ -9296,7 +9307,7 @@ var require_line_counter = __commonJS((exports) => {
9296
9307
  exports.LineCounter = LineCounter;
9297
9308
  });
9298
9309
  var require_parser = __commonJS((exports) => {
9299
- var node_process = __require2("process");
9310
+ var node_process = __require("process");
9300
9311
  var cst = require_cst();
9301
9312
  var lexer = require_lexer();
9302
9313
  function includesToken(list, type) {
@@ -10846,6 +10857,33 @@ function assertAgentPolicyCollections(policy) {
10846
10857
  for (const key of ["agent", "home", "alias", "target", "link", "aliasIdentity", "targetIdentity"])
10847
10858
  text(alias[key]);
10848
10859
  }
10860
+ if (bridge.codexPluginSkills !== undefined)
10861
+ for (const control of array(bridge.codexPluginSkills, 4096)) {
10862
+ requireBound(object(control) && Object.keys(control).every((key) => ["name", "pluginId", "namespace", "pluginParent", "manifestSha256", "appSha256", "mcpSha256", "remotePluginId"].includes(key)));
10863
+ text(control.name, 129);
10864
+ text(control.pluginId, 1024);
10865
+ text(control.namespace, 64);
10866
+ text(control.pluginParent);
10867
+ requireBound(isAbsolute22(control.pluginParent) && resolve22(control.pluginParent) === control.pluginParent && /^[a-f0-9]{64}$/.test(control.manifestSha256));
10868
+ if (control.remotePluginId !== undefined)
10869
+ requireBound(typeof control.remotePluginId === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(control.remotePluginId));
10870
+ if (control.mcpSha256 !== undefined)
10871
+ requireBound(typeof control.mcpSha256 === "string" && /^[a-f0-9]{64}$/.test(control.mcpSha256));
10872
+ if (control.appSha256 !== undefined)
10873
+ requireBound(typeof control.appSha256 === "string" && /^[a-f0-9]{64}$/.test(control.appSha256));
10874
+ }
10875
+ if (bridge.codexInactivePlugins !== undefined) {
10876
+ const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
10877
+ requireBound(controls.length === 0 || object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === "codex-cli 0.160.0" && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
10878
+ for (const control of controls) {
10879
+ requireBound(object(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
10880
+ text(control.pluginId, 1024);
10881
+ text(control.namespace, 64);
10882
+ text(control.pluginParent);
10883
+ requireBound(!ids.has(control.pluginId) && isAbsolute22(control.pluginParent) && resolve22(control.pluginParent) === control.pluginParent && ["local", "git", "npm"].includes(control.sourceType) && typeof control.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(control.sourceSha256));
10884
+ ids.add(control.pluginId);
10885
+ }
10886
+ }
10849
10887
  if (bridge.disabledBuiltins !== undefined)
10850
10888
  for (const builtin of array(bridge.disabledBuiltins, AGENT_POLICY_LIMITS.builtinNames)) {
10851
10889
  requireBound(object(builtin));
@@ -10858,6 +10896,25 @@ function assertAgentPolicyCollections(policy) {
10858
10896
  text(agent, 128);
10859
10897
  requireBound(object(value));
10860
10898
  text(value.agent, 128);
10899
+ if (value.codexInstallationInputs !== undefined) {
10900
+ const proof = record(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
10901
+ requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && proof.version === "codex-cli 0.160.0" && typeof proof.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(proof.catalogSha256) && inputs.length > 0 && object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
10902
+ for (const input of inputs) {
10903
+ requireBound(object(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
10904
+ text(input.pluginId, 1024);
10905
+ text(input.namespace, 64);
10906
+ text(input.pluginParent);
10907
+ text(input.sourceRoot);
10908
+ requireBound(!ids.has(input.pluginId) && [input.pluginParent, input.sourceRoot].every((path) => isAbsolute22(path) && resolve22(path) === path) && typeof input.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(input.sourceSha256));
10909
+ ids.add(input.pluginId);
10910
+ }
10911
+ if (proof.directories !== undefined)
10912
+ for (const directory of array(proof.directories, AGENT_POLICY_LIMITS.discoveryDirectories)) {
10913
+ requireBound(object(directory));
10914
+ text(directory.path);
10915
+ requireBound(directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256));
10916
+ }
10917
+ }
10861
10918
  for (const root of array(value.roots, AGENT_POLICY_LIMITS.discoveryRoots))
10862
10919
  text(root);
10863
10920
  for (const source of array(value.sources, AGENT_POLICY_LIMITS.discoverySources)) {
@@ -10865,7 +10922,9 @@ function assertAgentPolicyCollections(policy) {
10865
10922
  text(source.path);
10866
10923
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
10867
10924
  if (source.hashMode !== undefined)
10868
- requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
10925
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
10926
+ if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3")
10927
+ requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute22(source.path) && resolve22(source.path) === source.path && basename2(source.path) === "config.toml");
10869
10928
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
10870
10929
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute22(source.path) && resolve22(source.path) === source.path && basename2(source.path) === "settings.json");
10871
10930
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
@@ -11136,7 +11195,7 @@ function safe(path) {
11136
11195
  function same(before, after) {
11137
11196
  return Boolean(after && after.isDirectory() && before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs);
11138
11197
  }
11139
- function capture(path, budget) {
11198
+ function capture(path, budget, excludedRoots) {
11140
11199
  safe(path);
11141
11200
  const root = lstatSync4(path, { throwIfNoEntry: false });
11142
11201
  if (!root)
@@ -11165,7 +11224,7 @@ function capture(path, budget) {
11165
11224
  budget.bytes += Buffer.byteLength(row);
11166
11225
  need(budget.bytes <= AGENT_POLICY_LIMITS.discoveryDirectoryBytes, "Native discovery directory metadata limit exceeded");
11167
11226
  hash.update(row);
11168
- if (stat.isDirectory())
11227
+ if (stat.isDirectory() && !excludedRoots.includes(child))
11169
11228
  walk(child, childRelative, stat, depth + 1);
11170
11229
  }
11171
11230
  need(same(before, lstatSync4(current2, { throwIfNoEntry: false })), "Native discovery directory changed while reading");
@@ -11174,16 +11233,18 @@ function capture(path, budget) {
11174
11233
  safe(path);
11175
11234
  return { path, sha256: hash.digest("hex") };
11176
11235
  }
11177
- function captureDiscoveryDirectories(paths) {
11236
+ function captureDiscoveryDirectories(paths, excludedRoots = []) {
11178
11237
  need(Array.isArray(paths) && paths.length <= AGENT_POLICY_LIMITS.discoveryDirectories && new Set(paths).size === paths.length, "Invalid native discovery directory collection");
11179
11238
  const budget = { entries: 0, bytes: 0 };
11180
- return paths.map((path) => capture(path, budget));
11239
+ for (const root of excludedRoots)
11240
+ safe(root);
11241
+ return paths.map((path) => capture(path, budget, excludedRoots));
11181
11242
  }
11182
- function verifyDiscoveryDirectories(directories) {
11243
+ function verifyDiscoveryDirectories(directories, excludedRoots = []) {
11183
11244
  need(Array.isArray(directories) && directories.length <= AGENT_POLICY_LIMITS.discoveryDirectories, "Invalid native discovery directory collection");
11184
11245
  for (const directory of directories)
11185
11246
  need(directory && typeof directory === "object" && (directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256)), "Invalid native discovery directory digest");
11186
- const current2 = captureDiscoveryDirectories(directories.map((directory) => directory.path));
11247
+ const current2 = captureDiscoveryDirectories(directories.map((directory) => directory.path), excludedRoots);
11187
11248
  for (let i = 0;i < directories.length; i++)
11188
11249
  need(current2[i].sha256 === directories[i].sha256, `Native discovery directory membership changed; run skills hook install with a fresh discovery review: ${directories[i].path}`);
11189
11250
  }
@@ -11359,236 +11420,6 @@ function hashDiscoveryPathFile(path, budget, changes) {
11359
11420
  throw new Error("Discovery path input changed during read");
11360
11421
  return createHash42("sha256").update(JSON.stringify({ version: 1, path, rows: before.rows, content })).digest("hex");
11361
11422
  }
11362
- var package_default = {
11363
- name: "@hasna/skills",
11364
- version: "0.9.17",
11365
- description: "Skills library for AI coding agents",
11366
- type: "module",
11367
- bin: {
11368
- skills: "bin/index.js",
11369
- "skills-mcp": "bin/mcp.js",
11370
- "skills-serve": "bin/server.js",
11371
- "skills-server": "bin/server.js",
11372
- "skills-worker": "bin/worker.js",
11373
- "skills-maintenance": "bin/maintenance.js",
11374
- "skills-migrate": "bin/migrate.js"
11375
- },
11376
- exports: {
11377
- ".": {
11378
- import: "./dist/index.js",
11379
- types: "./dist/index.d.ts"
11380
- },
11381
- "./storage": {
11382
- import: "./dist/storage.js",
11383
- types: "./dist/storage.d.ts"
11384
- },
11385
- "./sdk": {
11386
- import: "./dist/sdk/index.js",
11387
- types: "./dist/sdk/index.d.ts"
11388
- },
11389
- "./admin-contract": {
11390
- import: "./dist/admin-contract.js",
11391
- types: "./dist/admin-contract.d.ts"
11392
- }
11393
- },
11394
- files: [
11395
- "dist/",
11396
- "!dist/**/*.test.d.ts",
11397
- "!dist/**/*.fixture.d.ts",
11398
- "!dist/test-preload.d.ts",
11399
- "!dist/platform",
11400
- "bin/",
11401
- "migrations/",
11402
- "docs/skill-standard.md",
11403
- "docs/plugin-admission.md",
11404
- "schemas/",
11405
- "LICENSE",
11406
- "README.md",
11407
- "!dist/**/*test-utils.d.ts",
11408
- "!dist/**/*fixtures.d.ts"
11409
- ],
11410
- main: "./dist/index.js",
11411
- types: "./dist/index.d.ts",
11412
- scripts: {
11413
- clean: "rm -rf bin/ dist/",
11414
- build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
11415
- "build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
11416
- test: "bun test --timeout 30000",
11417
- dev: "bun run ./src/cli/index.tsx",
11418
- "dev:watch": "bun --watch run ./src/cli/index.tsx",
11419
- "dev:mcp": "bun --watch run ./src/mcp/index.ts",
11420
- "dev:server": "bun --watch run ./src/server/index.ts",
11421
- "dev:worker": "bun --watch run ./src/server/worker.ts",
11422
- "docker:check": "bash scripts/docker-build-check.sh",
11423
- migrate: "bun run ./src/server/migrate.ts",
11424
- typecheck: "tsc --noEmit",
11425
- "verify:release": "bun run scripts/release-guard.ts",
11426
- "verify:consumer-types": "bun run scripts/consumer-types.ts",
11427
- prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
11428
- prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
11429
- "verify:producer": "bun run scripts/release-dependencies.ts",
11430
- "verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
11431
- "verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
11432
- },
11433
- keywords: [
11434
- "skills",
11435
- "ai",
11436
- "agent",
11437
- "cli",
11438
- "typescript",
11439
- "bun",
11440
- "claude",
11441
- "codex",
11442
- "gemini",
11443
- "mcp",
11444
- "model-context-protocol",
11445
- "open-source",
11446
- "skills-library",
11447
- "automation"
11448
- ],
11449
- author: "Hasna",
11450
- license: "Apache-2.0",
11451
- devDependencies: {
11452
- "@hasna/contracts": "1.1.0",
11453
- "@types/bun": "1.3.14",
11454
- "@types/node": "25.2.3",
11455
- "@types/react": "^18.2.0",
11456
- "bun-types": "1.3.14",
11457
- "react-devtools-core": "^7.0.1",
11458
- typescript: "^5",
11459
- yaml: "^2.9.0"
11460
- },
11461
- dependencies: {
11462
- "@aws-sdk/client-ecs": "^3.1079.0",
11463
- "@aws-sdk/client-s3": "^3.1079.0",
11464
- "@hasna/events": "0.1.18",
11465
- "@hasna/secrets": "0.4.2",
11466
- "@modelcontextprotocol/sdk": "^1.26.0",
11467
- chalk: "^5.3.0",
11468
- commander: "^12.1.0",
11469
- ink: "^5.0.1",
11470
- "ink-select-input": "^6.0.0",
11471
- "ink-spinner": "^5.0.0",
11472
- "ink-text-input": "^6.0.0",
11473
- react: "^18.2.0",
11474
- zod: "^4.3.6"
11475
- },
11476
- engines: {
11477
- bun: ">=1.0.0"
11478
- },
11479
- publishConfig: {
11480
- registry: "https://registry.npmjs.org",
11481
- access: "public"
11482
- },
11483
- repository: {
11484
- type: "git",
11485
- url: "https://github.com/hasna/apps.git",
11486
- directory: "apps/skills"
11487
- },
11488
- homepage: "https://github.com/hasna/apps/tree/main/apps/skills",
11489
- bugs: {
11490
- url: "https://github.com/hasna/apps/issues"
11491
- }
11492
- };
11493
- function envToken(name) {
11494
- return name.toUpperCase().replace(/-/g, "_");
11495
- }
11496
- function clientTransportEnvKeys(name) {
11497
- const envSegment = envToken(name);
11498
- return {
11499
- apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
11500
- apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
11501
- };
11502
- }
11503
- function credentialPointerEnvKey(name) {
11504
- return `HASNA_${envToken(name)}_API_KEY_REF`;
11505
- }
11506
- var MAX_PACKAGE_BYTES = 1024 * 1024;
11507
- var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
11508
- var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
11509
- var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
11510
- var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
11511
- var requireSecretsSdk = createRequire(import.meta.url);
11512
- var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
11513
- var AUTHORITY_OVERRIDE_HEADERS = new Set([
11514
- "host",
11515
- ":authority",
11516
- "forwarded",
11517
- "x-forwarded-host",
11518
- "x-original-host"
11519
- ]);
11520
- var SKILLS_APP = "skills";
11521
- var ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
11522
- var SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
11523
- var SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
11524
- var SKILLS_API_URL_ENV = SKILLS_API_URL_ENV_KEYS[0];
11525
- var SKILLS_API_KEY_ENV = SKILLS_API_KEY_ENV_KEYS[0];
11526
-
11527
- class SkillsFleetCredentialError extends Error {
11528
- code;
11529
- constructor(message, code = "MISSING_API_CREDENTIAL") {
11530
- super(message);
11531
- this.name = "SkillsFleetCredentialError";
11532
- this.code = code;
11533
- }
11534
- }
11535
- function normalizeSkillsApiOrigin(apiUrl) {
11536
- const url = new URL(apiUrl);
11537
- if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
11538
- throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
11539
- }
11540
- const pathname = url.pathname.replace(/\/+$/, "");
11541
- if (url.origin === "https://api.hasna.com" && pathname === "/skills/v1") {
11542
- url.pathname = "/skills";
11543
- } else if (pathname === "/api" || pathname === "/api/v1") {
11544
- url.pathname = "/";
11545
- } else if (pathname.endsWith("/api/v1")) {
11546
- url.pathname = pathname.slice(0, -"/api/v1".length) || "/";
11547
- } else if (pathname.endsWith("/api")) {
11548
- url.pathname = pathname.slice(0, -"/api".length) || "/";
11549
- }
11550
- return url.toString().replace(/\/+$/, "");
11551
- }
11552
- function skillsApiRequestUrl(apiUrl, route) {
11553
- const origin = normalizeSkillsApiOrigin(apiUrl);
11554
- if (!route.startsWith("/api/") || route.includes("#") || route.includes("\\")) {
11555
- throw new SkillsFleetCredentialError("Invalid Skills API route", "INVALID_API_URL");
11556
- }
11557
- if (origin === "https://api.hasna.com/skills") {
11558
- if (route === "/api/auth/whoami")
11559
- return `${origin}/v1/auth/whoami`;
11560
- if (!route.startsWith("/api/v1/")) {
11561
- throw new SkillsFleetCredentialError("The internal Skills gateway has no established login contract yet. Select an explicitly configured instance with supported authentication.", "GATEWAY_AUTH_UNAVAILABLE");
11562
- }
11563
- return `${origin}${route.slice("/api".length)}`;
11564
- }
11565
- return `${origin}${route}`;
11566
- }
11567
- var MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
11568
- function selectionAliasError(selections) {
11569
- const names = new Set(selections.map((selection) => selection.slug));
11570
- for (const selection of selections) {
11571
- if (selection.aliases === undefined)
11572
- continue;
11573
- if (!Array.isArray(selection.aliases) || selection.aliases.length > 32)
11574
- return "Selection aliases must be an array of at most 32 names.";
11575
- for (const alias of selection.aliases) {
11576
- if (typeof alias !== "string" || alias.length > 128 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(alias))
11577
- return "Selection aliases must be bounded kebab-case names.";
11578
- if (names.has(alias))
11579
- return "Selection aliases must be unique and cannot shadow a selected canonical name.";
11580
- names.add(alias);
11581
- }
11582
- }
11583
- }
11584
- var MAX_PROFILE_SELECTIONS = 4096;
11585
- var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
11586
- var MAX_SKILL_SESSION_ID_CHARS = 256;
11587
- var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
11588
- var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
11589
- function profileDocumentBytes(value) {
11590
- return new TextEncoder().encode(JSON.stringify(value)).byteLength;
11591
- }
11592
11423
  var BLOCK = 512;
11593
11424
  var ANY_SEGMENT_EXCLUDES = new Set([
11594
11425
  ".git",
@@ -11853,6 +11684,30 @@ var SKILL_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
11853
11684
  function isValidSkillVersion(value) {
11854
11685
  return typeof value === "string" && SKILL_VERSION_PATTERN.test(value) && !/^\.+$/.test(value) && !value.includes("..");
11855
11686
  }
11687
+ function selectionAliasError(selections) {
11688
+ const names = new Set(selections.map((selection) => selection.slug));
11689
+ for (const selection of selections) {
11690
+ if (selection.aliases === undefined)
11691
+ continue;
11692
+ if (!Array.isArray(selection.aliases) || selection.aliases.length > 32)
11693
+ return "Selection aliases must be an array of at most 32 names.";
11694
+ for (const alias of selection.aliases) {
11695
+ if (typeof alias !== "string" || alias.length > 128 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(alias))
11696
+ return "Selection aliases must be bounded kebab-case names.";
11697
+ if (names.has(alias))
11698
+ return "Selection aliases must be unique and cannot shadow a selected canonical name.";
11699
+ names.add(alias);
11700
+ }
11701
+ }
11702
+ }
11703
+ var MAX_PROFILE_SELECTIONS = 4096;
11704
+ var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
11705
+ var MAX_SKILL_SESSION_ID_CHARS = 256;
11706
+ var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
11707
+ var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
11708
+ function profileDocumentBytes(value) {
11709
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
11710
+ }
11856
11711
  class SkillSelectionError extends Error {
11857
11712
  code;
11858
11713
  constructor(code, message) {
@@ -11862,7 +11717,7 @@ class SkillSelectionError extends Error {
11862
11717
  }
11863
11718
  }
11864
11719
  var MAX_CACHED_PROFILE_AGE_MS = 24 * 60 * 60 * 1000;
11865
- function validateSelection2(selection) {
11720
+ function validateSelection(selection) {
11866
11721
  if (!selection || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(selection.slug) || !isValidSkillVersion(selection.version) || !/^sha256:[a-f0-9]{64}$/.test(selection.bundleDigest) || typeof selection.workspaceId !== "string" || !selection.workspaceId.trim() || typeof selection.profileRevision !== "string" || !selection.profileRevision.trim()) {
11867
11722
  throw new SkillSelectionError("INVALID_SELECTION", "A skill selection must carry an exact version, digest, workspace and profile revision.");
11868
11723
  }
@@ -11885,7 +11740,7 @@ function validateResolvedProfile(profile, authority) {
11885
11740
  throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The resolved Skills profile exceeds its size limit.");
11886
11741
  const slugs = new Set;
11887
11742
  for (const selection of profile.selections) {
11888
- validateSelection2(selection);
11743
+ validateSelection(selection);
11889
11744
  if (selection.authority !== profile.authority || selection.workspaceId !== profile.workspaceId || selection.profileRevision !== profile.profileRevision || slugs.has(selection.slug)) {
11890
11745
  throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The resolved profile contains conflicting selection identities.");
11891
11746
  }
@@ -11895,110 +11750,276 @@ function validateResolvedProfile(profile, authority) {
11895
11750
  if (aliasError)
11896
11751
  throw new SkillSelectionError("INVALID_SELECTION_ALIASES", aliasError);
11897
11752
  }
11898
- function refusal(reason = "is unreadable") {
11899
- return new SkillSelectionError("INVALID_AGENT_POLICY", `The Skills agent loading policy ${reason}; refusing legacy fallback.`);
11900
- }
11901
- function parseManagedSkillPolicy(text2) {
11902
- if (Buffer.byteLength(text2) > AGENT_POLICY_LIMITS.bytes)
11903
- throw refusal("exceeds its size limit");
11904
- let value;
11905
- try {
11906
- value = JSON.parse(text2);
11907
- } catch {
11908
- throw refusal();
11753
+ var package_default = {
11754
+ name: "@hasna/skills",
11755
+ version: "0.10.28",
11756
+ description: "Skills library for AI coding agents",
11757
+ type: "module",
11758
+ bin: {
11759
+ skills: "bin/index.js",
11760
+ "skills-mcp": "bin/mcp.js",
11761
+ "skills-serve": "bin/server.js",
11762
+ "skills-server": "bin/server.js",
11763
+ "skills-worker": "bin/worker.js",
11764
+ "skills-maintenance": "bin/maintenance.js",
11765
+ "skills-migrate": "bin/migrate.js"
11766
+ },
11767
+ exports: {
11768
+ ".": {
11769
+ import: "./dist/index.js",
11770
+ types: "./dist/index.d.ts"
11771
+ },
11772
+ "./storage": {
11773
+ import: "./dist/storage.js",
11774
+ types: "./dist/storage.d.ts"
11775
+ },
11776
+ "./sdk": {
11777
+ import: "./dist/sdk/index.js",
11778
+ types: "./dist/sdk/index.d.ts"
11779
+ },
11780
+ "./admin-contract": {
11781
+ import: "./dist/admin-contract.js",
11782
+ types: "./dist/admin-contract.d.ts"
11783
+ }
11784
+ },
11785
+ files: [
11786
+ "dist/",
11787
+ "!dist/**/*.test.d.ts",
11788
+ "!dist/**/*.fixture.d.ts",
11789
+ "!dist/test-preload.d.ts",
11790
+ "!dist/platform",
11791
+ "bin/",
11792
+ "migrations/",
11793
+ "docs/skill-standard.md",
11794
+ "docs/plugin-admission.md",
11795
+ "schemas/",
11796
+ "LICENSE",
11797
+ "README.md",
11798
+ "!dist/**/*test-utils.d.ts",
11799
+ "!dist/**/*fixtures.d.ts"
11800
+ ],
11801
+ main: "./dist/index.js",
11802
+ types: "./dist/index.d.ts",
11803
+ scripts: {
11804
+ clean: "rm -rf bin/ dist/",
11805
+ build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
11806
+ "build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
11807
+ test: "bun test --timeout 30000",
11808
+ dev: "bun run ./src/cli/index.tsx",
11809
+ "dev:watch": "bun --watch run ./src/cli/index.tsx",
11810
+ "dev:mcp": "bun --watch run ./src/mcp/index.ts",
11811
+ "dev:server": "bun --watch run ./src/server/index.ts",
11812
+ "dev:worker": "bun --watch run ./src/server/worker.ts",
11813
+ "docker:check": "bash scripts/docker-build-check.sh",
11814
+ migrate: "bun run ./src/server/migrate.ts",
11815
+ typecheck: "tsc --noEmit",
11816
+ "verify:release": "bun run scripts/release-guard.ts",
11817
+ "verify:release-review": "bun run scripts/verify-release-review.ts",
11818
+ "verify:consumer-types": "bun run scripts/consumer-types.ts",
11819
+ prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
11820
+ prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
11821
+ "verify:producer": "bun run scripts/release-dependencies.ts",
11822
+ "verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
11823
+ "verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
11824
+ },
11825
+ keywords: [
11826
+ "skills",
11827
+ "ai",
11828
+ "agent",
11829
+ "cli",
11830
+ "typescript",
11831
+ "bun",
11832
+ "claude",
11833
+ "codex",
11834
+ "gemini",
11835
+ "mcp",
11836
+ "model-context-protocol",
11837
+ "open-source",
11838
+ "skills-library",
11839
+ "automation"
11840
+ ],
11841
+ author: "Hasna",
11842
+ license: "Apache-2.0",
11843
+ devDependencies: {
11844
+ "@hasna/contracts": "1.1.0",
11845
+ "@types/bun": "1.3.14",
11846
+ "@types/node": "25.2.3",
11847
+ "@types/react": "^18.2.0",
11848
+ "bun-types": "1.3.14",
11849
+ "react-devtools-core": "^7.0.1",
11850
+ typescript: "^5",
11851
+ yaml: "^2.9.0"
11852
+ },
11853
+ dependencies: {
11854
+ "@aws-sdk/client-ecs": "^3.1079.0",
11855
+ "@aws-sdk/client-s3": "^3.1079.0",
11856
+ "@hasna/events": "0.1.18",
11857
+ "@hasna/secrets": "0.4.2",
11858
+ "@modelcontextprotocol/sdk": "^1.26.0",
11859
+ chalk: "^5.3.0",
11860
+ commander: "^12.1.0",
11861
+ ink: "^5.0.1",
11862
+ "ink-select-input": "^6.0.0",
11863
+ "ink-spinner": "^5.0.0",
11864
+ "ink-text-input": "^6.0.0",
11865
+ react: "^18.2.0",
11866
+ zod: "^4.3.6"
11867
+ },
11868
+ engines: {
11869
+ bun: ">=1.0.0"
11870
+ },
11871
+ publishConfig: {
11872
+ registry: "https://registry.npmjs.org",
11873
+ access: "public"
11874
+ },
11875
+ repository: {
11876
+ type: "git",
11877
+ url: "https://github.com/hasna/skills.git",
11878
+ directory: "apps/skills"
11879
+ },
11880
+ homepage: "https://github.com/hasna/skills/tree/main/apps/skills",
11881
+ bugs: {
11882
+ url: "https://github.com/hasna/skills/issues"
11909
11883
  }
11910
- if (!value || typeof value !== "object" || Array.isArray(value) || value.loading !== "cli" || value.version !== undefined && value.version !== 1)
11911
- throw refusal();
11912
- if (value.profileId !== undefined && (typeof value.profileId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value.profileId) || value.profileId.includes("..")))
11913
- throw refusal();
11914
- try {
11915
- assertAgentPolicyCollections(value);
11916
- } catch {
11917
- throw refusal("has invalid collection bounds");
11884
+ };
11885
+ function envToken(name) {
11886
+ return name.toUpperCase().replace(/-/g, "_");
11887
+ }
11888
+ function clientTransportEnvKeys(name) {
11889
+ const envSegment = envToken(name);
11890
+ return {
11891
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
11892
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
11893
+ };
11894
+ }
11895
+ function credentialPointerEnvKey(name) {
11896
+ return `HASNA_${envToken(name)}_API_KEY_REF`;
11897
+ }
11898
+ var MAX_PACKAGE_BYTES = 1024 * 1024;
11899
+ var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
11900
+ var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
11901
+ var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
11902
+ var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
11903
+ var requireSecretsSdk = createRequire(import.meta.url);
11904
+ var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
11905
+ var AUTHORITY_OVERRIDE_HEADERS = new Set([
11906
+ "host",
11907
+ ":authority",
11908
+ "forwarded",
11909
+ "x-forwarded-host",
11910
+ "x-original-host"
11911
+ ]);
11912
+ var SKILLS_APP = "skills";
11913
+ var ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
11914
+ var SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
11915
+ var SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
11916
+ var SKILLS_API_URL_ENV = SKILLS_API_URL_ENV_KEYS[0];
11917
+ var SKILLS_API_KEY_ENV = SKILLS_API_KEY_ENV_KEYS[0];
11918
+
11919
+ class SkillsFleetCredentialError extends Error {
11920
+ code;
11921
+ next;
11922
+ constructor(message, code = "MISSING_API_CREDENTIAL", next) {
11923
+ super(message);
11924
+ this.name = "SkillsFleetCredentialError";
11925
+ this.code = code;
11926
+ if (next)
11927
+ this.next = next;
11918
11928
  }
11919
- return value;
11920
11929
  }
11921
- function serializeManagedSkillPolicy(value) {
11922
- const text2 = `${JSON.stringify(value, null, 2)}
11923
- `;
11924
- parseManagedSkillPolicy(text2);
11925
- return text2;
11930
+ function normalizeSkillsApiOrigin(apiUrl) {
11931
+ const url = new URL(apiUrl);
11932
+ if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
11933
+ throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
11934
+ }
11935
+ const pathname = url.pathname.replace(/\/+$/, "");
11936
+ if (url.origin === "https://api.hasna.com" && pathname === "/skills/v1") {
11937
+ url.pathname = "/skills";
11938
+ } else if (pathname === "/api" || pathname === "/api/v1") {
11939
+ url.pathname = "/";
11940
+ } else if (pathname.endsWith("/api/v1")) {
11941
+ url.pathname = pathname.slice(0, -"/api/v1".length) || "/";
11942
+ } else if (pathname.endsWith("/api")) {
11943
+ url.pathname = pathname.slice(0, -"/api".length) || "/";
11944
+ }
11945
+ return url.toString().replace(/\/+$/, "");
11926
11946
  }
11927
- function readManagedSkillPolicySnapshot(dataDir22 = getDataDirReadOnly()) {
11928
- const path = join14(dataDir22, "agent-policy.json");
11929
- let descriptor, observed = false;
11930
- try {
11931
- const stat = lstatSync11(path);
11932
- observed = true;
11933
- if (!stat.isFile() || stat.isSymbolicLink())
11934
- throw refusal();
11935
- if (stat.size > AGENT_POLICY_LIMITS.bytes)
11936
- throw refusal("exceeds its size limit");
11937
- descriptor = openSync8(path, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0) | constants5.O_NONBLOCK);
11938
- const opened = fstatSync8(descriptor);
11939
- if (!opened.isFile())
11940
- throw refusal();
11941
- if (opened.size > AGENT_POLICY_LIMITS.bytes)
11942
- throw refusal("exceeds its size limit");
11943
- const bytes = Buffer.allocUnsafe(AGENT_POLICY_LIMITS.bytes + 1);
11944
- let length = 0;
11945
- while (length < bytes.length) {
11946
- const count = readSync4(descriptor, bytes, length, bytes.length - length, null);
11947
- if (count === 0)
11948
- break;
11949
- length += count;
11950
- }
11951
- if (length > AGENT_POLICY_LIMITS.bytes)
11952
- throw refusal("exceeds its size limit");
11953
- const text2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
11954
- return { text: text2, value: parseManagedSkillPolicy(text2) };
11955
- } catch (error) {
11956
- if (error.code === "ENOENT" && !observed) {
11957
- if (lstatSync11(path, { throwIfNoEntry: false }) === undefined)
11958
- return null;
11947
+ function skillsApiRequestUrl(apiUrl, route) {
11948
+ const origin = normalizeSkillsApiOrigin(apiUrl);
11949
+ if (!route.startsWith("/api/") || route.includes("#") || route.includes("\\")) {
11950
+ throw new SkillsFleetCredentialError("Invalid Skills API route", "INVALID_API_URL");
11951
+ }
11952
+ if (origin === "https://api.hasna.com/skills") {
11953
+ if (route === "/api/auth/whoami")
11954
+ return `${origin}/v1/auth/whoami`;
11955
+ if (!route.startsWith("/api/v1/")) {
11956
+ throw new SkillsFleetCredentialError("The internal Skills gateway has no established login contract yet. Select an explicitly configured instance with supported authentication.", "GATEWAY_AUTH_UNAVAILABLE");
11959
11957
  }
11960
- if (error instanceof SkillSelectionError)
11961
- throw error;
11962
- throw refusal();
11963
- } finally {
11964
- if (descriptor !== undefined)
11965
- closeSync8(descriptor);
11958
+ return `${origin}${route.slice("/api".length)}`;
11966
11959
  }
11960
+ return `${origin}${route}`;
11967
11961
  }
11968
- var CLI_BRIDGE_NAME = "skills-cli";
11969
- var CLI_BRIDGE_VERSION = 1;
11970
- var markdown = `---
11971
- name: skills-cli
11972
- description: Discover, load, author, and run versioned skills through the Skills CLI when a task needs reusable instructions or an executable skill.
11973
- ---
11974
-
11975
- Use the Skills CLI as the skill authority for this station.
11976
-
11977
- - Discover available skills with \`skills list --json\` or \`skills search <query> --json\`.
11978
- - Read selected instructions with \`skills load <slug>\`. Use \`skills sync\` to refresh the station's shared selection profile.
11979
- - Create local drafts with \`skills new <name> --kind instruction\` or \`--kind executable\` in the Skills-owned source directory. Validate with \`skills validate <name>\`; use \`skills push --help\` for explicit versioned publication.
11980
- - Run a skill only when the user's task calls for execution. Use \`skills run --help\` and select the intended local or cloud target explicitly.
11981
-
11982
- This bridge contains no skill payloads. Do not copy skill content into agent-native skill directories or load other native skill copies. If a Skills command refuses authority, selection, integrity, or native drift, report that refusal and use its repair guidance; do not substitute bundled or stale local content. Explicit local draft authoring remains available.
11983
- `;
11984
- var CLI_BRIDGE_DIGEST = createHash8("sha256").update(markdown).digest("hex");
11985
- var CLI_BRIDGE_FILES = Object.freeze({
11986
- "SKILL.md": markdown,
11987
- ".hasna-skills.json": `${JSON.stringify({ managedBy: "@hasna/skills", kind: "cli-bridge", version: CLI_BRIDGE_VERSION, protocol: "selection-v1", contentSha256: CLI_BRIDGE_DIGEST }, null, 2)}
11988
- `
11989
- });
11990
- function isOwnedCliBridge(path, expectedPaths) {
11991
- if (!expectedPaths.includes(path))
11992
- return false;
11962
+ var MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
11963
+ function isCodexLocalStdioMcpServer(name, server) {
11964
+ const identifier2 = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(value);
11965
+ const refuse = () => {
11966
+ throw new Error("Unsupported Codex local stdio MCP controls");
11967
+ };
11968
+ const object4 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date));
11969
+ const textField = (v, empty = false) => typeof v === "string" && (empty || Boolean(v.trim())) && Buffer.byteLength(v) <= 16384 && !/[\x00-\x1f\x7f]/.test(v);
11970
+ const envName = (v) => typeof v === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,255}$/.test(v);
11971
+ const optional = (v, valid2) => v === undefined || v === null || valid2(v);
11972
+ const stringList = (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => textField(item));
11973
+ const approval = (v) => ["auto", "prompt", "writes", "approve"].includes(v);
11974
+ const duration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
11975
+ const positiveInteger = (v) => Number.isSafeInteger(v) && v > 0;
11976
+ const fields = [
11977
+ "command",
11978
+ "args",
11979
+ "cwd",
11980
+ "env",
11981
+ "env_vars",
11982
+ "enabled",
11983
+ "required",
11984
+ "environment_id",
11985
+ "startup_readiness",
11986
+ "supports_parallel_tool_calls",
11987
+ "tool_input_schema_max_bytes",
11988
+ "startup_timeout_sec",
11989
+ "startup_timeout_ms",
11990
+ "tool_timeout_sec",
11991
+ "default_tools_approval_mode",
11992
+ "enabled_tools",
11993
+ "disabled_tools",
11994
+ "omit_tools_from",
11995
+ "tools",
11996
+ "name"
11997
+ ];
11993
11998
  try {
11994
- if (!lstatSync12(path).isDirectory())
11995
- return false;
11996
- if (JSON.stringify(readdirSync6(path).sort()) !== JSON.stringify(Object.keys(CLI_BRIDGE_FILES).sort()))
11997
- return false;
11998
- return Object.entries(CLI_BRIDGE_FILES).every(([name, content]) => {
11999
- const file = join15(path, name), stat = lstatSync12(file);
12000
- return stat.isFile() && !stat.isSymbolicLink() && stat.size === Buffer.byteLength(content) && readFileSync10(file, "utf8") === content;
12001
- });
11999
+ if (!identifier2(name) || !object4(server) || !Object.keys(server).every((key) => fields.includes(key)) || !textField(server.command) || !optional(server.args, (v) => Array.isArray(v) && v.length <= 256 && v.every((arg) => textField(arg, true))) || !optional(server.cwd, textField) || !optional(server.env, (v) => object4(v) && Object.keys(v).length <= 256 && Object.entries(v).every(([key, value]) => envName(key) && textField(value, true))) || !["enabled", "required", "supports_parallel_tool_calls"].every((key) => optional(server[key], (v) => typeof v === "boolean")) || !optional(server.environment_id, (v) => v === "local") || !optional(server.startup_readiness, (v) => ["connection", "catalog"].includes(v)) || !optional(server.tool_input_schema_max_bytes, positiveInteger) || !["startup_timeout_sec", "tool_timeout_sec"].every((key) => optional(server[key], duration)) || !optional(server.startup_timeout_ms, (v) => Number.isSafeInteger(v) && v >= 0) || !optional(server.default_tools_approval_mode, approval) || !["enabled_tools", "disabled_tools"].every((key) => optional(server[key], stringList)) || !optional(server.omit_tools_from, (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => ["code_mode", "deferred", "direct"].includes(item))) || !optional(server.name, textField))
12000
+ refuse();
12001
+ if (server.env_vars !== undefined && server.env_vars !== null) {
12002
+ if (!Array.isArray(server.env_vars) || server.env_vars.length > 256)
12003
+ refuse();
12004
+ const names = server.env_vars.map((entry) => {
12005
+ if (envName(entry))
12006
+ return entry;
12007
+ if (!object4(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || !optional(entry.source, (v) => v === "local"))
12008
+ refuse();
12009
+ return entry.name;
12010
+ });
12011
+ if (new Set(names).size !== names.length)
12012
+ refuse();
12013
+ }
12014
+ if (server.tools !== undefined && server.tools !== null) {
12015
+ if (!object4(server.tools) || Object.keys(server.tools).length > 256)
12016
+ refuse();
12017
+ for (const [tool, config] of Object.entries(server.tools)) {
12018
+ if (!textField(tool) || !object4(config) || !Object.keys(config).every((key) => ["approval_mode", "output_token_limit"].includes(key)) || !optional(config.approval_mode, approval) || !optional(config.output_token_limit, positiveInteger))
12019
+ refuse();
12020
+ }
12021
+ }
12022
+ return true;
12002
12023
  } catch {
12003
12024
  return false;
12004
12025
  }
@@ -12015,36 +12036,36 @@ var sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode
12015
12036
  function parents(path) {
12016
12037
  const result = [];
12017
12038
  for (let at = dirname9(path);; at = dirname9(at)) {
12018
- const stat = lstatSync13(at, { bigint: true });
12039
+ const stat = lstatSync11(at, { bigint: true });
12019
12040
  need2(stat.isDirectory() && !stat.isSymbolicLink(), "path traverses a symlink or non-directory");
12020
12041
  result.push([at, stat]);
12021
12042
  if (at === dirname9(at))
12022
12043
  return result;
12023
12044
  }
12024
12045
  }
12025
- function readSettings(path, budget) {
12046
+ function readNativeSettingsWitnessFile(path, budget, filename = "settings.json") {
12026
12047
  absolutePath(path);
12027
- need2(basename4(path) === "settings.json", "requires settings.json");
12048
+ need2(basename4(path) === filename, `requires ${filename}`);
12028
12049
  need2(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
12029
- const ancestors = parents(path), initial = lstatSync13(path, { bigint: true });
12050
+ const ancestors = parents(path), initial = lstatSync11(path, { bigint: true });
12030
12051
  need2(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_SETTINGS_WITNESS_LIMITS.bytes), "is not a bounded regular file");
12031
12052
  need2(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
12032
- const fd = openSync9(path, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
12053
+ const fd = openSync6(path, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
12033
12054
  try {
12034
- const opened = fstatSync9(fd, { bigint: true });
12055
+ const opened = fstatSync6(fd, { bigint: true });
12035
12056
  need2(opened.isFile() && sameFile(initial, opened), "changed during open");
12036
12057
  const bytes = Buffer.alloc(Number(opened.size) + 1);
12037
12058
  let length = 0;
12038
12059
  while (length < bytes.length) {
12039
- const count = readSync5(fd, bytes, length, bytes.length - length, null);
12060
+ const count = readSync4(fd, bytes, length, bytes.length - length, null);
12040
12061
  if (!count)
12041
12062
  break;
12042
12063
  length += count;
12043
12064
  }
12044
- const after = fstatSync9(fd, { bigint: true }), current2 = lstatSync13(path, { bigint: true });
12065
+ const after = fstatSync6(fd, { bigint: true }), current2 = lstatSync11(path, { bigint: true });
12045
12066
  need2(current2.isFile() && sameFile(opened, after) && sameFile(after, current2) && BigInt(length) === opened.size, "changed during read");
12046
12067
  for (const [ancestor, before] of ancestors) {
12047
- const now = lstatSync13(ancestor, { bigint: true });
12068
+ const now = lstatSync11(ancestor, { bigint: true });
12048
12069
  need2(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
12049
12070
  }
12050
12071
  budget.remaining -= length;
@@ -12054,7 +12075,7 @@ function readSettings(path, budget) {
12054
12075
  throw new Error("Claude settings witness is not strict UTF-8");
12055
12076
  }
12056
12077
  } finally {
12057
- closeSync9(fd);
12078
+ closeSync6(fd);
12058
12079
  }
12059
12080
  }
12060
12081
  function parse3(text2) {
@@ -12256,7 +12277,7 @@ function settingsDigest(text2, version = 1) {
12256
12277
  }
12257
12278
  return true;
12258
12279
  });
12259
- return createHash9("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
12280
+ return createHash8("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
12260
12281
  }
12261
12282
  function hashClaudeSettingsReplacement(text2, budget) {
12262
12283
  need2(typeof text2 === "string", "requires settings text");
@@ -12266,10 +12287,10 @@ function hashClaudeSettingsReplacement(text2, budget) {
12266
12287
  return settingsDigest(text2);
12267
12288
  }
12268
12289
  function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
12269
- return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
12290
+ return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget)) };
12270
12291
  }
12271
12292
  function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
12272
- return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readSettings(path, budget), 2) };
12293
+ return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 2) };
12273
12294
  }
12274
12295
  function hashClaudeSettingsReplacementV2(text2, budget) {
12275
12296
  need2(typeof text2 === "string", "requires settings text");
@@ -12279,7 +12300,7 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
12279
12300
  return settingsDigest(text2, 2);
12280
12301
  }
12281
12302
  function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
12282
- return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
12303
+ return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 3) };
12283
12304
  }
12284
12305
  function hashClaudeSettingsReplacementV3(text2, budget) {
12285
12306
  need2(typeof text2 === "string", "requires settings text");
@@ -12288,14 +12309,849 @@ function hashClaudeSettingsReplacementV3(text2, budget) {
12288
12309
  budget.remaining -= bytes;
12289
12310
  return settingsDigest(text2, 3);
12290
12311
  }
12291
- var sha = (data) => createHash10("sha256").update(data).digest("hex");
12312
+ function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
12313
+ need2(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes" || previous.hashMode === "claude-settings-v1" || previous.hashMode === "claude-settings-v2") && typeof previous.sha256 === "string" && /^[a-f0-9]{64}$/.test(previous.sha256), "requires an exact legacy settings witness");
12314
+ const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
12315
+ const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget), current2 = readNativeSettingsWitnessFile(previous.path, budget);
12316
+ const beforeDigest = previous.hashMode === undefined || previous.hashMode === "bytes" ? createHash8("sha256").update(before).digest("hex") : settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : 2);
12317
+ need2(beforeDigest === previous.sha256, "reviewed preimage does not match the legacy witness");
12318
+ const sha2562 = settingsDigest(current2, 3);
12319
+ need2(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
12320
+ return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
12321
+ }
12322
+ function hashNativeJsonControls(text2, excludedMetadata) {
12323
+ need2(Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, "exceeds its byte limit");
12324
+ const value = parse3(text2);
12325
+ value.entries = value.entries.filter(([key, child]) => {
12326
+ if (key !== excludedMetadata)
12327
+ return true;
12328
+ need2(child.kind === "string" && child.value.length <= 128, "has invalid version metadata");
12329
+ return false;
12330
+ });
12331
+ return createHash8("sha256").update("hasna.skills.native-json-controls.v1\x00").update(JSON.stringify(canonical2(value))).digest("hex");
12332
+ }
12333
+ var MEASURED_CODEX_CAPABILITIES = {
12334
+ "codex-cli 0.153.0": ["hooks"],
12335
+ "codex-cli 0.154.0": ["hooks"],
12336
+ "codex-cli 0.155.0": ["hooks"],
12337
+ "codex-cli 0.155.1": ["hooks"],
12338
+ "codex-cli 0.156.1": ["hooks"],
12339
+ "codex-cli 0.157.0": ["hooks"],
12340
+ "codex-cli 0.157.1": ["hooks"],
12341
+ "codex-cli 0.158.0": ["hooks"],
12342
+ "codex-cli 0.159.0": ["hooks"],
12343
+ "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
12344
+ "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog"]
12345
+ };
12346
+ function supportsCodexNativeCapability(version, capability) {
12347
+ return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
12348
+ }
12349
+ var SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
12350
+ function codexPluginSourceIsConfigControlled(plugin) {
12351
+ const remoteRealms = ["openai-curated-remote", "created-by-me-remote", "workspace-directory", "workspace-shared-with-me", "workspace-shared-with-me-private", "workspace-shared-with-me-unlisted"];
12352
+ return Boolean(plugin.sourceType && ["local", "git", "npm"].includes(plugin.sourceType) && plugin.sourceSha256 && /^[a-f0-9]{64}$/.test(plugin.sourceSha256) && !plugin.remotePluginId && !remoteRealms.includes(plugin.id.slice(plugin.id.lastIndexOf("@") + 1)));
12353
+ }
12354
+ var MAX_SKILLS = 4096;
12355
+ var MAX_NAME_BYTES = 1024;
12356
+ var MAX_PATH_BYTES = 16384;
12357
+ function refuse() {
12358
+ throw new Error("CODEX_NATIVE_SKILL_CATALOG_INVALID");
12359
+ }
12360
+ var object4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
12361
+ var scalar = (value, max) => typeof value === "string" && value.length > 0 && value.trim() === value && Buffer.byteLength(value, "utf8") <= max && !/[\u0000-\u001f\u007f]/u.test(value);
12362
+ var remotePluginIdentifier = (value) => typeof value === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(value);
12363
+ var absolutePath2 = (value) => scalar(value, MAX_PATH_BYTES) && isAbsolute10(value);
12364
+ function canonicalPath(path) {
12365
+ try {
12366
+ return realpathSync4(path);
12367
+ } catch {
12368
+ return normalize2(path);
12369
+ }
12370
+ }
12371
+ function isCodexNativeSkillDisabled(skill, rules) {
12372
+ if (!scalar(skill.name, MAX_NAME_BYTES) || !absolutePath2(skill.path))
12373
+ refuse();
12374
+ if (rules === undefined)
12375
+ return false;
12376
+ if (!Array.isArray(rules) || rules.length > MAX_SKILLS)
12377
+ refuse();
12378
+ let disabled = false;
12379
+ for (const rule of rules) {
12380
+ if (!object4(rule) || typeof rule.enabled !== "boolean")
12381
+ refuse();
12382
+ const hasName = rule.name !== undefined && rule.name !== null;
12383
+ const hasPath = rule.path !== undefined && rule.path !== null;
12384
+ if (hasName === hasPath)
12385
+ refuse();
12386
+ let matches;
12387
+ if (hasName) {
12388
+ if (typeof rule.name !== "string" || !scalar(rule.name.trim(), MAX_NAME_BYTES))
12389
+ refuse();
12390
+ matches = rule.name.trim() === skill.name;
12391
+ } else {
12392
+ if (!absolutePath2(rule.path))
12393
+ refuse();
12394
+ matches = canonicalPath(rule.path) === canonicalPath(skill.path);
12395
+ }
12396
+ if (matches)
12397
+ disabled = !rule.enabled;
12398
+ }
12399
+ return disabled;
12400
+ }
12401
+ var identifier2 = (v) => typeof v === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(v);
12402
+ var CODEX_DEFAULT_AGENT_PLUGIN_VERSION = "1.0.0";
12403
+ function refuse2() {
12404
+ throw new Error("CODEX_NATIVE_SKILL_IDENTITY_UNSUPPORTED: review native plugin names and hook controls");
12405
+ }
12406
+ function remoteInstallationMatches(parent, remotePluginId, read2) {
12407
+ try {
12408
+ if (!remotePluginIdentifier(remotePluginId) || !lstatSync12(parent).isDirectory() || realpathSync5(parent) !== parent)
12409
+ return false;
12410
+ const path = join14(parent, ".codex-remote-plugin-install.json"), stat = lstatSync12(path, { throwIfNoEntry: false });
12411
+ if (!stat?.isFile() || stat.isSymbolicLink() || stat.size > 16384)
12412
+ return false;
12413
+ const text2 = read2(path);
12414
+ hashNativeJsonControls(text2);
12415
+ const value = JSON.parse(text2);
12416
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 2 && value.schema_version === 1 && value.remote_plugin_id === remotePluginId);
12417
+ } catch {
12418
+ return false;
12419
+ }
12420
+ }
12421
+ function appControls(path, read2) {
12422
+ const text2 = read2(path);
12423
+ let value, sha2562;
12424
+ try {
12425
+ sha2562 = hashNativeJsonControls(text2);
12426
+ value = JSON.parse(text2);
12427
+ } catch {
12428
+ refuse2();
12429
+ }
12430
+ const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
12431
+ if (!object5(value) || Object.keys(value).length !== 1 || !object5(value.apps))
12432
+ refuse2();
12433
+ const apps = Object.entries(value.apps);
12434
+ if (!apps.length || apps.length > 256)
12435
+ refuse2();
12436
+ for (const [name, entry] of apps) {
12437
+ if (!identifier2(name) || !object5(entry) || !Object.keys(entry).every((key) => ["id", "category", "required"].includes(key)) || typeof entry.id !== "string" || !/^[A-Za-z0-9_-]{1,256}$/.test(entry.id) || entry.category !== undefined && (typeof entry.category !== "string" || !entry.category.trim() || entry.category.length > 256 || /[\x00-\x1f\x7f]/.test(entry.category)) || entry.required !== undefined && typeof entry.required !== "boolean")
12438
+ refuse2();
12439
+ }
12440
+ return sha2562;
12441
+ }
12442
+ function mcpControls(path, read2) {
12443
+ const text2 = read2(path);
12444
+ let value, sha2562;
12445
+ try {
12446
+ sha2562 = hashNativeJsonControls(text2);
12447
+ value = JSON.parse(text2);
12448
+ } catch {
12449
+ refuse2();
12450
+ }
12451
+ const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
12452
+ if (!object5(value) || Object.keys(value).length !== 1 || !object5(value.mcpServers))
12453
+ refuse2();
12454
+ const servers = Object.entries(value.mcpServers);
12455
+ if (!servers.length || servers.length > 256)
12456
+ refuse2();
12457
+ for (const [name, server] of servers)
12458
+ if (!isCodexLocalStdioMcpServer(name, server))
12459
+ refuse2();
12460
+ return sha2562;
12461
+ }
12462
+ function rootControls(root, read2) {
12463
+ const manifestPath = join14(root, ".codex-plugin/plugin.json"), manifestFile = lstatSync12(manifestPath, { throwIfNoEntry: false });
12464
+ if (!lstatSync12(join14(root, ".codex-plugin"), { throwIfNoEntry: false })?.isDirectory() || !manifestFile?.isFile() || manifestFile.size > 1024 * 1024)
12465
+ refuse2();
12466
+ let manifest, manifestSha256;
12467
+ try {
12468
+ const text2 = read2(manifestPath);
12469
+ manifestSha256 = hashNativeJsonControls(text2, "version");
12470
+ manifest = JSON.parse(text2);
12471
+ } catch {
12472
+ refuse2();
12473
+ }
12474
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
12475
+ refuse2();
12476
+ const cacheVersion = basename5(root), declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
12477
+ if (declaredVersion === null || (declaredVersion ? declaredVersion !== cacheVersion : cacheVersion !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
12478
+ refuse2();
12479
+ if (manifest.hooks !== undefined || manifest.commands !== undefined || manifest.mcpServers !== undefined && manifest.mcpServers !== "./.mcp.json" || manifest.apps !== undefined && manifest.apps !== "./.app.json" || lstatSync12(join14(root, "hooks"), { throwIfNoEntry: false }))
12480
+ refuse2();
12481
+ const mcp = join14(root, ".mcp.json"), mcpPresent = lstatSync12(mcp, { throwIfNoEntry: false });
12482
+ if (mcpPresent && (!mcpPresent.isFile() || mcpPresent.isSymbolicLink() || mcpPresent.size > 1024 * 1024 || realpathSync5(mcp) !== mcp))
12483
+ refuse2();
12484
+ if (Boolean(mcpPresent) !== (manifest.mcpServers !== undefined))
12485
+ refuse2();
12486
+ const mcpSha256 = mcpPresent ? mcpControls(mcp, read2) : undefined;
12487
+ const app = join14(root, ".app.json"), present = lstatSync12(app, { throwIfNoEntry: false });
12488
+ if (present && (!present.isFile() || present.isSymbolicLink() || present.size > 1024 * 1024))
12489
+ refuse2();
12490
+ if (manifest.apps !== undefined && !present)
12491
+ refuse2();
12492
+ const appSha256 = present ? appControls(app, read2) : undefined;
12493
+ return { namespace: manifest.name, manifestSha256, ...appSha256 ? { appSha256 } : {}, ...mcpSha256 ? { mcpSha256 } : {} };
12494
+ }
12495
+ function documentIdentity(document2, cache, read2) {
12496
+ if (!document2.startsWith(cache + sep4) || !document2.endsWith(sep4 + "SKILL.md"))
12497
+ refuse2();
12498
+ const text2 = read2(document2);
12499
+ if (Buffer.byteLength(text2) > 1024 * 1024)
12500
+ refuse2();
12501
+ const lines = text2.split(/\r?\n/);
12502
+ if (lines[0]?.trim() !== "---")
12503
+ refuse2();
12504
+ const end = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
12505
+ if (end < 2)
12506
+ refuse2();
12507
+ const yaml = lines.slice(1, end).join(`
12508
+ `);
12509
+ if (/(?:^|\s)[&*!]|<<\s*:/.test(yaml) || lines.slice(1, end).filter((line) => /^name\s*:/.test(line)).length !== 1)
12510
+ refuse2();
12511
+ let front;
12512
+ try {
12513
+ front = Bun.YAML.parse(yaml);
12514
+ } catch {
12515
+ refuse2();
12516
+ }
12517
+ if (!front || typeof front !== "object" || !identifier2(front.name) || typeof front.description !== "string" || !front.description.trim())
12518
+ refuse2();
12519
+ for (let root = dirname10(document2), depth = 0;root.startsWith(cache + sep4) && depth < 12; root = dirname10(root), depth++) {
12520
+ const path = join14(root, ".codex-plugin", "plugin.json");
12521
+ if (!lstatSync12(path, { throwIfNoEntry: false }))
12522
+ continue;
12523
+ const manifestFile = lstatSync12(path);
12524
+ if (!lstatSync12(join14(root, ".codex-plugin")).isDirectory() || !manifestFile.isFile() || manifestFile.size > 1024 * 1024)
12525
+ refuse2();
12526
+ let manifest;
12527
+ try {
12528
+ const text3 = read2(path);
12529
+ hashNativeJsonControls(text3);
12530
+ manifest = JSON.parse(text3);
12531
+ } catch {
12532
+ refuse2();
12533
+ }
12534
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
12535
+ refuse2();
12536
+ const declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
12537
+ if (declaredVersion === null || (declaredVersion ? declaredVersion !== basename5(root) : basename5(root) !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
12538
+ refuse2();
12539
+ return { name: `${manifest.name}:${front.name}`, namespace: manifest.name, pluginParent: dirname10(root), root };
12540
+ }
12541
+ refuse2();
12542
+ }
12543
+ function identity3(document2, cache, read2) {
12544
+ const parsed = documentIdentity(document2, cache, read2);
12545
+ const controls = rootControls(parsed.root, read2);
12546
+ if (controls.namespace !== parsed.namespace)
12547
+ refuse2();
12548
+ return { name: parsed.name, pluginParent: parsed.pluginParent, ...controls };
12549
+ }
12550
+ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
12551
+ if (!Array.isArray(controls) || controls.length > 4096)
12552
+ return false;
12553
+ let entries = 0;
12554
+ try {
12555
+ for (const parent of new Set(controls.map((control) => control.pluginParent))) {
12556
+ if (!parent.startsWith(cache + sep4) || !lstatSync12(parent, { throwIfNoEntry: false })?.isDirectory())
12557
+ return false;
12558
+ for (const control of controls.filter((control2) => control2.pluginParent === parent)) {
12559
+ if (control.remotePluginId !== undefined && !remoteInstallationMatches(parent, control.remotePluginId, read2))
12560
+ return false;
12561
+ }
12562
+ const names = readdirSync6(parent);
12563
+ entries += names.length;
12564
+ if (entries > 4096)
12565
+ return false;
12566
+ for (const name of names) {
12567
+ let root = join14(parent, name), stat = lstatSync12(root);
12568
+ if (stat.isSymbolicLink()) {
12569
+ const target = resolve9(parent, readlinkSync3(root));
12570
+ if (name !== "latest" || dirname10(target) !== parent || !lstatSync12(target).isDirectory() || realpathSync5(target) !== target)
12571
+ return false;
12572
+ root = target;
12573
+ stat = lstatSync12(root);
12574
+ }
12575
+ if (!stat.isDirectory() || ![".codex-plugin/plugin.json", ".app.json", "hooks", ".mcp.json"].some((path) => lstatSync12(join14(root, path), { throwIfNoEntry: false })))
12576
+ continue;
12577
+ const parsed = rootControls(root, read2);
12578
+ if (!controls.some((control) => control.pluginParent === parent && control.namespace === parsed.namespace && control.manifestSha256 === parsed.manifestSha256 && control.appSha256 === parsed.appSha256 && control.mcpSha256 === parsed.mcpSha256))
12579
+ return false;
12580
+ }
12581
+ }
12582
+ return true;
12583
+ } catch {
12584
+ return false;
12585
+ }
12586
+ }
12587
+ function sourceInputUnchanged(input, cache, read2) {
12588
+ try {
12589
+ const parts = relative4(cache, input.pluginParent).split(sep4);
12590
+ if (parts.length !== 2 || parts.some((part) => !identifier2(part)) || parts[1] !== input.namespace || input.pluginId !== `${parts[1]}@${parts[0]}` || !codexPluginSourceIsConfigControlled({ id: input.pluginId, sourceType: "local", sourceSha256: input.sourceSha256 }) || input.sourceSha256 !== hashNativeJsonControls(JSON.stringify({ type: "local", path: input.sourceRoot })) || resolve9(input.sourceRoot) !== input.sourceRoot || !lstatSync12(input.sourceRoot).isDirectory() || realpathSync5(input.sourceRoot) !== input.sourceRoot || input.sourceRoot === cache || input.sourceRoot.startsWith(cache + sep4) || cache.startsWith(input.sourceRoot + sep4))
12591
+ return false;
12592
+ const path = join14(input.sourceRoot, ".codex-plugin/plugin.json"), stat = lstatSync12(path);
12593
+ if (!stat.isFile() || stat.size > 1024 * 1024 || realpathSync5(path) !== path)
12594
+ return false;
12595
+ const text2 = read2(path);
12596
+ hashNativeJsonControls(text2);
12597
+ const manifest = JSON.parse(text2);
12598
+ return Boolean(manifest && typeof manifest === "object" && !Array.isArray(manifest) && manifest.name === input.namespace);
12599
+ } catch {
12600
+ return false;
12601
+ }
12602
+ }
12603
+ function reviewedCodexPluginSourceRoots(cache, inputs, read2) {
12604
+ if (!Array.isArray(inputs) || inputs.length > 4096 || new Set(inputs.map((input) => input.pluginId)).size !== inputs.length || new Set(inputs.map((input) => input.sourceRoot)).size !== inputs.length || inputs.some((input) => !sourceInputUnchanged(input, cache, read2)))
12605
+ refuse2();
12606
+ const roots = inputs.map((input) => input.sourceRoot);
12607
+ if (roots.some((root) => roots.some((other) => root !== other && root.startsWith(other + sep4))))
12608
+ refuse2();
12609
+ return roots;
12610
+ }
12611
+ function isReviewedCodexPluginInactive(document2, cache, controls, pluginSettings, rules, read2) {
12612
+ if (!Array.isArray(controls) || controls.length > 4096 || !pluginSettings || typeof pluginSettings !== "object" || Array.isArray(pluginSettings) || !Array.isArray(rules))
12613
+ return false;
12614
+ try {
12615
+ const parsed = documentIdentity(document2, cache, read2), matches = controls.filter((control2) => control2.pluginParent === parsed.pluginParent && control2.namespace === parsed.namespace);
12616
+ if (matches.length !== 1 || !lstatSync12(document2).isFile() || realpathSync5(document2) !== document2 || realpathSync5(parsed.root) !== parsed.root || realpathSync5(parsed.pluginParent) !== parsed.pluginParent || realpathSync5(join14(parsed.root, ".codex-plugin/plugin.json")) !== join14(parsed.root, ".codex-plugin/plugin.json"))
12617
+ return false;
12618
+ const control = matches[0], parts = relative4(cache, parsed.pluginParent).split(sep4);
12619
+ if (parts.length !== 2 || parts.some((part) => !identifier2(part)) || parts[1] !== parsed.namespace || control.pluginId !== `${parts[1]}@${parts[0]}` || !codexPluginSourceIsConfigControlled({ ...control, id: control.pluginId }) || !Object.hasOwn(pluginSettings, control.pluginId) || pluginSettings[control.pluginId]?.enabled !== false)
12620
+ return false;
12621
+ return !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule?.enabled !== false);
12622
+ } catch {
12623
+ return false;
12624
+ }
12625
+ }
12626
+ function isReviewedCodexPluginSkillDisabled(document2, cache, controls, rules, read2) {
12627
+ if (!document2.startsWith(cache + sep4) || !Array.isArray(controls) || controls.length > 4096)
12628
+ return false;
12629
+ let parsed;
12630
+ try {
12631
+ parsed = identity3(document2, cache, read2);
12632
+ } catch {
12633
+ return false;
12634
+ }
12635
+ const reviewed = controls.find((item) => item.name === parsed.name && item.namespace === parsed.namespace && item.pluginParent === parsed.pluginParent && item.manifestSha256 === parsed.manifestSha256 && item.appSha256 === parsed.appSha256 && item.mcpSha256 === parsed.mcpSha256);
12636
+ if (!reviewed || reviewed.remotePluginId !== undefined && !remoteInstallationMatches(reviewed.pluginParent, reviewed.remotePluginId, read2))
12637
+ return false;
12638
+ return isCodexNativeSkillDisabled({ name: parsed.name, path: document2, pluginId: reviewed.pluginId, enabled: true }, rules);
12639
+ }
12640
+ var sha = (text2) => createHash9("sha256").update(text2).digest("hex");
12641
+ var EFFORT = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
12642
+ var VERBOSITY = new Set(["low", "medium", "high"]);
12643
+ function need3(value) {
12644
+ if (!value)
12645
+ throw new Error("Invalid Codex settings witness");
12646
+ }
12647
+ function numericTokens(text2) {
12648
+ const result = [];
12649
+ for (let at = 0;at < text2.length; ) {
12650
+ const char = text2[at];
12651
+ if (char === "#") {
12652
+ while (at < text2.length && text2[at] !== `
12653
+ `)
12654
+ at++;
12655
+ continue;
12656
+ }
12657
+ if (char === '"' || char === "'") {
12658
+ const quote3 = char, triple = text2.slice(at, at + 3) === quote3.repeat(3);
12659
+ at += triple ? 3 : 1;
12660
+ while (at < text2.length) {
12661
+ if (quote3 === '"' && text2[at] === "\\") {
12662
+ at += 2;
12663
+ continue;
12664
+ }
12665
+ if (text2.slice(at, at + (triple ? 3 : 1)) === quote3.repeat(triple ? 3 : 1)) {
12666
+ at += triple ? 3 : 1;
12667
+ break;
12668
+ }
12669
+ at++;
12670
+ }
12671
+ continue;
12672
+ }
12673
+ if (/[0-9+-]/.test(char)) {
12674
+ const start = at;
12675
+ while (at < text2.length && !/[\s,\]{}#=]/.test(text2[at]))
12676
+ at++;
12677
+ if (at > start) {
12678
+ result.push(text2.slice(start, at));
12679
+ continue;
12680
+ }
12681
+ }
12682
+ at++;
12683
+ }
12684
+ return result;
12685
+ }
12686
+ function canonical3(value, depth = 0, budget = { remaining: 65536 }) {
12687
+ need3(depth <= 32 && --budget.remaining >= 0);
12688
+ if (value === null)
12689
+ return ["null"];
12690
+ if (typeof value === "string")
12691
+ return ["string", value];
12692
+ if (typeof value === "boolean")
12693
+ return ["boolean", value];
12694
+ if (typeof value === "number") {
12695
+ need3(Number.isFinite(value));
12696
+ return ["number", String(value)];
12697
+ }
12698
+ if (value instanceof Date) {
12699
+ need3(Number.isFinite(value.getTime()));
12700
+ return ["date", value.toISOString()];
12701
+ }
12702
+ if (Array.isArray(value))
12703
+ return ["array", value.map((child) => canonical3(child, depth + 1, budget))];
12704
+ need3(value && typeof value === "object");
12705
+ return ["object", Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, child]) => [key, canonical3(child, depth + 1, budget)])];
12706
+ }
12707
+ function hashCodexSettingsReplacement(text2, budget = { remaining: 256 * 1024 * 1024 }) {
12708
+ need3(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
12709
+ budget.remaining -= Buffer.byteLength(text2);
12710
+ let config;
12711
+ try {
12712
+ config = Bun.TOML.parse(text2);
12713
+ } catch {
12714
+ throw new Error("Invalid Codex settings witness");
12715
+ }
12716
+ need3(config && typeof config === "object" && !Array.isArray(config));
12717
+ if (Object.hasOwn(config, "model")) {
12718
+ need3(typeof config.model === "string" && /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(config.model));
12719
+ delete config.model;
12720
+ }
12721
+ for (const [key, values] of [["model_reasoning_effort", EFFORT], ["model_verbosity", VERBOSITY]])
12722
+ if (Object.hasOwn(config, key)) {
12723
+ need3(typeof config[key] === "string" && values.has(config[key]));
12724
+ delete config[key];
12725
+ }
12726
+ return sha(`hasna.skills.codex-settings.v1\x00${JSON.stringify([canonical3(config), numericTokens(text2)])}`);
12727
+ }
12728
+ function captureCodexSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
12729
+ const text2 = readNativeSettingsWitnessFile(path, budget, "config.toml");
12730
+ return { path, hashMode: "codex-settings-v1", sha256: hashCodexSettingsReplacement(text2) };
12731
+ }
12732
+ function record2(value) {
12733
+ return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
12734
+ }
12735
+ function localStdio(name, row) {
12736
+ return name !== "codex_apps" && name !== "_codex_apps" && isCodexLocalStdioMcpServer(name, row);
12737
+ }
12738
+ function stripEmptyParent(config) {
12739
+ if (record2(config.mcp_servers) && !Object.keys(config.mcp_servers).length)
12740
+ delete config.mcp_servers;
12741
+ return config;
12742
+ }
12743
+ function projectRegularCodexMcp(config, source) {
12744
+ const unchanged2 = { config, numericSource: source };
12745
+ if (!record2(config.mcp_servers))
12746
+ return unchanged2;
12747
+ const candidates = Object.entries(config.mcp_servers).filter(([name, row]) => localStdio(name, row)).map(([name]) => name);
12748
+ if (!candidates.length)
12749
+ return unchanged2;
12750
+ const headers = [];
12751
+ let attempts = 0;
12752
+ for (const match of source.matchAll(/^[ \t]*\[{1,2}[^\r\n]*\]{1,2}[ \t]*(?:#[^\r\n]*)?(?:\r?\n|$)/gm)) {
12753
+ if (++attempts > 256)
12754
+ return unchanged2;
12755
+ try {
12756
+ Bun.TOML.parse(source.slice(0, match.index));
12757
+ } catch {
12758
+ continue;
12759
+ }
12760
+ let header;
12761
+ try {
12762
+ header = Bun.TOML.parse(match[0]);
12763
+ } catch {
12764
+ return unchanged2;
12765
+ }
12766
+ const path = [];
12767
+ while (record2(header) && Object.keys(header).length === 1) {
12768
+ const key = Object.keys(header)[0];
12769
+ path.push(key);
12770
+ header = header[key];
12771
+ }
12772
+ headers.push({ start: match.index, path });
12773
+ }
12774
+ const names = candidates.filter((name) => headers.filter((header) => header.path.length === 2 && header.path[0] === "mcp_servers" && header.path[1] === name).length === 1);
12775
+ if (!names.length)
12776
+ return unchanged2;
12777
+ const selected = headers.map((header, index) => ({ ...header, end: headers[index + 1]?.start ?? source.length })).filter((header) => header.path[0] === "mcp_servers" && names.includes(header.path[1]));
12778
+ let numericSource = source;
12779
+ for (const range of selected.reverse())
12780
+ numericSource = numericSource.slice(0, range.start) + numericSource.slice(range.end);
12781
+ const expected = structuredClone(config);
12782
+ for (const name of names)
12783
+ delete expected.mcp_servers[name];
12784
+ stripEmptyParent(expected);
12785
+ try {
12786
+ if (!isDeepStrictEqual2(stripEmptyParent(Bun.TOML.parse(numericSource)), expected))
12787
+ return unchanged2;
12788
+ } catch {
12789
+ return unchanged2;
12790
+ }
12791
+ return { config: expected, numericSource };
12792
+ }
12793
+ var sha2 = (text2) => createHash10("sha256").update(text2).digest("hex");
12794
+ var EFFORT2 = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
12795
+ var VERBOSITY2 = new Set(["low", "medium", "high"]);
12796
+ var MODEL = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
12797
+ var CODEX_DISCOVERY_PROJECTION_FIELDS = ["plugins", "marketplaces", "skills"];
12798
+ var INFERENCE = ["model", "model_reasoning_effort", "model_verbosity"];
12799
+ function need4(value) {
12800
+ if (!value)
12801
+ throw new Error("Invalid Codex settings witness");
12802
+ }
12803
+ function isRecord3(value) {
12804
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
12805
+ }
12806
+ function isDate(value) {
12807
+ return value instanceof Date;
12808
+ }
12809
+ function numericTokens2(text2) {
12810
+ const result = [];
12811
+ for (let at = 0;at < text2.length; ) {
12812
+ const char = text2[at];
12813
+ if (char === "#") {
12814
+ while (at < text2.length && text2[at] !== `
12815
+ `)
12816
+ at++;
12817
+ continue;
12818
+ }
12819
+ if (char === '"' || char === "'") {
12820
+ const quote3 = char, triple = text2.slice(at, at + 3) === quote3.repeat(3);
12821
+ at += triple ? 3 : 1;
12822
+ while (at < text2.length) {
12823
+ if (quote3 === '"' && text2[at] === "\\") {
12824
+ at += 2;
12825
+ continue;
12826
+ }
12827
+ if (text2.slice(at, at + (triple ? 3 : 1)) === quote3.repeat(triple ? 3 : 1)) {
12828
+ at += triple ? 3 : 1;
12829
+ break;
12830
+ }
12831
+ at++;
12832
+ }
12833
+ continue;
12834
+ }
12835
+ if (/[0-9+-]/.test(char)) {
12836
+ const start = at;
12837
+ while (at < text2.length && !/[\s,\]{}#=]/.test(text2[at]))
12838
+ at++;
12839
+ if (at > start) {
12840
+ result.push(text2.slice(start, at));
12841
+ continue;
12842
+ }
12843
+ }
12844
+ at++;
12845
+ }
12846
+ return result.sort();
12847
+ }
12848
+ function canonical4(value, depth = 0, budget = { remaining: 65536 }) {
12849
+ need4(depth <= 32 && --budget.remaining >= 0);
12850
+ if (value === null)
12851
+ return ["null"];
12852
+ if (typeof value === "string")
12853
+ return ["string", value];
12854
+ if (typeof value === "boolean")
12855
+ return ["boolean", value];
12856
+ if (typeof value === "number") {
12857
+ need4(Number.isFinite(value));
12858
+ return ["number", String(value)];
12859
+ }
12860
+ if (isDate(value)) {
12861
+ need4(Number.isFinite(value.getTime()));
12862
+ return ["date", value.toISOString()];
12863
+ }
12864
+ if (Array.isArray(value))
12865
+ return ["array", value.map((child) => canonical4(child, depth + 1, budget))];
12866
+ need4(isRecord3(value));
12867
+ return ["object", Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, child]) => [key, canonical4(child, depth + 1, budget)])];
12868
+ }
12869
+ function normalize3(config, version = 2) {
12870
+ if (isRecord3(config.hooks)) {
12871
+ delete config.hooks.state;
12872
+ if (!Object.keys(config.hooks).length)
12873
+ delete config.hooks;
12874
+ }
12875
+ if (isRecord3(config.skills) && Array.isArray(config.skills.config)) {
12876
+ const skills = config.skills;
12877
+ const retained = skills.config.filter((entry) => !disablesOneSkill(entry));
12878
+ if (retained.length !== skills.config.length) {
12879
+ if (retained.length)
12880
+ skills.config = retained;
12881
+ else
12882
+ delete skills.config;
12883
+ }
12884
+ if (!Object.keys(skills).length)
12885
+ delete config.skills;
12886
+ }
12887
+ inference(config, version);
12888
+ if (isRecord3(config.profiles)) {
12889
+ for (const profile of Object.values(config.profiles))
12890
+ if (isRecord3(profile))
12891
+ inference(profile, version);
12892
+ }
12893
+ }
12894
+ function disablesOneSkill(entry) {
12895
+ if (!isRecord3(entry) || entry.enabled !== false)
12896
+ return false;
12897
+ const keys = Object.keys(entry);
12898
+ if (!keys.every((key) => key === "path" || key === "name" || key === "enabled"))
12899
+ return false;
12900
+ const selector = ["path", "name"].filter((key) => Object.hasOwn(entry, key));
12901
+ return selector.length === 1 && typeof entry[selector[0]] === "string" && entry[selector[0]].length > 0;
12902
+ }
12903
+ function inference(target, version) {
12904
+ const keys = version === 3 ? [...INFERENCE, "service_tier", "plan_mode_reasoning_effort", "personality"] : INFERENCE;
12905
+ for (const key of keys)
12906
+ if (Object.hasOwn(target, key)) {
12907
+ const value = target[key];
12908
+ if (key === "model")
12909
+ need4(typeof value === "string" && MODEL.test(value));
12910
+ else if (key === "personality")
12911
+ need4(typeof value === "string" && ["none", "friendly", "pragmatic"].includes(value));
12912
+ else if (version === 3 && key !== "model_verbosity") {
12913
+ need4(typeof value === "string" && value.length <= 128 && !/[\x00-\x1f\x7f]/.test(value));
12914
+ if (key !== "service_tier")
12915
+ need4(value.length > 0);
12916
+ } else
12917
+ need4(typeof value === "string" && (key === "model_reasoning_effort" ? EFFORT2.has(value) : VERBOSITY2.has(value)));
12918
+ delete target[key];
12919
+ }
12920
+ }
12921
+ function settingsDigest2(text2, version = 2) {
12922
+ let config;
12923
+ try {
12924
+ config = Bun.TOML.parse(text2);
12925
+ } catch {
12926
+ throw new Error("Invalid Codex settings witness");
12927
+ }
12928
+ need4(isRecord3(config));
12929
+ const projected = version === 3 ? projectRegularCodexMcp(config, text2) : { config, numericSource: text2 };
12930
+ normalize3(projected.config, version);
12931
+ return sha2(`hasna.skills.codex-settings.v${version}\x00${JSON.stringify([canonical4(projected.config), numericTokens2(projected.numericSource)])}`);
12932
+ }
12933
+ function hashCodexSettingsReplacementV2(text2, budget = { remaining: 256 * 1024 * 1024 }) {
12934
+ need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
12935
+ budget.remaining -= Buffer.byteLength(text2);
12936
+ return settingsDigest2(text2);
12937
+ }
12938
+ function captureCodexSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
12939
+ const text2 = readNativeSettingsWitnessFile(path, budget, "config.toml");
12940
+ return { path, hashMode: "codex-settings-v2", sha256: settingsDigest2(text2) };
12941
+ }
12942
+ function readCodexSettingsPreimage(path, budget = { remaining: 2 * 1024 * 1024 }) {
12943
+ return readNativeSettingsWitnessFile(path, budget, "config.toml");
12944
+ }
12945
+ function upgradeCodexSettingsWitness(previous, reviewedSettingsPath) {
12946
+ need4(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes" || previous.hashMode === "codex-settings-v1") && /^[a-f0-9]{64}$/.test(previous.sha256));
12947
+ const budget = { remaining: 2 * 1024 * 1024 };
12948
+ const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget, "config.toml"), current2 = readNativeSettingsWitnessFile(previous.path, budget, "config.toml");
12949
+ need4((previous.hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(before) : sha2(before)) === previous.sha256);
12950
+ const sha2562 = settingsDigest2(current2);
12951
+ if (settingsDigest2(before) !== sha2562)
12952
+ throw new Error("Codex non-preference settings changed; explicit discovery review required");
12953
+ return { path: previous.path, hashMode: "codex-settings-v2", sha256: sha2562 };
12954
+ }
12955
+ function hashCodexSettingsReplacementV3(text2, budget = { remaining: 256 * 1024 * 1024 }) {
12956
+ need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
12957
+ budget.remaining -= Buffer.byteLength(text2);
12958
+ return settingsDigest2(text2, 3);
12959
+ }
12960
+ function captureCodexSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
12961
+ return { path, hashMode: "codex-settings-v3", sha256: settingsDigest2(readNativeSettingsWitnessFile(path, budget, "config.toml"), 3) };
12962
+ }
12963
+ function upgradeCodexSettingsWitnessV3(previous, reviewedSettingsPath) {
12964
+ need4(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || ["bytes", "codex-settings-v1", "codex-settings-v2"].includes(previous.hashMode)) && /^[a-f0-9]{64}$/.test(previous.sha256));
12965
+ const budget = { remaining: 2 * 1024 * 1024 };
12966
+ const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget, "config.toml"), current2 = readNativeSettingsWitnessFile(previous.path, budget, "config.toml");
12967
+ const original = previous.hashMode === "codex-settings-v2" ? settingsDigest2(before) : previous.hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(before) : sha2(before);
12968
+ need4(original === previous.sha256);
12969
+ const sha2562 = settingsDigest2(current2, 3);
12970
+ if (settingsDigest2(before, 3) !== sha2562)
12971
+ throw new Error("Codex settings outside the reviewed V3 contract changed; explicit discovery review required");
12972
+ return { path: previous.path, hashMode: "codex-settings-v3", sha256: sha2562 };
12973
+ }
12974
+ function refusal(reason = "is unreadable") {
12975
+ return new SkillSelectionError("INVALID_AGENT_POLICY", `The Skills agent loading policy ${reason}; refusing legacy fallback.`);
12976
+ }
12977
+ function parseManagedSkillPolicy(text2) {
12978
+ if (Buffer.byteLength(text2) > AGENT_POLICY_LIMITS.bytes)
12979
+ throw refusal("exceeds its size limit");
12980
+ let value;
12981
+ try {
12982
+ value = JSON.parse(text2);
12983
+ } catch {
12984
+ throw refusal();
12985
+ }
12986
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.loading !== "cli" || value.version !== undefined && value.version !== 1)
12987
+ throw refusal();
12988
+ if (value.profileId !== undefined && (typeof value.profileId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value.profileId) || value.profileId.includes("..")))
12989
+ throw refusal();
12990
+ try {
12991
+ assertAgentPolicyCollections(value);
12992
+ } catch {
12993
+ throw refusal("has invalid collection bounds");
12994
+ }
12995
+ return value;
12996
+ }
12997
+ function serializeManagedSkillPolicy(value) {
12998
+ const text2 = `${JSON.stringify(value, null, 2)}
12999
+ `;
13000
+ parseManagedSkillPolicy(text2);
13001
+ return text2;
13002
+ }
13003
+ function readManagedSkillPolicySnapshot(dataDir22 = getDataDirReadOnly()) {
13004
+ const path = join15(dataDir22, "agent-policy.json");
13005
+ let descriptor, observed = false;
13006
+ try {
13007
+ const stat = lstatSync13(path);
13008
+ observed = true;
13009
+ if (!stat.isFile() || stat.isSymbolicLink())
13010
+ throw refusal();
13011
+ if (stat.size > AGENT_POLICY_LIMITS.bytes)
13012
+ throw refusal("exceeds its size limit");
13013
+ descriptor = openSync7(path, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0) | constants6.O_NONBLOCK);
13014
+ const opened = fstatSync7(descriptor);
13015
+ if (!opened.isFile())
13016
+ throw refusal();
13017
+ if (opened.size > AGENT_POLICY_LIMITS.bytes)
13018
+ throw refusal("exceeds its size limit");
13019
+ const bytes = Buffer.allocUnsafe(AGENT_POLICY_LIMITS.bytes + 1);
13020
+ let length = 0;
13021
+ while (length < bytes.length) {
13022
+ const count = readSync5(descriptor, bytes, length, bytes.length - length, null);
13023
+ if (count === 0)
13024
+ break;
13025
+ length += count;
13026
+ }
13027
+ if (length > AGENT_POLICY_LIMITS.bytes)
13028
+ throw refusal("exceeds its size limit");
13029
+ const text2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
13030
+ return { text: text2, value: parseManagedSkillPolicy(text2) };
13031
+ } catch (error) {
13032
+ if (error.code === "ENOENT" && !observed) {
13033
+ if (lstatSync13(path, { throwIfNoEntry: false }) === undefined)
13034
+ return null;
13035
+ }
13036
+ if (error instanceof SkillSelectionError)
13037
+ throw error;
13038
+ throw refusal();
13039
+ } finally {
13040
+ if (descriptor !== undefined)
13041
+ closeSync7(descriptor);
13042
+ }
13043
+ }
13044
+ var CLI_BRIDGE_NAME = "skills-cli";
13045
+ var CLI_BRIDGE_VERSION = 1;
13046
+ var markdown = `---
13047
+ name: skills-cli
13048
+ description: Discover, load, author, and run versioned skills through the Skills CLI when a task needs reusable instructions or an executable skill.
13049
+ ---
13050
+
13051
+ Use the Skills CLI as the skill authority for this station.
13052
+
13053
+ - Discover available skills with \`skills list --json\` or \`skills search <query> --json\`.
13054
+ - Read selected instructions with \`skills load <slug>\`. Use \`skills sync\` to refresh the station's shared selection profile.
13055
+ - Create local drafts with \`skills new <name> --kind instruction\` or \`--kind executable\` in the Skills-owned source directory. Validate with \`skills validate <name>\`; use \`skills push --help\` for explicit versioned publication.
13056
+ - Run a skill only when the user's task calls for execution. Use \`skills run --help\` and select the intended local or cloud target explicitly.
13057
+
13058
+ This bridge contains no skill payloads. Do not copy skill content into agent-native skill directories or load other native skill copies. If a Skills command refuses authority, selection, integrity, or native drift, report that refusal and use its repair guidance; do not substitute bundled or stale local content. Explicit local draft authoring remains available.
13059
+ `;
13060
+ var CLI_BRIDGE_DIGEST = createHash11("sha256").update(markdown).digest("hex");
13061
+ var CLI_BRIDGE_FILES = Object.freeze({
13062
+ "SKILL.md": markdown,
13063
+ ".hasna-skills.json": `${JSON.stringify({ managedBy: "@hasna/skills", kind: "cli-bridge", version: CLI_BRIDGE_VERSION, protocol: "selection-v1", contentSha256: CLI_BRIDGE_DIGEST }, null, 2)}
13064
+ `
13065
+ });
13066
+ function isOwnedCliBridge(path, expectedPaths) {
13067
+ if (!expectedPaths.includes(path))
13068
+ return false;
13069
+ try {
13070
+ if (!lstatSync14(path).isDirectory())
13071
+ return false;
13072
+ if (JSON.stringify(readdirSync7(path).sort()) !== JSON.stringify(Object.keys(CLI_BRIDGE_FILES).sort()))
13073
+ return false;
13074
+ return Object.entries(CLI_BRIDGE_FILES).every(([name, content]) => {
13075
+ const file = join16(path, name), stat = lstatSync14(file);
13076
+ return stat.isFile() && !stat.isSymbolicLink() && stat.size === Buffer.byteLength(content) && readFileSync11(file, "utf8") === content;
13077
+ });
13078
+ } catch {
13079
+ return false;
13080
+ }
13081
+ }
13082
+ var sha3 = (data) => createHash12("sha256").update(data).digest("hex");
12292
13083
  var ROOTS = NATIVE_SKILL_ROOTS;
13084
+ function hasNoDarwinAcl(fd) {
13085
+ try {
13086
+ const { dlopen, read: read2 } = __require("bun:ffi");
13087
+ const library = dlopen("/usr/lib/libSystem.B.dylib", {
13088
+ acl_get_fd: { args: ["i32"], returns: "ptr" },
13089
+ acl_free: { args: ["ptr"], returns: "i32" },
13090
+ __error: { args: [], returns: "ptr" }
13091
+ });
13092
+ try {
13093
+ const errno = library.symbols.__error();
13094
+ if (!errno)
13095
+ return false;
13096
+ const acl = library.symbols.acl_get_fd(fd);
13097
+ if (acl) {
13098
+ library.symbols.acl_free(acl);
13099
+ return false;
13100
+ }
13101
+ return (acl === null || acl === 0) && read2.i32(errno) === 2;
13102
+ } finally {
13103
+ library.close();
13104
+ }
13105
+ } catch {
13106
+ return false;
13107
+ }
13108
+ }
13109
+ function isSystemRootAlias(path) {
13110
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(path))
13111
+ return false;
13112
+ const target = `/private${path}`, link = `private${path}`;
13113
+ const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
13114
+ try {
13115
+ const before = lstatSync15(path, { bigint: true });
13116
+ if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
13117
+ return false;
13118
+ const directories = ["/", "/private", target].map((directory) => {
13119
+ const stat = lstatSync15(directory, { bigint: true });
13120
+ if (!stat.isDirectory() || stat.uid !== 0n)
13121
+ throw new Error("Untrusted system alias directory");
13122
+ if (directory !== "/private/tmp") {
13123
+ if ((stat.mode & 0o022n) !== 0n)
13124
+ throw new Error("Writable system alias directory");
13125
+ } else if ((stat.mode & 0o1000n) === 0n)
13126
+ throw new Error("Unprotected system temporary directory");
13127
+ const fd = openSync8(directory, constants7.O_RDONLY | constants7.O_DIRECTORY | constants7.O_NOFOLLOW);
13128
+ try {
13129
+ if (identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat) || !hasNoDarwinAcl(fd) || identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat))
13130
+ throw new Error("Unverified system alias directory ACL");
13131
+ } finally {
13132
+ closeSync8(fd);
13133
+ }
13134
+ return { directory, identity: identity4(stat) };
13135
+ });
13136
+ return realpathSync6(path) === target && readlinkSync4(path) === link && identity4(lstatSync15(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync15(item.directory, { bigint: true })) === item.identity);
13137
+ } catch {
13138
+ return false;
13139
+ }
13140
+ }
13141
+ function canonicalSystemPath(path) {
13142
+ const absolute = resolve10(path), root = `/${absolute.split(sep5)[1]}`;
13143
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync15(root, { throwIfNoEntry: false })?.isSymbolicLink())
13144
+ return absolute;
13145
+ if (!isSystemRootAlias(root))
13146
+ throw new Error(`Refusing symlink path: ${root}`);
13147
+ return `/private${absolute}`;
13148
+ }
12293
13149
  function assertSafePath(path) {
12294
- let cursor = resolve9(path);
13150
+ let cursor = resolve10(path);
12295
13151
  while (true) {
12296
- if (lstatSync14(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
13152
+ if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
12297
13153
  throw new Error(`Refusing symlink path: ${cursor}`);
12298
- const parent = dirname10(cursor);
13154
+ const parent = dirname11(cursor);
12299
13155
  if (parent === cursor)
12300
13156
  return;
12301
13157
  cursor = parent;
@@ -12303,17 +13159,17 @@ function assertSafePath(path) {
12303
13159
  }
12304
13160
  function readRootAlias(home, agent) {
12305
13161
  assertSafePath(home);
12306
- const realHome = realpathSync32(home), alias = join16(realHome, `.${agent}`);
12307
- const stat = lstatSync14(alias, { throwIfNoEntry: false, bigint: true });
13162
+ const realHome = realpathSync6(home), alias = join17(realHome, `.${agent}`);
13163
+ const stat = lstatSync15(alias, { throwIfNoEntry: false, bigint: true });
12308
13164
  if (!stat?.isSymbolicLink())
12309
13165
  throw new Error(`Agent root alias changed after planning: ${alias}`);
12310
- const link = readlinkSync2(alias), targetPath = resolve9(dirname10(alias), link);
13166
+ const link = readlinkSync4(alias), targetPath = resolve10(dirname11(alias), link);
12311
13167
  assertSafePath(targetPath);
12312
- const targetStat = lstatSync14(targetPath, { throwIfNoEntry: false });
13168
+ const targetStat = lstatSync15(targetPath, { throwIfNoEntry: false });
12313
13169
  if (!targetStat?.isDirectory())
12314
13170
  throw new Error(`Agent root alias must target an existing directory: ${alias}`);
12315
- const target = realpathSync32(targetPath), inside = relative4(realHome, target);
12316
- if (!inside || inside === ".." || inside.startsWith(`..${sep4}`) || isAbsolute9(inside))
13171
+ const target = realpathSync6(targetPath), inside = relative5(realHome, target);
13172
+ if (!inside || inside === ".." || inside.startsWith(`..${sep5}`) || isAbsolute11(inside))
12317
13173
  throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
12318
13174
  return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
12319
13175
  }
@@ -12322,19 +13178,46 @@ function rootAliases(home, allowed = false) {
12322
13178
  return [];
12323
13179
  const result = [];
12324
13180
  for (const agent of ["claude", "codex"])
12325
- if (lstatSync14(join16(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
13181
+ if (lstatSync15(join17(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
12326
13182
  result.push(readRootAlias(home, agent));
12327
- if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep4) || other.target.startsWith(item.target + sep4))))
13183
+ if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep5) || other.target.startsWith(item.target + sep5))))
12328
13184
  throw new Error("Agent root alias targets must not overlap");
12329
13185
  return result;
12330
13186
  }
12331
13187
  function canonicalAgentPath(path, aliases) {
12332
- const absolute = resolve9(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep4));
12333
- return binding ? join16(binding.target, relative4(binding.alias, absolute)) : absolute;
13188
+ const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
13189
+ return binding ? join17(binding.target, relative5(binding.alias, absolute)) : absolute;
13190
+ }
13191
+ function disabledCodexSkillPaths(config, aliases) {
13192
+ const skills = config.skills;
13193
+ if (!skills || typeof skills !== "object" || Array.isArray(skills))
13194
+ return [];
13195
+ const rows = skills.config;
13196
+ if (!Array.isArray(rows))
13197
+ return [];
13198
+ if (rows.some((entry) => !entry || typeof entry !== "object" || Array.isArray(entry) || typeof entry.enabled !== "boolean" || entry.path !== undefined === (entry.name !== undefined) || entry.path !== undefined && (typeof entry.path !== "string" || !isAbsolute11(entry.path)) || entry.name !== undefined && (typeof entry.name !== "string" || !entry.name.trim())))
13199
+ return [];
13200
+ if (rows.some((entry) => entry.name !== undefined && entry.enabled !== false))
13201
+ return [];
13202
+ const settings = new Map;
13203
+ for (const entry of rows) {
13204
+ if (typeof entry.path !== "string" || !isAbsolute11(entry.path))
13205
+ continue;
13206
+ let document2;
13207
+ try {
13208
+ document2 = realpathSync6(canonicalAgentPath(entry.path, aliases));
13209
+ } catch {
13210
+ continue;
13211
+ }
13212
+ if (!document2.endsWith(`${sep5}SKILL.md`))
13213
+ continue;
13214
+ settings.set(document2, [...settings.get(document2) ?? [], entry.enabled === false]);
13215
+ }
13216
+ return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) => dirname11(document2));
12334
13217
  }
12335
13218
  function recheckRootAliases(aliases) {
12336
13219
  for (const binding of aliases) {
12337
- if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join16(binding.home, `.${binding.agent}`))
13220
+ if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join17(binding.home, `.${binding.agent}`))
12338
13221
  throw new Error("Invalid agent root alias binding");
12339
13222
  const current2 = readRootAlias(binding.home, binding.agent);
12340
13223
  if (JSON.stringify(current2) !== JSON.stringify(binding))
@@ -12343,12 +13226,12 @@ function recheckRootAliases(aliases) {
12343
13226
  }
12344
13227
  function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
12345
13228
  assertSafePath(path);
12346
- const before = lstatSync14(path);
13229
+ const before = lstatSync15(path);
12347
13230
  if (!before.isFile() || before.size > maximum)
12348
13231
  throw new Error("Unsupported or oversized native skill file");
12349
- const descriptor = openSync10(path, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
13232
+ const descriptor = openSync8(path, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
12350
13233
  try {
12351
- const opened = fstatSync10(descriptor);
13234
+ const opened = fstatSync8(descriptor);
12352
13235
  if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size)
12353
13236
  throw new Error("Native skill file changed while reading");
12354
13237
  const bytes = Buffer.allocUnsafe(before.size + 1);
@@ -12359,26 +13242,26 @@ function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
12359
13242
  break;
12360
13243
  length += count;
12361
13244
  }
12362
- const after = fstatSync10(descriptor);
12363
- if (length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
13245
+ const after = fstatSync8(descriptor), current2 = lstatSync15(path);
13246
+ if (current2.dev !== opened.dev || current2.ino !== opened.ino || current2.size !== after.size || current2.mtimeMs !== after.mtimeMs || current2.ctimeMs !== after.ctimeMs || length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
12364
13247
  throw new Error("Native skill file changed while reading");
12365
13248
  return bytes.subarray(0, length);
12366
13249
  } finally {
12367
- closeSync10(descriptor);
13250
+ closeSync8(descriptor);
12368
13251
  }
12369
13252
  }
12370
13253
  function treeHash(root) {
12371
- const hash2 = createHash10("sha256");
13254
+ const hash2 = createHash12("sha256");
12372
13255
  let bytes = 0, files = 0;
12373
13256
  function visit2(path) {
12374
- const stat = lstatSync14(path);
13257
+ const stat = lstatSync15(path);
12375
13258
  if (stat.isSymbolicLink())
12376
13259
  throw new Error(`Refusing symlink in skill: ${path}`);
12377
- const rel = relative4(root, path);
13260
+ const rel = relative5(root, path);
12378
13261
  if (stat.isDirectory()) {
12379
13262
  hash2.update(`d\x00${rel}\x00`);
12380
- for (const name of readdirSync7(path).sort())
12381
- visit2(join16(path, name));
13263
+ for (const name of readdirSync8(path).sort())
13264
+ visit2(join17(path, name));
12382
13265
  return;
12383
13266
  }
12384
13267
  if (!stat.isFile())
@@ -12397,11 +13280,11 @@ function treeHash(root) {
12397
13280
  function projectAncestorDirectories(projects) {
12398
13281
  const directories = new Set;
12399
13282
  for (const project of projects) {
12400
- for (let path = resolve9(project), depth = 0;; depth++) {
13283
+ for (let path = canonicalSystemPath(project), depth = 0;; depth++) {
12401
13284
  if (depth >= 100)
12402
13285
  throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
12403
13286
  directories.add(path);
12404
- const parent = dirname10(path);
13287
+ const parent = dirname11(path);
12405
13288
  if (parent === path)
12406
13289
  break;
12407
13290
  path = parent;
@@ -12410,16 +13293,26 @@ function projectAncestorDirectories(projects) {
12410
13293
  return [...directories];
12411
13294
  }
12412
13295
  function inventoryNativeSkills(home = homedir22(), options = {}) {
13296
+ home = canonicalSystemPath(home);
13297
+ if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) || resolve10(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
13298
+ throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
13299
+ if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) || resolve10(path) !== path))
13300
+ throw new Error("Disabled vendor paths must be exact absolute paths");
13301
+ const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
13302
+ const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
12413
13303
  const aliases = rootAliases(home, options.allowRootAliases);
12414
13304
  const selectedAgents = options.agents ? new Set(options.agents) : undefined;
12415
13305
  const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
12416
13306
  const rootDefinitions = selectedAgents ? ROOTS.filter(([agent, path]) => selectedAgents.has(agent) || selectedAgents.has("hermes") && agent === "codex" && path === ".agents/skills") : ROOTS;
12417
- const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join16(home, path), aliases)]);
12418
- const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join16(home, adapter.root, CLI_BRIDGE_NAME), aliases));
13307
+ const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join17(home, path), aliases)]);
13308
+ const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join17(home, adapter.root, CLI_BRIDGE_NAME), aliases));
12419
13309
  for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
12420
13310
  for (const [agent, path] of rootDefinitions)
12421
- roots.push([agent, canonicalAgentPath(join16(project, path), aliases)]);
13311
+ roots.push([agent, canonicalAgentPath(join17(project, path), aliases)]);
12422
13312
  }
13313
+ const installationInputs = options.codexInstallationInputRoots ?? [];
13314
+ if (installationInputs.some((input) => resolve10(input) !== input || realpathSync6(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep5) || input.startsWith(path + sep5)))))
13315
+ throw new Error("Native installation input overlaps an unconditional skill-loading root");
12423
13316
  const entries = [], seen = new Set;
12424
13317
  const cacheScans = new Map, maxAliasProofEntries = 1e4;
12425
13318
  let discoveryEntries = 0, discoveryPathBytes = 0;
@@ -12430,42 +13323,59 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
12430
13323
  if (discoveryPathBytes > 4 * 1024 * 1024)
12431
13324
  throw new Error("Native skill discovery metadata limit exceeded");
12432
13325
  }
12433
- function emptySiblingCacheAlias(path, parent) {
12434
- const link = readlinkSync2(path), target = resolve9(parent, link);
12435
- if (dirname10(target) !== parent || (isAbsolute9(link) ? link !== target : dirname10(link) !== "."))
13326
+ let reviewedCacheAliasFound = false;
13327
+ function verifiedSiblingCacheAlias(agent, path, parent) {
13328
+ const link = readlinkSync4(path), lexicalTarget = resolve10(parent, link);
13329
+ if (isAbsolute11(link) ? link !== lexicalTarget : dirname11(link) !== ".")
12436
13330
  return false;
12437
- if (!lstatSync14(target, { throwIfNoEntry: false })?.isDirectory())
13331
+ const target = canonicalSystemPath(lexicalTarget);
13332
+ if (dirname11(target) !== parent)
13333
+ return false;
13334
+ if (!lstatSync15(target, { throwIfNoEntry: false })?.isDirectory())
12438
13335
  return false;
12439
13336
  const scan = cacheScans.get(target);
12440
- return Boolean(scan?.complete && !scan.hasSkills && scan.entries <= maxAliasProofEntries);
13337
+ if (!scan?.complete || scan.entries > maxAliasProofEntries)
13338
+ return false;
13339
+ if (scan.hasSkills) {
13340
+ const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep5)));
13341
+ if (path === reviewedCacheAlias && targetSkills.length > 0)
13342
+ reviewedCacheAliasFound = true;
13343
+ if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
13344
+ return true;
13345
+ if (path !== reviewedCacheAlias)
13346
+ return false;
13347
+ }
13348
+ return true;
12441
13349
  }
12442
- function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false) {
13350
+ function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false, configuredRoot = false) {
12443
13351
  if (!admitted)
12444
13352
  admit(path);
12445
13353
  const scan = { complete: true, hasSkills: false, entries: 1 };
12446
13354
  assertSafePath(path);
13355
+ if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${sep5}`)))
13356
+ return scan;
12447
13357
  if (!existsSync82(path))
12448
13358
  return scan;
12449
- const stat = lstatSync14(path);
13359
+ const stat = lstatSync15(path);
12450
13360
  if (stat.isFile())
12451
13361
  return scan;
12452
13362
  if (!stat.isDirectory())
12453
13363
  throw new Error(`Unsupported native discovery entry: ${path}`);
12454
- if (existsSync82(join16(path, "SKILL.md"))) {
13364
+ if (existsSync82(join17(path, "SKILL.md"))) {
12455
13365
  scan.hasSkills = true;
12456
13366
  if (seen.has(path))
12457
13367
  return scan;
12458
13368
  seen.add(path);
12459
13369
  let managed = false;
12460
- const marker = join16(path, ".hasna-skills.json");
13370
+ const marker = join17(path, ".hasna-skills.json");
12461
13371
  if (existsSync82(marker)) {
12462
13372
  try {
12463
- managed = JSON.parse(readFileSync11(marker, "utf8")).managedBy === "@hasna/skills";
13373
+ managed = JSON.parse(readFileSync12(marker, "utf8")).managedBy === "@hasna/skills";
12464
13374
  } catch {}
12465
13375
  }
12466
13376
  const bridge = !vendor && isOwnedCliBridge(path, bridgePaths);
12467
- const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep4));
12468
- entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join16(home, ".codex", "skills", ".system"), aliases) + sep4) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve9(home) } : {}, ...rootAlias ? { rootAlias } : {} });
13377
+ const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep5));
13378
+ entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join17(home, ".codex", "skills", ".system"), aliases) + sep5) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve10(home) } : {}, ...rootAlias ? { rootAlias } : {} });
12469
13379
  return scan;
12470
13380
  }
12471
13381
  if (depth > (vendor ? 32 : 3))
@@ -12474,7 +13384,7 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
12474
13384
  try {
12475
13385
  let child;
12476
13386
  while ((child = directory.readSync()) !== null) {
12477
- admit(join16(path, child.name));
13387
+ admit(join17(path, child.name));
12478
13388
  children.push(child);
12479
13389
  }
12480
13390
  } finally {
@@ -12486,20 +13396,20 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
12486
13396
  for (const { name } of children) {
12487
13397
  if (name === "node_modules")
12488
13398
  continue;
12489
- if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && !(options.guardHermes && `${path}${sep4}`.includes(`${sep4}.agents${sep4}skills${sep4}`)))
13399
+ if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && !(options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`)))
12490
13400
  continue;
12491
13401
  const isVendor = vendor || name === ".system";
12492
13402
  if (isVendor && !options.includeVendor)
12493
13403
  continue;
12494
- const child = join16(path, name);
12495
- if (isVendor && lstatSync14(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
12496
- const target = statSync7(child, { throwIfNoEntry: false });
12497
- if (target?.isFile() || pluginCache && target?.isDirectory() && emptySiblingCacheAlias(child, path)) {
13404
+ const child = join17(path, name);
13405
+ if (isVendor && lstatSync15(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
13406
+ const target = statSync8(child, { throwIfNoEntry: false });
13407
+ if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
12498
13408
  scan.entries++;
12499
13409
  continue;
12500
13410
  }
12501
13411
  }
12502
- const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true);
13412
+ const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true, configuredRoot);
12503
13413
  scan.complete &&= childScan.complete;
12504
13414
  scan.hasSkills ||= childScan.hasSkills;
12505
13415
  scan.entries += childScan.entries;
@@ -12508,32 +13418,34 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
12508
13418
  cacheScans.set(path, scan);
12509
13419
  return scan;
12510
13420
  }
12511
- function scanRoot(agent, path, vendor = false, pluginCache = false) {
12512
- if (agent === "hermes" || options.guardHermes && `${path}${sep4}`.includes(`${sep4}.agents${sep4}skills${sep4}`))
13421
+ function scanRoot(agent, path, vendor = false, pluginCache = false, configuredRoot = false) {
13422
+ if (agent === "hermes" || options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`))
12513
13423
  assertNoHermesLegacyShadow(path);
12514
- if (!visit2(agent, path, vendor, 0, pluginCache).complete)
13424
+ if (!visit2(agent, path, vendor, 0, pluginCache, false, configuredRoot).complete)
12515
13425
  throw new Error(`Native skill discovery limit exceeded; review this root before continuing: ${path}`);
12516
13426
  }
12517
13427
  for (const [agent, path] of roots)
12518
13428
  scanRoot(agent, path);
12519
13429
  if (options.includeVendor) {
12520
13430
  for (const agent of ["codex", "claude"].filter(includesAgent))
12521
- scanRoot(agent, canonicalAgentPath(join16(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
13431
+ scanRoot(agent, canonicalAgentPath(join17(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
12522
13432
  if (includesAgent("claude"))
12523
- scanRoot("claude", canonicalAgentPath(join16(home, ".claude", "plugins", "synced"), aliases), true);
13433
+ scanRoot("claude", canonicalAgentPath(join17(home, ".claude", "plugins", "synced"), aliases), true);
12524
13434
  if (includesAgent("gemini"))
12525
- scanRoot("gemini", join16(home, ".gemini", "extensions"), true);
13435
+ scanRoot("gemini", join17(home, ".gemini", "extensions"), true);
12526
13436
  }
12527
13437
  const configured = options.configured ? INTEGRATION_AGENTS.filter(includesAgent).flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
12528
13438
  for (const root of [...options.agentRoots ?? [], ...configured])
12529
13439
  if (includesAgent(root.agent))
12530
- scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true);
13440
+ scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true, false, true);
13441
+ if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
13442
+ throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
12531
13443
  recheckRootAliases(aliases);
12532
13444
  return entries;
12533
13445
  }
12534
13446
  function readOptional(path) {
12535
13447
  assertSafePath(path);
12536
- return existsSync82(path) ? readFileSync11(path, "utf8") : null;
13448
+ return existsSync82(path) ? readFileSync12(path, "utf8") : null;
12537
13449
  }
12538
13450
  function jsonObject(text2, path) {
12539
13451
  if (text2 === null)
@@ -12631,12 +13543,12 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
12631
13543
  }
12632
13544
  }
12633
13545
  function claudeSettingsConsumers(home, path, discoveries, aliases) {
12634
- const canonical3 = (value) => canonicalAgentPath(value, aliases);
13546
+ const canonical5 = (value) => canonicalAgentPath(value, aliases);
12635
13547
  const containsSettings = (root) => {
12636
- const distance = relative4(canonical3(root), path);
12637
- return distance === "" || distance !== ".." && !distance.startsWith(`..${sep4}`) && !isAbsolute9(distance);
13548
+ const distance = relative5(canonical5(root), path);
13549
+ return distance === "" || distance !== ".." && !distance.startsWith(`..${sep5}`) && !isAbsolute11(distance);
12638
13550
  };
12639
- return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical3(join16(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical3(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
13551
+ return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical5(join17(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical5(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
12640
13552
  }
12641
13553
  function verifyCoordinatedDiscovery(binding, home, aliases) {
12642
13554
  try {
@@ -12653,8 +13565,8 @@ function verifyCoordinatedDiscovery(binding, home, aliases) {
12653
13565
  }
12654
13566
  }
12655
13567
  function planClaudeHookEventsUpdate(options) {
12656
- const home = resolve9(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
12657
- const policyPath = join16(dataDir22, "agent-policy.json");
13568
+ const home = resolve10(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
13569
+ const policyPath = join17(dataDir22, "agent-policy.json");
12658
13570
  assertSafePath(policyPath);
12659
13571
  const snapshot = readManagedSkillPolicySnapshot(dataDir22);
12660
13572
  if (!snapshot)
@@ -12665,10 +13577,10 @@ function planClaudeHookEventsUpdate(options) {
12665
13577
  }
12666
13578
  const aliases = bridge.rootAliases ?? [];
12667
13579
  recheckRootAliases(aliases);
12668
- const path = canonicalAgentPath(join16(home, ".claude", "settings.json"), aliases);
13580
+ const path = canonicalAgentPath(join17(home, ".claude", "settings.json"), aliases);
12669
13581
  assertSafePath(path);
12670
- const before = lstatSync14(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
12671
- if ((before === null ? "absent" : sha(before)) !== options.expectedSettingsSha256)
13582
+ const before = lstatSync15(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
13583
+ if ((before === null ? "absent" : sha3(before)) !== options.expectedSettingsSha256)
12672
13584
  throw new Error("Claude settings preimage changed");
12673
13585
  assertClaudeHookEventsReplacement(before, options.replacement, options.events);
12674
13586
  const agents = bridge.agents;
@@ -12721,7 +13633,7 @@ function planClaudeHookEventsUpdate(options) {
12721
13633
  }
12722
13634
  function atomicWrite2(path, content) {
12723
13635
  assertSafePath(path);
12724
- mkdirSync5(dirname10(path), { recursive: true, mode: 448 });
13636
+ mkdirSync5(dirname11(path), { recursive: true, mode: 448 });
12725
13637
  const temporary = `${path}.skills-${randomUUID2()}`;
12726
13638
  try {
12727
13639
  writeFileSync4(temporary, content, { mode: 384, flag: "wx" });
@@ -12731,20 +13643,88 @@ function atomicWrite2(path, content) {
12731
13643
  unlinkSync3(temporary);
12732
13644
  }
12733
13645
  }
13646
+ function planAgentSettingsWitnessUpgrade(options) {
13647
+ if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
13648
+ throw new Error("Invalid Codex witness target version");
13649
+ const home = resolve10(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
13650
+ const snapshot = readManagedSkillPolicySnapshot(dataDir22);
13651
+ if (!snapshot || sha3(snapshot.text) !== options.expectedPolicySha256)
13652
+ throw new Error("Managed policy preimage changed");
13653
+ const policy = snapshot.value, aliases = policy.bridge?.rootAliases ?? [];
13654
+ recheckRootAliases(aliases);
13655
+ const binding = policy.bridge?.discovery?.[options.agent];
13656
+ if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
13657
+ throw new Error("Settings witness upgrade requires an existing reviewed native binding");
13658
+ const configPath = canonicalAgentPath(join17(home, options.agent === "claude" ? ".claude/settings.json" : ".codex/config.toml"), aliases);
13659
+ const settings = readOptional(configPath);
13660
+ if (settings === null || sha3(settings) !== options.expectedSettingsSha256)
13661
+ throw new Error("Native settings preimage changed");
13662
+ const sources = binding.sources.filter((source) => source.path === configPath && source.format === undefined && source.fields === undefined);
13663
+ if (sources.length !== 1)
13664
+ throw new Error("Settings witness upgrade requires one exact settings source");
13665
+ const previous = sources[0];
13666
+ if (typeof previous.sha256 !== "string")
13667
+ throw new Error("Settings witness upgrade requires a preserved settings preimage");
13668
+ const replaced = [previous];
13669
+ if (options.agent === "codex") {
13670
+ if (previous.hashMode !== undefined && previous.hashMode !== "bytes" && previous.hashMode !== "codex-settings-v1" && !(options.targetCodexVersion === 3 && previous.hashMode === "codex-settings-v2"))
13671
+ throw new Error("Codex settings witness upgrade requires an eligible preserved configuration witness");
13672
+ const preimage = readCodexSettingsPreimage(options.reviewedPreimage);
13673
+ for (const source of binding.sources) {
13674
+ if (source === previous || source.path !== configPath)
13675
+ continue;
13676
+ const fields = source.fields;
13677
+ if (source.hashMode !== undefined || source.format !== "toml" || !Array.isArray(fields) || !isDeepStrictEqual4(fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]) || typeof source.sha256 !== "string")
13678
+ throw new Error("Codex settings witness upgrade found an unrecognized configuration witness");
13679
+ if (projectNativeDiscoveryFields(preimage, "toml", fields, configPath) !== source.sha256)
13680
+ throw new Error("Reviewed preimage does not explain the Codex discovery projection witness");
13681
+ replaced.push(source);
13682
+ }
13683
+ }
13684
+ const next = options.agent === "claude" ? upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : options.targetCodexVersion === 3 ? upgradeCodexSettingsWitnessV3(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
13685
+ const rebound = [];
13686
+ for (const source of binding.sources) {
13687
+ if (!replaced.includes(source)) {
13688
+ rebound.push(source);
13689
+ continue;
13690
+ }
13691
+ if (source === replaced[0])
13692
+ rebound.push(next);
13693
+ }
13694
+ const replacement = { ...binding, sources: rebound };
13695
+ verifyAgentDiscovery(replacement);
13696
+ const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
13697
+ return { dataDir: dataDir22, profileId: policy.profileId, changes: [{ path: join17(dataDir22, "agent-policy.json"), before: snapshot.text, after }], nativeSkills: [], observedPolicy: { path: join17(dataDir22, "agent-policy.json"), before: snapshot.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
13698
+ }
12734
13699
  function applyAgentIntegration(plan) {
12735
- const policyPath = join16(plan.dataDir, "agent-policy.json");
13700
+ for (const source of plan.observedNativeSources ?? [])
13701
+ if (sha3(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
13702
+ throw new Error("Native identity source changed after planning");
13703
+ if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
13704
+ throw new Error("Native settings changed after witness planning");
13705
+ const policyPath = join17(plan.dataDir, "agent-policy.json");
12736
13706
  assertSafePath(policyPath);
12737
- const currentText = (path) => resolve9(path) === resolve9(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
13707
+ const currentText = (path) => resolve10(path) === resolve10(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
12738
13708
  readManagedSkillPolicySnapshot(plan.dataDir);
12739
13709
  for (const change of plan.changes)
12740
- if (resolve9(change.path) === resolve9(policyPath))
13710
+ if (resolve10(change.path) === resolve10(policyPath))
12741
13711
  parseManagedSkillPolicy(change.after);
12742
13712
  const aliases = plan.rootAliases ?? [];
12743
13713
  recheckRootAliases(aliases);
12744
13714
  for (const agent of plan.retainedReviewChecks?.agents ?? [])
12745
13715
  assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
13716
+ let provenDiscovery;
13717
+ if (plan.settingsWitnessUpgrade) {
13718
+ const upgrade = plan.settingsWitnessUpgrade;
13719
+ if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
13720
+ throw new Error("Invalid settings witness upgrade plan");
13721
+ const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, ...upgrade.agent === "codex" ? { targetCodexVersion: upgrade.toHashMode === "codex-settings-v3" ? 3 : 2 } : {}, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha3(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
13722
+ if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
13723
+ throw new Error("Settings witness upgrade plan changed");
13724
+ provenDiscovery = verified.discoveryBefore[0];
13725
+ }
12746
13726
  for (const agent of plan.managedAgentChecks?.agents ?? [])
12747
- assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
13727
+ assertManagedAgentBridgeWithDiscovery(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home }, provenDiscovery?.agent === agent ? provenDiscovery : undefined);
12748
13728
  for (const binding of plan.discoveryBefore ?? []) {
12749
13729
  if (plan.managedAgentChecks)
12750
13730
  verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
@@ -12756,7 +13736,7 @@ function applyAgentIntegration(plan) {
12756
13736
  for (const change of plan.changes)
12757
13737
  if (currentText(change.path) !== change.before)
12758
13738
  throw new Error(`Configuration changed after planning: ${change.path}`);
12759
- const backupRoot = join16(plan.dataDir, "migration", randomUUID2()), backups = [], written = [], createdDirectories = new Set;
13739
+ const backupRoot = join17(plan.dataDir, "migration", randomUUID2()), backups = [], written = [], createdDirectories = new Set;
12760
13740
  if (!plan.changes.length) {
12761
13741
  recheckRootAliases(aliases);
12762
13742
  return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
@@ -12766,8 +13746,10 @@ function applyAgentIntegration(plan) {
12766
13746
  chmodSync(backupRoot, 448);
12767
13747
  for (const [index, change] of plan.changes.entries())
12768
13748
  if (change.before !== null) {
12769
- const backup = join16(backupRoot, `${index}-${sha(change.path).slice(0, 12)}.backup`);
13749
+ const backup = join17(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
12770
13750
  writeFileSync4(backup, change.before, { mode: 384, flag: "wx" });
13751
+ if (readFileSync12(backup, "utf8") !== change.before)
13752
+ throw new Error("Native configuration preservation readback failed");
12771
13753
  backups.push(backup);
12772
13754
  }
12773
13755
  try {
@@ -12775,13 +13757,18 @@ function applyAgentIntegration(plan) {
12775
13757
  recheckRootAliases(aliases);
12776
13758
  if (currentText(change.path) !== change.before)
12777
13759
  throw new Error(`Configuration changed after planning: ${change.path}`);
12778
- for (let directory = dirname10(change.path);!existsSync82(directory); directory = dirname10(directory))
13760
+ for (let directory = dirname11(change.path);!existsSync82(directory); directory = dirname11(directory))
12779
13761
  createdDirectories.add(directory);
12780
13762
  const after = change.after;
12781
13763
  atomicWrite2(change.path, after);
12782
13764
  written.push({ path: change.path, before: change.before, after });
12783
13765
  }
12784
13766
  recheckRootAliases(aliases);
13767
+ for (const source of plan.observedNativeSources ?? [])
13768
+ if (sha3(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
13769
+ throw new Error("Native identity source changed during application");
13770
+ if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
13771
+ throw new Error("Native settings changed during witness application");
12785
13772
  for (const binding of plan.discoveryAfter ?? []) {
12786
13773
  if (plan.managedAgentChecks)
12787
13774
  verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
@@ -12790,7 +13777,7 @@ function applyAgentIntegration(plan) {
12790
13777
  }
12791
13778
  for (const agent of plan.managedAgentChecks?.agents ?? [])
12792
13779
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
12793
- atomicWrite2(join16(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha(change.before), afterHash: sha(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
13780
+ atomicWrite2(join17(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha3(change.before), afterHash: sha3(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
12794
13781
  `);
12795
13782
  } catch (error) {
12796
13783
  for (const change of written.reverse()) {
@@ -12820,8 +13807,11 @@ function applyAgentIntegration(plan) {
12820
13807
  return { changed: plan.changes.map((change) => change.path), backups, ...aliases.length ? { rootAliases: aliases } : {} };
12821
13808
  }
12822
13809
  function assertManagedAgentBridge(agent, options = {}) {
12823
- const home = resolve9(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
12824
- assertSafePath(join16(dataDir22, "agent-policy.json"));
13810
+ assertManagedAgentBridgeWithDiscovery(agent, options);
13811
+ }
13812
+ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
13813
+ const home = resolve10(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
13814
+ assertSafePath(join17(dataDir22, "agent-policy.json"));
12825
13815
  const snapshot = readManagedSkillPolicySnapshot(dataDir22);
12826
13816
  if (!snapshot)
12827
13817
  throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
@@ -12832,14 +13822,14 @@ function assertManagedAgentBridge(agent, options = {}) {
12832
13822
  if (!Array.isArray(aliases))
12833
13823
  throw new Error("NATIVE_SKILL_DRIFT: invalid root alias binding");
12834
13824
  recheckRootAliases(aliases);
12835
- const expected = canonicalAgentPath(join16(home, AGENT_ADAPTERS[agent].root, CLI_BRIDGE_NAME), aliases);
13825
+ const expected = canonicalAgentPath(join17(home, AGENT_ADAPTERS[agent].root, CLI_BRIDGE_NAME), aliases);
12836
13826
  if (!isOwnedCliBridge(expected, [expected]))
12837
13827
  throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
12838
13828
  const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
12839
- const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${sep4}.agents${sep4}skills${sep4}`) || agent === "opencode" && entry.agent === "claude";
12840
- const discovery = binding.discovery?.[agent];
13829
+ const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) || agent === "opencode" && entry.agent === "claude";
13830
+ const discovery = provenDiscovery ?? binding.discovery?.[agent];
12841
13831
  assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
12842
- const configPath = canonicalAgentPath(join16(home, AGENT_ADAPTERS[agent].config), aliases), config = agent === "hermes" ? parseHermesConfig(readOptional(configPath)) : jsonObject(readOptional(configPath), configPath);
13832
+ const configPath = canonicalAgentPath(join17(home, AGENT_ADAPTERS[agent].config), aliases), config = agent === "hermes" ? parseHermesConfig(readOptional(configPath)) : jsonObject(readOptional(configPath), configPath);
12843
13833
  const command = binding.commands?.[agent], profile = binding.profiles?.[agent];
12844
13834
  if (typeof command !== "string" || typeof profile !== "string")
12845
13835
  throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
@@ -12847,11 +13837,11 @@ function assertManagedAgentBridge(agent, options = {}) {
12847
13837
  throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
12848
13838
  if (agent === "hermes") {
12849
13839
  const supervisor2 = binding.supervisors?.hermes;
12850
- if (supervisor2?.path !== join16(dataDir22, "agent-hooks", "hermes.js") || supervisor2?.sha256 !== sha(renderHermesSupervisor(command, profile)))
13840
+ if (supervisor2?.path !== join17(dataDir22, "agent-hooks", "hermes.js") || supervisor2?.sha256 !== sha3(renderHermesSupervisor(command, profile)))
12851
13841
  throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
12852
13842
  assertHermesProtection(home, config, command, profile, supervisor2);
12853
13843
  } else if (agent === "opencode") {
12854
- if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join16(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
13844
+ if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join17(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
12855
13845
  throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
12856
13846
  } else {
12857
13847
  const required = {};
@@ -12868,21 +13858,44 @@ function assertManagedAgentBridge(agent, options = {}) {
12868
13858
  if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
12869
13859
  throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
12870
13860
  }
12871
- const codexPath = canonicalAgentPath(join16(home, ".codex", "config.toml"), aliases);
13861
+ const codexPath = canonicalAgentPath(join17(home, ".codex", "config.toml"), aliases);
12872
13862
  if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
12873
13863
  throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
12874
13864
  const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
12875
13865
  if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
12876
13866
  throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
12877
- const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join16(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
13867
+ const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join17(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
12878
13868
  if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
12879
13869
  throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
12880
- const disabledBuiltin = (entry) => {
12881
- if (agent !== "codex" || !entry.vendor || !entry.path.startsWith(canonicalAgentPath(join16(home, ".codex", "skills", ".system"), aliases) + sep4))
13870
+ if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024))))
13871
+ throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
13872
+ const disabledPlugin = (entry) => {
13873
+ if (agent !== "codex" || !entry.vendor)
13874
+ return false;
13875
+ const document2 = join17(entry.path, "SKILL.md"), cache = canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
13876
+ return isReviewedCodexPluginInactive(document2, cache, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document2, cache, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
13877
+ };
13878
+ const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
13879
+ const disabledVendorSkill = (entry) => {
13880
+ if (agent !== "codex" || !entry.vendor)
12882
13881
  return false;
12883
- const matched = Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
12884
- const settings = setting(entry.path);
12885
- return matched && settings.length === 1 && settings[0]?.enabled === false;
13882
+ if ((binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent + sep5)))
13883
+ return disabledPlugin(entry);
13884
+ if (disabledPlugin(entry))
13885
+ return true;
13886
+ if (!disabledPaths.has(entry.path))
13887
+ return false;
13888
+ if (entry.path.startsWith(canonicalAgentPath(join17(home, ".codex", "plugins", "cache"), aliases) + sep5))
13889
+ return true;
13890
+ const runtimeCache = canonicalAgentPath(join17(home, ".cache", "codex-runtimes"), aliases);
13891
+ if (entry.path.startsWith(runtimeCache + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
13892
+ return true;
13893
+ const bundledMarketplaces = canonicalAgentPath(join17(home, ".codex", ".tmp", "bundled-marketplaces"), aliases);
13894
+ if (entry.path.startsWith(bundledMarketplaces + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
13895
+ return true;
13896
+ if (!entry.path.startsWith(canonicalAgentPath(join17(home, ".codex", "skills", ".system"), aliases) + sep5))
13897
+ return false;
13898
+ return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
12886
13899
  };
12887
13900
  if (!discovery || discovery.agent !== agent)
12888
13901
  throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
@@ -12898,8 +13911,16 @@ function assertManagedAgentBridge(agent, options = {}) {
12898
13911
  } catch (error) {
12899
13912
  throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
12900
13913
  }
12901
- const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
12902
- const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledBuiltin(entry));
13914
+ let installationInputRoots = [];
13915
+ if (agent === "codex") {
13916
+ try {
13917
+ installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
13918
+ } catch {
13919
+ throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
13920
+ }
13921
+ }
13922
+ const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0, disabledVendorPaths: agent === "codex" ? disabledCodexSkillPaths(codexConfig, aliases) : [], disabledVendorSkill: disabledPlugin, codexInstallationInputRoots: installationInputRoots });
13923
+ const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
12903
13924
  if (unexpected.length) {
12904
13925
  const paths = unexpected.slice(0, 3).map((entry) => {
12905
13926
  const escaped = JSON.stringify(entry.path).replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
@@ -12918,7 +13939,7 @@ function pluginNeed(value, message) {
12918
13939
  if (!value)
12919
13940
  pluginRefusal(message);
12920
13941
  }
12921
- var pluginHash = (bytes) => createHash11("sha256").update(bytes).digest("hex");
13942
+ var pluginHash = (bytes) => createHash13("sha256").update(bytes).digest("hex");
12922
13943
  function pluginObject(value) {
12923
13944
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
12924
13945
  }
@@ -12974,7 +13995,7 @@ function validatePluginManifest(value) {
12974
13995
  pluginNeed(item.kind === "skill" || item.kind === "command", "Unknown plugin payload kind");
12975
13996
  pluginDigest(item.sourceDigest);
12976
13997
  pluginKeys(item.target, ["slug", "version", "bundleDigest"]);
12977
- validateSelection2({ ...item.target, authority: "https://example.com/skills/v1", workspaceId: "validation", profileRevision: "validation" });
13998
+ validateSelection({ ...item.target, authority: "https://example.com/skills/v1", workspaceId: "validation", profileRevision: "validation" });
12978
13999
  }
12979
14000
  }
12980
14001
  function pluginFileWitnesses(entries) {
@@ -12995,15 +14016,15 @@ var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid())
12995
14016
  var missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
12996
14017
  function stat(path) {
12997
14018
  try {
12998
- return lstatSync16(path, { bigint: true });
14019
+ return lstatSync17(path, { bigint: true });
12999
14020
  } catch (error) {
13000
14021
  if (missing(error))
13001
14022
  return null;
13002
14023
  return pluginRefusal("Cannot inspect plugin filesystem entry");
13003
14024
  }
13004
14025
  }
13005
- function canonical3(path) {
13006
- pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute10(path) && resolve10(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
14026
+ function canonical5(path) {
14027
+ pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute12(path) && resolve11(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
13007
14028
  }
13008
14029
  function owner(stats, system = false) {
13009
14030
  const current2 = uid();
@@ -13013,12 +14034,12 @@ function same3(left, right, identityOnly = false) {
13013
14034
  pluginNeed(right && left.dev === right.dev && left.ino === right.ino && left.mode === right.mode && left.uid === right.uid && left.gid === right.gid && (identityOnly || left.size === right.size && left.nlink === right.nlink && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs), "Plugin filesystem entry changed during access");
13014
14035
  }
13015
14036
  function ancestors(path, create = false) {
13016
- canonical3(path);
13017
- const result = [], parent = dirname12(path), filesystemRoot = parse5(parent).root;
14037
+ canonical5(path);
14038
+ const result = [], parent = dirname13(path), filesystemRoot = parse5(parent).root;
13018
14039
  let current2 = filesystemRoot;
13019
14040
  for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
13020
14041
  if (segment)
13021
- current2 = join21(current2, segment);
14042
+ current2 = join23(current2, segment);
13022
14043
  let currentStat = stat(current2);
13023
14044
  if (!currentStat && create) {
13024
14045
  try {
@@ -13064,8 +14085,8 @@ function readBytes(path, maximum, kind) {
13064
14085
  fileShape(before, maximum, kind);
13065
14086
  let fd;
13066
14087
  try {
13067
- fd = openSync11(path, READ_FLAGS);
13068
- const opened = fstatSync11(fd, { bigint: true });
14088
+ fd = openSync9(path, READ_FLAGS);
14089
+ const opened = fstatSync9(fd, { bigint: true });
13069
14090
  same3(before, opened);
13070
14091
  fileShape(opened, maximum, kind);
13071
14092
  const bytes = new Uint8Array(Number(opened.size));
@@ -13076,7 +14097,7 @@ function readBytes(path, maximum, kind) {
13076
14097
  offset += count;
13077
14098
  }
13078
14099
  pluginNeed(readSync7(fd, new Uint8Array(1), 0, 1, null) === 0, "Plugin file grew while being read");
13079
- same3(opened, fstatSync11(fd, { bigint: true }));
14100
+ same3(opened, fstatSync9(fd, { bigint: true }));
13080
14101
  same3(opened, stat(path));
13081
14102
  verifyAncestors(chain);
13082
14103
  return bytes;
@@ -13086,7 +14107,7 @@ function readBytes(path, maximum, kind) {
13086
14107
  return pluginRefusal("Cannot safely read plugin file");
13087
14108
  } finally {
13088
14109
  if (fd !== undefined)
13089
- closeSync11(fd);
14110
+ closeSync9(fd);
13090
14111
  }
13091
14112
  }
13092
14113
  function exactPaths(entries) {
@@ -13116,7 +14137,7 @@ function directoryNames(path) {
13116
14137
  function snapshotPluginTree(root, options = {}) {
13117
14138
  const chain = ancestors(root), entries = [], files = [], directories = [];
13118
14139
  let bytes = 0, visited = 0;
13119
- const visit2 = (directory, relative5) => {
14140
+ const visit2 = (directory, relative6) => {
13120
14141
  const before = stat(directory);
13121
14142
  pluginNeed(before && before.isDirectory() && !before.isSymbolicLink(), "Plugin tree must contain real directories");
13122
14143
  const names = directoryNames(directory);
@@ -13124,18 +14145,18 @@ function snapshotPluginTree(root, options = {}) {
13124
14145
  directories.push({ path: directory, stat: before, names });
13125
14146
  for (const name of names) {
13126
14147
  pluginNeed(++visited <= PLUGIN_PROJECTION_LIMITS.files * 50, "Plugin tree membership exceeds its limit");
13127
- const path = relative5 ? `${relative5}/${name}` : name;
14148
+ const path = relative6 ? `${relative6}/${name}` : name;
13128
14149
  pluginNeed(new TextEncoder().encode(path).byteLength <= 100 && !/[\\:\x00-\x1f\x7f]/u.test(path), "Plugin entry path exceeds its limit or is unsafe");
13129
- const absolute = join21(directory, name), entryStat = stat(absolute);
14150
+ const absolute = join23(directory, name), entryStat = stat(absolute);
13130
14151
  pluginNeed(entryStat, "Plugin tree member disappeared");
13131
- if (relative5 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
14152
+ if (relative6 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
13132
14153
  const bytes2 = readBytes(absolute, 32, "native-pin");
13133
14154
  pluginNeed(/^[1-9][0-9]{12}$/.test(new TextDecoder().decode(bytes2)), "Malformed native orphan timestamp");
13134
14155
  same3(entryStat, stat(absolute));
13135
14156
  files.push({ path: absolute, stat: entryStat });
13136
14157
  continue;
13137
14158
  }
13138
- if (relative5 === "" && name === ".in_use" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
14159
+ if (relative6 === "" && name === ".in_use" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
13139
14160
  pluginNeed(entryStat.isDirectory() && !entryStat.isSymbolicLink(), "Native plugin pins require a real directory");
13140
14161
  owner(entryStat);
13141
14162
  const pins = directoryNames(absolute);
@@ -13143,7 +14164,7 @@ function snapshotPluginTree(root, options = {}) {
13143
14164
  directories.push({ path: absolute, stat: entryStat, names: pins });
13144
14165
  for (const pin2 of pins) {
13145
14166
  pluginNeed(/^[1-9][0-9]{0,9}$/.test(pin2) && Number(pin2) <= 2147483647, "Unrecognized native plugin pin name");
13146
- const pinPath = join21(absolute, pin2), pinStat = stat(pinPath);
14167
+ const pinPath = join23(absolute, pin2), pinStat = stat(pinPath);
13147
14168
  pluginNeed(pinStat, "Native plugin pin disappeared");
13148
14169
  const bytes2 = readBytes(pinPath, 4096, "native-pin");
13149
14170
  let value;
@@ -13215,7 +14236,7 @@ function validatePluginTarget(value) {
13215
14236
  for (const registration of value.registrations) {
13216
14237
  pluginKeys(registration, ["scope", "projectPath"]);
13217
14238
  pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
13218
- pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute11(registration.projectPath) && resolve11(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
14239
+ pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute13(registration.projectPath) && resolve12(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
13219
14240
  const key = JSON.stringify([registration.scope, registration.projectPath]);
13220
14241
  pluginNeed(!scopes.has(key), "Duplicate plugin registration scope");
13221
14242
  scopes.add(key);
@@ -13223,7 +14244,7 @@ function validatePluginTarget(value) {
13223
14244
  for (const key of ["native", "resolver"]) {
13224
14245
  pluginKeys(value[key], key === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
13225
14246
  pluginText(value[key].executable);
13226
- pluginNeed(isAbsolute11(value[key].executable) && resolve11(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
14247
+ pluginNeed(isAbsolute13(value[key].executable) && resolve12(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
13227
14248
  pluginDigest(value[key].digest);
13228
14249
  }
13229
14250
  pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
@@ -13269,11 +14290,11 @@ function validateBinding2(value) {
13269
14290
  for (const key of ["authority", "workspaceId", "profileId", "bundleSlug"])
13270
14291
  pluginText(value[key]);
13271
14292
  pluginNeed(value.principal.accountId === value.workspaceId, "Plugin principal account differs from its workspace");
13272
- validateSelection2({ slug: value.bundleSlug, version: "1.0.0", bundleDigest: `sha256:${"0".repeat(64)}`, authority: value.authority, workspaceId: value.workspaceId, profileRevision: "validation" });
14293
+ validateSelection({ slug: value.bundleSlug, version: "1.0.0", bundleDigest: `sha256:${"0".repeat(64)}`, authority: value.authority, workspaceId: value.workspaceId, profileRevision: "validation" });
13273
14294
  }
13274
14295
  function validateObservedSelection(value) {
13275
14296
  pluginKeys(value, ["slug", "version", "bundleDigest", "aliases", "triggers", "authority", "workspaceId", "profileRevision"]);
13276
- validateSelection2(value);
14297
+ validateSelection(value);
13277
14298
  if (value.triggers !== undefined) {
13278
14299
  pluginKeys(value.triggers, ["keywords", "paths", "always"]);
13279
14300
  pluginNeed(value.triggers.always === undefined || typeof value.triggers.always === "boolean", "Invalid plugin selection trigger");
@@ -13368,12 +14389,12 @@ function pluginResolverCommand(binding) {
13368
14389
  }
13369
14390
  function pluginBindingPath(storeRoot, id) {
13370
14391
  pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
13371
- return join23(storeRoot, "bindings", `${id}.json`);
14392
+ return join25(storeRoot, "bindings", `${id}.json`);
13372
14393
  }
13373
14394
  function pluginReceiptPath(storeRoot, id, digest) {
13374
14395
  pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
13375
14396
  pluginDigest(digest);
13376
- return join23(storeRoot, "receipts", id, `${digest.slice(7)}.json`);
14397
+ return join25(storeRoot, "receipts", id, `${digest.slice(7)}.json`);
13377
14398
  }
13378
14399
  function readPluginBinding(storeRoot, id) {
13379
14400
  const value = readPluginJson(pluginBindingPath(storeRoot, id));
@@ -13393,7 +14414,7 @@ function readPluginAdmissionReceipt(storeRoot, id, digest) {
13393
14414
  pluginNeed(receipt.plan.bindingId === id && receipt.plan.planDigest === digest, "Plugin receipt identity differs from its filename");
13394
14415
  const binding = readPluginBinding(storeRoot, id);
13395
14416
  pluginNeed(canonicalJson(binding) === canonicalJson(receipt.plan.binding) && receipt.plan.sourceCommand === pluginResolverCommand(binding), "Plugin receipt binding changed");
13396
- pluginNeed(receipt.materializedPath === join23(storeRoot, "objects", id, digest.slice(7)), "Plugin receipt points outside its immutable materialization");
14417
+ pluginNeed(receipt.materializedPath === join25(storeRoot, "objects", id, digest.slice(7)), "Plugin receipt points outside its immutable materialization");
13397
14418
  pluginText(receipt.admittedAt, 64);
13398
14419
  pluginNeed(Number.isFinite(Date.parse(receipt.admittedAt)), "Invalid plugin admission time");
13399
14420
  return receipt;
@@ -13406,25 +14427,25 @@ function validateManagedPluginWitnesses(value) {
13406
14427
  pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
13407
14428
  seen.add(item.bindingId);
13408
14429
  pluginText(item.storeRoot);
13409
- pluginNeed(isAbsolute12(item.storeRoot) && resolve12(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
14430
+ pluginNeed(isAbsolute14(item.storeRoot) && resolve13(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
13410
14431
  }
13411
14432
  }
13412
14433
  function safe3(path) {
13413
14434
  pluginText(path);
13414
- pluginNeed(isAbsolute12(path) && resolve12(path) === path, "Invalid plugin registry path");
13415
- for (let at = path;; at = dirname13(at)) {
13416
- pluginNeed(!lstatSync17(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
13417
- if (at === dirname13(at))
14435
+ pluginNeed(isAbsolute14(path) && resolve13(path) === path, "Invalid plugin registry path");
14436
+ for (let at = path;; at = dirname14(at)) {
14437
+ pluginNeed(!lstatSync18(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
14438
+ if (at === dirname14(at))
13418
14439
  break;
13419
14440
  }
13420
14441
  }
13421
14442
  function readRegistry(path) {
13422
14443
  safe3(path);
13423
- const initial = lstatSync17(path);
14444
+ const initial = lstatSync18(path);
13424
14445
  pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
13425
- const fd = openSync12(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
14446
+ const fd = openSync10(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
13426
14447
  try {
13427
- const opened = fstatSync12(fd);
14448
+ const opened = fstatSync10(fd);
13428
14449
  pluginNeed(opened.isFile() && opened.ino === initial.ino && opened.dev === initial.dev, "Plugin registry changed during open");
13429
14450
  const bytes = Buffer.alloc(1024 * 1024 + 1);
13430
14451
  let length = 0;
@@ -13434,7 +14455,7 @@ function readRegistry(path) {
13434
14455
  break;
13435
14456
  length += count;
13436
14457
  }
13437
- const after = fstatSync12(fd), current2 = lstatSync17(path);
14458
+ const after = fstatSync10(fd), current2 = lstatSync18(path);
13438
14459
  safe3(path);
13439
14460
  pluginNeed(length <= 1024 * 1024 && length === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs && current2.dev === opened.dev && current2.ino === opened.ino && current2.mtimeMs === opened.mtimeMs && current2.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
13440
14461
  let value;
@@ -13446,7 +14467,7 @@ function readRegistry(path) {
13446
14467
  pluginNeed(pluginObject(value) && value.version === 2 && pluginObject(value.plugins), "Unsupported native plugin registry schema");
13447
14468
  return { value, rawDigest: pluginHash(bytes.subarray(0, length)) };
13448
14469
  } finally {
13449
- closeSync12(fd);
14470
+ closeSync10(fd);
13450
14471
  }
13451
14472
  }
13452
14473
  function nativeVersionMatches(version, receipt) {
@@ -13464,7 +14485,7 @@ function hashManagedPluginRegistry(path, managed) {
13464
14485
  seen.add(target.pluginId);
13465
14486
  const rows = plugins[target.pluginId];
13466
14487
  pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
13467
- const [name, market] = target.pluginId.split("@"), cacheRoot = join25(dirname13(path), "cache", market, name);
14488
+ const [name, market] = target.pluginId.split("@"), cacheRoot = join26(dirname14(path), "cache", market, name);
13468
14489
  const scopes = new Set, approvals = new Map, currentFiles = new Map;
13469
14490
  plugins[target.pluginId] = rows.map((row) => {
13470
14491
  pluginNeed(pluginObject(row), "Invalid managed plugin registration");
@@ -13477,10 +14498,10 @@ function hashManagedPluginRegistry(path, managed) {
13477
14498
  pluginText(row.sourceProducerPath);
13478
14499
  pluginText(row.lastUpdated, 64);
13479
14500
  pluginNeed(Number.isFinite(Date.parse(row.lastUpdated)), "Invalid managed plugin update timestamp");
13480
- pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join25(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
14501
+ pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join26(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
13481
14502
  const approval = (producer) => {
13482
14503
  pluginText(producer);
13483
- const digest = `sha256:${basename6(producer)}`;
14504
+ const digest = `sha256:${basename7(producer)}`;
13484
14505
  const receipt2 = readPluginAdmissionReceipt(witness.storeRoot, witness.bindingId, digest);
13485
14506
  pluginNeed(receipt2.materializedPath === producer, "Managed producer path is not an approved immutable projection");
13486
14507
  if (!verified.has(producer)) {
@@ -13507,7 +14528,7 @@ function hashManagedPluginRegistry(path, managed) {
13507
14528
  return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
13508
14529
  });
13509
14530
  safe3(cacheRoot);
13510
- const before = lstatSync17(cacheRoot);
14531
+ const before = lstatSync18(cacheRoot);
13511
14532
  pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
13512
14533
  const directory = opendirSync5(cacheRoot), names = [];
13513
14534
  try {
@@ -13520,17 +14541,17 @@ function hashManagedPluginRegistry(path, managed) {
13520
14541
  directory.closeSync();
13521
14542
  }
13522
14543
  for (const version of names.sort()) {
13523
- const files = pluginFileWitnesses(snapshotPluginTree(join25(cacheRoot, version), { nativeRuntime: `claude-${target.native.version}` }));
14544
+ const files = pluginFileWitnesses(snapshotPluginTree(join26(cacheRoot, version), { nativeRuntime: `claude-${target.native.version}` }));
13524
14545
  verifiedBytes += files.reduce((total, file) => total + file.size, 0);
13525
14546
  pluginNeed(verifiedBytes <= 256 * 1024 * 1024, "Managed plugin verification exceeds its aggregate byte budget");
13526
14547
  const serialized = JSON.stringify(files);
13527
- const expected = currentFiles.get(join25(cacheRoot, version));
14548
+ const expected = currentFiles.get(join26(cacheRoot, version));
13528
14549
  pluginNeed(expected === undefined || serialized === expected, "Current native cache differs from its exact registered producer receipt");
13529
14550
  pluginNeed([...approvals.values()].some((receipt) => nativeVersionMatches(version, receipt) && JSON.stringify(receipt.plan.files) === serialized), "A retained native plugin cache version differs from every approved projection");
13530
14551
  }
13531
14552
  for (const row of rows)
13532
14553
  pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
13533
- const after = lstatSync17(cacheRoot);
14554
+ const after = lstatSync18(cacheRoot);
13534
14555
  safe3(cacheRoot);
13535
14556
  pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
13536
14557
  }
@@ -13538,35 +14559,35 @@ function hashManagedPluginRegistry(path, managed) {
13538
14559
  return pluginHash(JSON.stringify(snapshot.value));
13539
14560
  }
13540
14561
  var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
13541
- function need3(value, reason) {
14562
+ function need5(value, reason) {
13542
14563
  if (!value)
13543
14564
  throw new Error(`Claude marketplace registry ${reason}`);
13544
14565
  }
13545
- function absolutePath2(value) {
13546
- need3(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute13(value) && resolve13(value) === value, "requires a normalized absolute path");
14566
+ function absolutePath3(value) {
14567
+ need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve14(value) === value, "requires a normalized absolute path");
13547
14568
  }
13548
14569
  var sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
13549
14570
  function parents2(path) {
13550
14571
  const result = [];
13551
- for (let at = dirname14(path);; at = dirname14(at)) {
13552
- const stat2 = lstatSync18(at, { bigint: true });
13553
- need3(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
14572
+ for (let at = dirname15(path);; at = dirname15(at)) {
14573
+ const stat2 = lstatSync19(at, { bigint: true });
14574
+ need5(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
13554
14575
  result.push([at, stat2]);
13555
- if (at === dirname14(at))
14576
+ if (at === dirname15(at))
13556
14577
  return result;
13557
14578
  }
13558
14579
  }
13559
14580
  function readRegistry2(path, budget) {
13560
- absolutePath2(path);
13561
- need3(basename7(path) === "known_marketplaces.json", "requires known_marketplaces.json");
13562
- need3(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
13563
- const ancestors2 = parents2(path), initial = lstatSync18(path, { bigint: true });
13564
- need3(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
13565
- need3(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
13566
- const fd = openSync13(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
14581
+ absolutePath3(path);
14582
+ need5(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
14583
+ need5(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
14584
+ const ancestors2 = parents2(path), initial = lstatSync19(path, { bigint: true });
14585
+ need5(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
14586
+ need5(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
14587
+ const fd = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
13567
14588
  try {
13568
- const opened = fstatSync13(fd, { bigint: true });
13569
- need3(opened.isFile() && sameFile2(initial, opened), "changed during open");
14589
+ const opened = fstatSync11(fd, { bigint: true });
14590
+ need5(opened.isFile() && sameFile2(initial, opened), "changed during open");
13570
14591
  const bytes = Buffer.alloc(Number(opened.size) + 1);
13571
14592
  let length = 0;
13572
14593
  while (length < bytes.length) {
@@ -13575,11 +14596,11 @@ function readRegistry2(path, budget) {
13575
14596
  break;
13576
14597
  length += count;
13577
14598
  }
13578
- const after = fstatSync13(fd, { bigint: true }), current2 = lstatSync18(path, { bigint: true });
13579
- need3(current2.isFile() && sameFile2(opened, after) && sameFile2(after, current2) && BigInt(length) === opened.size, "changed during read");
14599
+ const after = fstatSync11(fd, { bigint: true }), current2 = lstatSync19(path, { bigint: true });
14600
+ need5(current2.isFile() && sameFile2(opened, after) && sameFile2(after, current2) && BigInt(length) === opened.size, "changed during read");
13580
14601
  for (const [ancestor, before] of ancestors2) {
13581
- const now = lstatSync18(ancestor, { bigint: true });
13582
- need3(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
14602
+ const now = lstatSync19(ancestor, { bigint: true });
14603
+ need5(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
13583
14604
  }
13584
14605
  budget.remaining -= length;
13585
14606
  try {
@@ -13588,7 +14609,7 @@ function readRegistry2(path, budget) {
13588
14609
  throw new Error("Claude marketplace registry is not strict UTF-8");
13589
14610
  }
13590
14611
  } finally {
13591
- closeSync13(fd);
14612
+ closeSync11(fd);
13592
14613
  }
13593
14614
  }
13594
14615
  function parse6(text2) {
@@ -13599,7 +14620,7 @@ function parse6(text2) {
13599
14620
  at++;
13600
14621
  };
13601
14622
  function string() {
13602
- need3(text2[at] === '"', "contains invalid JSON");
14623
+ need5(text2[at] === '"', "contains invalid JSON");
13603
14624
  const start = at++;
13604
14625
  while (at < text2.length) {
13605
14626
  const character = text2[at++];
@@ -13610,7 +14631,7 @@ function parse6(text2) {
13610
14631
  } catch {
13611
14632
  throw new Error("Claude marketplace registry contains invalid JSON string");
13612
14633
  }
13613
- need3(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
14634
+ need5(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
13614
14635
  return value2;
13615
14636
  }
13616
14637
  if (character === "\\")
@@ -13619,7 +14640,7 @@ function parse6(text2) {
13619
14640
  throw new Error("Claude marketplace registry contains an unterminated JSON string");
13620
14641
  }
13621
14642
  function value(depth) {
13622
- need3(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
14643
+ need5(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
13623
14644
  space();
13624
14645
  const character = text2[at];
13625
14646
  if (character === '"')
@@ -13627,26 +14648,26 @@ function parse6(text2) {
13627
14648
  if (character === "{" || character === "[") {
13628
14649
  at++;
13629
14650
  space();
13630
- const object4 = character === "{", end = object4 ? "}" : "]", entries = [], items = [], keys = new Set;
14651
+ const object5 = character === "{", end = object5 ? "}" : "]", entries = [], items = [], keys = new Set;
13631
14652
  if (text2[at] !== end)
13632
14653
  while (true) {
13633
- if (object4) {
14654
+ if (object5) {
13634
14655
  space();
13635
14656
  const key = string();
13636
- need3(!keys.has(key), "contains a duplicate JSON key");
14657
+ need5(!keys.has(key), "contains a duplicate JSON key");
13637
14658
  keys.add(key);
13638
14659
  space();
13639
- need3(text2[at++] === ":", "contains invalid JSON");
14660
+ need5(text2[at++] === ":", "contains invalid JSON");
13640
14661
  entries.push([key, value(depth + 1)]);
13641
14662
  } else
13642
14663
  items.push(value(depth + 1));
13643
14664
  space();
13644
14665
  if (text2[at] === end)
13645
14666
  break;
13646
- need3(text2[at++] === ",", "contains invalid JSON");
14667
+ need5(text2[at++] === ",", "contains invalid JSON");
13647
14668
  }
13648
14669
  at++;
13649
- return object4 ? { kind: "object", entries } : { kind: "array", items };
14670
+ return object5 ? { kind: "object", entries } : { kind: "array", items };
13650
14671
  }
13651
14672
  for (const literal of ["true", "false", "null"])
13652
14673
  if (text2.startsWith(literal, at)) {
@@ -13655,22 +14676,22 @@ function parse6(text2) {
13655
14676
  }
13656
14677
  numberPattern.lastIndex = at;
13657
14678
  const number = numberPattern.exec(text2);
13658
- need3(number, "contains invalid JSON");
14679
+ need5(number, "contains invalid JSON");
13659
14680
  at += number[0].length;
13660
14681
  return { kind: "number", value: number[0] };
13661
14682
  }
13662
14683
  const root2 = value(0);
13663
14684
  space();
13664
- need3(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
13665
- need3(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
14685
+ need5(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
14686
+ need5(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
13666
14687
  return root2;
13667
14688
  }
13668
14689
  function exactKeys(value, keys) {
13669
14690
  return value?.kind === "object" && value.entries.length === keys.length && value.entries.every(([key]) => keys.includes(key));
13670
14691
  }
13671
- var field = (object4, key) => object4.entries.find(([name]) => name === key)?.[1];
14692
+ var field = (object5, key) => object5.entries.find(([name]) => name === key)?.[1];
13672
14693
  function textField(value) {
13673
- need3(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
14694
+ need5(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
13674
14695
  return value.value;
13675
14696
  }
13676
14697
  function normalizeRow(row, version = 1) {
@@ -13678,7 +14699,7 @@ function normalizeRow(row, version = 1) {
13678
14699
  if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
13679
14700
  return;
13680
14701
  const autoUpdate = field(row, "autoUpdate");
13681
- need3(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
14702
+ need5(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
13682
14703
  }
13683
14704
  const source = field(row, "source");
13684
14705
  if (source?.kind !== "object")
@@ -13691,52 +14712,52 @@ function normalizeRow(row, version = 1) {
13691
14712
  return;
13692
14713
  const sourceLocation = textField(field(source, location));
13693
14714
  if (type.value === "directory")
13694
- absolutePath2(sourceLocation);
13695
- absolutePath2(textField(field(row, "installLocation")));
14715
+ absolutePath3(sourceLocation);
14716
+ absolutePath3(textField(field(row, "installLocation")));
13696
14717
  const updated = textField(field(row, "lastUpdated"));
13697
- need3(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
14718
+ need5(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
13698
14719
  const millis = Date.parse(updated);
13699
- need3(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
14720
+ need5(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
13700
14721
  row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
13701
14722
  }
13702
14723
  function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
13703
14724
  const value = parse6(readRegistry2(path, budget));
13704
14725
  for (const [, row] of value.entries)
13705
14726
  normalizeRow(row);
13706
- const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
14727
+ const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
13707
14728
  return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
13708
14729
  }
13709
14730
  function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
13710
14731
  const value = parse6(readRegistry2(path, budget));
13711
14732
  for (const [, row] of value.entries)
13712
14733
  normalizeRow(row, 2);
13713
- const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
14734
+ const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
13714
14735
  return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
13715
14736
  }
13716
- function need4(value, code) {
14737
+ function need6(value, code) {
13717
14738
  if (!value)
13718
14739
  throw new Error(`CODEX_HOOK_TRUST_${code}`);
13719
14740
  }
13720
14741
  var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
13721
14742
  function safeParents(file) {
13722
- for (let p = dirname15(file);; p = dirname15(p)) {
13723
- const s = lstatSync19(p);
13724
- need4(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
13725
- need4((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
13726
- if (dirname15(p) === p)
14743
+ for (let p = dirname16(file);; p = dirname16(p)) {
14744
+ const s = lstatSync20(p);
14745
+ need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
14746
+ need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
14747
+ if (dirname16(p) === p)
13727
14748
  break;
13728
14749
  }
13729
14750
  }
13730
14751
  function snapshot(file, privateFile = false, options = {}) {
13731
14752
  const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
13732
14753
  safeParents(file);
13733
- const initial = lstatSync19(file, { bigint: true });
13734
- need4(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
13735
- need4(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
13736
- const fd = openSync14(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
14754
+ const initial = lstatSync20(file, { bigint: true });
14755
+ need6(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
14756
+ need6(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
14757
+ const fd = openSync12(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
13737
14758
  try {
13738
- const before = fstatSync14(fd, { bigint: true });
13739
- need4(sameStat(initial, before), "INPUT_CHANGED");
14759
+ const before = fstatSync12(fd, { bigint: true });
14760
+ need6(sameStat(initial, before), "INPUT_CHANGED");
13740
14761
  const bytes = Buffer.alloc(Number(before.size) + 1);
13741
14762
  let length = 0;
13742
14763
  while (length < bytes.length) {
@@ -13745,25 +14766,26 @@ function snapshot(file, privateFile = false, options = {}) {
13745
14766
  break;
13746
14767
  length += n2;
13747
14768
  }
13748
- need4(length === Number(before.size) && sameStat(before, fstatSync14(fd, { bigint: true })) && sameStat(before, lstatSync19(file, { bigint: true })), "INPUT_CHANGED");
14769
+ need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync20(file, { bigint: true })), "INPUT_CHANGED");
13749
14770
  const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
13750
- return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash14("sha256").update(content).digest("hex") };
14771
+ return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash16("sha256").update(content).digest("hex") };
13751
14772
  } finally {
13752
- closeSync14(fd);
14773
+ closeSync12(fd);
13753
14774
  }
13754
14775
  }
13755
14776
  function unchanged2(before) {
13756
14777
  const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
13757
- need4(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
14778
+ need6(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
13758
14779
  }
13759
14780
  var events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
13760
14781
  var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
13761
14782
  var proofs = new WeakMap;
13762
- var sha2 = (text2) => createHash15("sha256").update(text2).digest("hex");
14783
+ var sha4 = (text2) => createHash17("sha256").update(text2).digest("hex");
14784
+ var fields = [...CODEX_DISCOVERY_PROJECTION_FIELDS];
13763
14785
  var proofs2 = new WeakMap;
13764
14786
  function assertCodexHookDiscoveryRecovery(binding, witness) {
13765
14787
  const proof = proofs2.get(witness);
13766
- need4(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha2(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
14788
+ need6(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
13767
14789
  unchanged2(proof.before);
13768
14790
  unchanged2(proof.current);
13769
14791
  proof.recheckAdditions();
@@ -13772,10 +14794,10 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
13772
14794
  if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
13773
14795
  return false;
13774
14796
  assertCodexHookDiscoveryRecovery(binding, witness);
13775
- need4(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
14797
+ need6(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
13776
14798
  return true;
13777
14799
  }
13778
- var digest = (text2) => createHash16("sha256").update(text2).digest("hex");
14800
+ var digest = (text2) => createHash18("sha256").update(text2).digest("hex");
13779
14801
  function parseConfig(text2, path, toml = false) {
13780
14802
  try {
13781
14803
  const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
@@ -13787,12 +14809,12 @@ function parseConfig(text2, path, toml = false) {
13787
14809
  }
13788
14810
  }
13789
14811
  function safe4(path) {
13790
- if (!isAbsolute14(path) || path.includes("\x00"))
14812
+ if (!isAbsolute16(path) || path.includes("\x00"))
13791
14813
  throw new Error("Expected an absolute native discovery path");
13792
- for (let cursor = path;; cursor = dirname16(cursor)) {
13793
- if (lstatSync20(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
14814
+ for (let cursor = path;; cursor = dirname17(cursor)) {
14815
+ if (lstatSync21(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
13794
14816
  throw new Error(`Refusing symlink native discovery input: ${cursor}`);
13795
- if (dirname16(cursor) === cursor)
14817
+ if (dirname17(cursor) === cursor)
13796
14818
  break;
13797
14819
  }
13798
14820
  }
@@ -13800,14 +14822,34 @@ function read2(path, changes) {
13800
14822
  safe4(path);
13801
14823
  if (changes?.has(path))
13802
14824
  return changes.get(path);
13803
- const stat2 = lstatSync20(path, { throwIfNoEntry: false });
14825
+ const stat2 = lstatSync21(path, { throwIfNoEntry: false });
13804
14826
  if (!stat2)
13805
14827
  return null;
13806
14828
  if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
13807
14829
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
13808
- return readFileSync16(path, "utf8");
14830
+ return readFileSync17(path, "utf8");
14831
+ }
14832
+ function projectNativeDiscoveryFields(text2, format, fields2, path) {
14833
+ const object5 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
14834
+ if (!object5 || typeof object5 !== "object" || Array.isArray(object5))
14835
+ throw new Error("Expected native discovery configuration object");
14836
+ return digest(JSON.stringify(Object.fromEntries(fields2.map((field2) => [field2, object5[field2] ?? null]))));
14837
+ }
14838
+ function isSupersededCodexProjection(agent, configPath, source) {
14839
+ return agent === "codex" && source.path === configPath && source.format === "toml" && isDeepStrictEqual5(source.fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]);
14840
+ }
14841
+ function supersedesCodexProjection(sources, configPath) {
14842
+ return sources.some((source) => source.path === configPath && (source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3"));
13809
14843
  }
13810
14844
  function projected(source, changes, budget = discoveryByteBudget()) {
14845
+ if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3") {
14846
+ if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
14847
+ throw new Error("Codex settings witnesses require exact typed metadata");
14848
+ const current2 = (source.hashMode === "codex-settings-v3" ? captureCodexSettingsV3 : source.hashMode === "codex-settings-v2" ? captureCodexSettingsV2 : captureCodexSettings)(source.path, budget).sha256;
14849
+ if (current2 !== source.sha256)
14850
+ throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
14851
+ return changes?.has(source.path) ? (source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget) : current2;
14852
+ }
13811
14853
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
13812
14854
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
13813
14855
  throw new Error("Claude settings witnesses require exact typed metadata");
@@ -13845,10 +14887,7 @@ function projected(source, changes, budget = discoveryByteBudget()) {
13845
14887
  return null;
13846
14888
  if (!source.format)
13847
14889
  return digest(text2);
13848
- const object4 = source.format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, source.path, source.format === "toml");
13849
- if (!object4 || typeof object4 !== "object" || Array.isArray(object4))
13850
- throw new Error("Expected native discovery configuration object");
13851
- return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((field2) => [field2, object4[field2] ?? null]))));
14890
+ return projectNativeDiscoveryFields(text2, source.format, source.fields ?? [], source.path);
13852
14891
  }
13853
14892
  function verifyAgentDiscovery(binding, codexRecovery) {
13854
14893
  if (codexRecovery)
@@ -13857,8 +14896,16 @@ function verifyAgentDiscovery(binding, codexRecovery) {
13857
14896
  throw new Error("Invalid native discovery binding");
13858
14897
  if (binding.agent === "hermes" && !binding.directories?.length)
13859
14898
  throw new Error("Hermes discovery requires directory membership coverage; run skills hook install with a fresh discovery review");
13860
- if (binding.directories !== undefined)
13861
- verifyDiscoveryDirectories(binding.directories);
14899
+ const installationRoots = codexInstallationRoots(binding);
14900
+ const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 + sep6));
14901
+ if (binding.directories !== undefined) {
14902
+ const projected2 = binding.codexInstallationInputs?.directories ?? [];
14903
+ const ancestors2 = binding.directories.filter((directory) => installationRoots.some((root2) => root2.startsWith(directory.path + sep6)));
14904
+ if (projected2.length !== ancestors2.length || projected2.some((directory) => !ancestors2.some((original) => original.path === directory.path)))
14905
+ throw new Error("Missing installation input directory projection");
14906
+ verifyDiscoveryDirectories(binding.directories.filter((directory) => !installationInput(directory.path) && !ancestors2.includes(directory)));
14907
+ verifyDiscoveryDirectories(projected2, installationRoots);
14908
+ }
13862
14909
  const budget = discoveryByteBudget();
13863
14910
  for (const source of binding.sources) {
13864
14911
  assertMarketplaceBinding(binding, source);
@@ -13868,6 +14915,10 @@ function verifyAgentDiscovery(binding, codexRecovery) {
13868
14915
  throw new Error("Invalid native discovery projection");
13869
14916
  if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
13870
14917
  throw new Error("Invalid native discovery digest");
14918
+ if (installationInput(source.path)) {
14919
+ safe4(source.path);
14920
+ continue;
14921
+ }
13871
14922
  const current2 = projected(source, undefined, budget);
13872
14923
  if (current2 !== source.sha256 && !verifiesCodexHookDiscoverySource(binding, source, current2, codexRecovery))
13873
14924
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
@@ -13875,25 +14926,52 @@ function verifyAgentDiscovery(binding, codexRecovery) {
13875
14926
  for (const root2 of binding.roots)
13876
14927
  safe4(root2);
13877
14928
  }
14929
+ function codexInstallationRoots(binding) {
14930
+ const proof = binding.codexInstallationInputs;
14931
+ if (proof === undefined)
14932
+ return [];
14933
+ if (binding.agent !== "codex" || binding.method !== "reviewed" || proof.version !== "codex-cli 0.160.0" || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
14934
+ throw new Error("Invalid native installation input proof");
14935
+ const cache = dirname17(dirname17(proof.plugins[0].pluginParent));
14936
+ const config = join27(dirname17(dirname17(cache)), "config.toml"), home = dirname17(dirname17(dirname17(cache)));
14937
+ const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
14938
+ const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join27(home, path));
14939
+ if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep6) || input.sourceRoot.startsWith(root2 + sep6)) || binding.sources.some((source) => basename9(source.path) === "config.toml" && (source.path === input.sourceRoot || source.path.startsWith(input.sourceRoot + sep6)))))
14940
+ throw new Error("Native installation input overlaps configuration or loading roots");
14941
+ if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path === join27(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
14942
+ throw new Error("Missing native installation input source witness");
14943
+ return reviewedCodexPluginSourceRoots(cache, proof.plugins, (path) => {
14944
+ const text2 = read2(path);
14945
+ if (text2 === null)
14946
+ throw new Error("Missing native installation input");
14947
+ return text2;
14948
+ });
14949
+ }
13878
14950
  function assertMarketplaceBinding(binding, source) {
13879
- if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
14951
+ if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3") && (binding.agent !== "codex" || binding.method !== "reviewed" || basename9(source.path) !== "config.toml"))
14952
+ throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
14953
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename9(source.path) !== "settings.json"))
13880
14954
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
13881
14955
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
13882
14956
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
13883
14957
  }
13884
14958
  function rebindAgentDiscovery(binding, changes) {
13885
14959
  const budget = discoveryByteBudget();
14960
+ const installationRoots = codexInstallationRoots(binding);
13886
14961
  return { ...binding, sources: binding.sources.map((source) => {
13887
14962
  assertMarketplaceBinding(binding, source);
14963
+ if (!changes.has(source.path) && installationRoots.some((root2) => source.path.startsWith(root2 + sep6)))
14964
+ return source;
13888
14965
  const sha2562 = projected(source, changes, budget);
13889
14966
  if (!changes.has(source.path) && sha2562 !== source.sha256)
13890
14967
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
13891
14968
  return { ...source, sha256: sha2562 };
13892
14969
  }) };
13893
14970
  }
13894
- function assertProjectDiscovery(agent, directories, home, canonical4 = resolve14, reviewed) {
14971
+ function assertProjectDiscovery(agent, directories, home, canonical6 = resolve15, reviewed) {
14972
+ const homeConfigPath = canonical6(agentDiscoveryConfigPath(home, agent));
13895
14973
  for (const directory of directories) {
13896
- const isHome = resolve14(directory) === resolve14(home);
14974
+ const isHome = resolve15(directory) === resolve15(home);
13897
14975
  if (agent === "hermes") {
13898
14976
  assertHermesEnvironment(home);
13899
14977
  continue;
@@ -13902,10 +14980,11 @@ function assertProjectDiscovery(agent, directories, home, canonical4 = resolve14
13902
14980
  continue;
13903
14981
  const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : [".cursor/hooks.json"];
13904
14982
  for (const suffix of paths) {
13905
- const path = canonical4(join26(directory, suffix)), raw = read2(path);
14983
+ const path = canonical6(join27(directory, suffix)), raw = read2(path);
13906
14984
  if (raw === null)
13907
14985
  continue;
13908
- if (agent === "claude" && suffix === ".claude/settings.json" && path === canonical4(join26(home, suffix)))
14986
+ const isRootAliasedHomeConfig = agent === "claude" && suffix === ".claude/settings.json" || agent === "codex" && suffix === ".codex/config.toml";
14987
+ if (isRootAliasedHomeConfig && path === homeConfigPath)
13909
14988
  continue;
13910
14989
  if (suffix.endsWith(".jsonc"))
13911
14990
  throw new Error(`NATIVE_SKILL_DRIFT: project JSONC discovery configuration requires review: ${path}`);
@@ -13917,28 +14996,28 @@ function assertProjectDiscovery(agent, directories, home, canonical4 = resolve14
13917
14996
  const managed = reviewed.sources.flatMap((source) => source.hashMode === "claude-plugin-registry" ? source.managedPlugins ?? [] : []);
13918
14997
  if (exact && projected(exact) === exact.sha256 && managed.length) {
13919
14998
  const bindings = managed.map((item) => readPluginBinding(item.storeRoot, item.bindingId));
13920
- admittedPlugins = Object.entries(config.enabledPlugins).every(([id, enabled]) => typeof enabled === "boolean" && bindings.some((binding) => binding.target.pluginId === id && binding.target.registrations.some((scope) => scope.scope === "project" && scope.projectPath === canonical4(directory))));
14999
+ admittedPlugins = Object.entries(config.enabledPlugins).every(([id, enabled]) => typeof enabled === "boolean" && bindings.some((binding) => binding.target.pluginId === id && binding.target.registrations.some((scope) => scope.scope === "project" && scope.projectPath === canonical6(directory))));
13921
15000
  }
13922
15001
  }
13923
15002
  if (keys.some((key) => config[key] !== undefined && !(key === "enabledPlugins" && admittedPlugins)) || config.disableAllHooks === true || config.disableBundledSkills === false || config.hooksConfig?.enabled === false || config.permission?.skill !== undefined || config.permissions?.deny?.some((rule) => typeof rule === "string" && /^Skill(?:\(|$)/.test(rule)))
13924
15003
  throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project skill or hook configuration requires review: ${path}`);
13925
15004
  }
13926
15005
  if (agent === "claude") {
13927
- const commands = canonical4(join26(directory, ".claude/commands"));
15006
+ const commands = canonical6(join27(directory, ".claude/commands"));
13928
15007
  safe4(commands);
13929
- if (lstatSync20(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length)
15008
+ if (lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
13930
15009
  throw new Error("NATIVE_SKILL_DRIFT: legacy project command discovery requires a dedicated format adapter");
13931
15010
  }
13932
15011
  }
13933
15012
  }
13934
15013
  function agentDiscoveryConfigPath(home, agent) {
13935
- return join26(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
15014
+ return join27(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
13936
15015
  }
13937
15016
  function resolveAgentDiscovery(options) {
13938
- const canonical4 = options.canonical ?? resolve14, home = resolve14(options.home), agent = options.agent;
15017
+ const canonical6 = options.canonical ?? resolve15, home = resolve15(options.home), agent = options.agent;
13939
15018
  const sources = [], roots = new Set, builtinNames = [];
13940
- const witness = (path, format, fields) => {
13941
- const source = { path: canonical4(path), sha256: null, ...format ? { format, fields } : {} };
15019
+ const witness = (path, format, fields2) => {
15020
+ const source = { path: canonical6(path), sha256: null, ...format ? { format, fields: fields2 } : {} };
13942
15021
  source.sha256 = projected(source);
13943
15022
  sources.push(source);
13944
15023
  return read2(source.path);
@@ -13946,13 +15025,13 @@ function resolveAgentDiscovery(options) {
13946
15025
  if (agent === "hermes")
13947
15026
  assertHermesEnvironment(home);
13948
15027
  const configPath = agentDiscoveryConfigPath(home, agent);
13949
- const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : ["version"]);
15028
+ 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"] : ["version"]);
13950
15029
  const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
13951
15030
  const unresolved = (detail) => {
13952
15031
  throw new Error(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
13953
15032
  };
13954
15033
  if (agent === "hermes") {
13955
- const profile = witness(join26(home, ".hermes/active_profile"));
15034
+ const profile = witness(join27(home, ".hermes/active_profile"));
13956
15035
  if (profile?.trim() && profile.trim() !== "default")
13957
15036
  unresolved("a non-default profile is active");
13958
15037
  const declared = config.skills?.external_dirs ?? [], paths = typeof declared === "string" ? [declared] : declared;
@@ -13966,7 +15045,7 @@ function resolveAgentDiscovery(options) {
13966
15045
  continue;
13967
15046
  if (normalized.startsWith("~") && normalized !== "~" && !normalized.startsWith("~/"))
13968
15047
  unresolved("user-specific tilde expansion requires a separate discovery adapter");
13969
- const path = normalized === "~" ? home : normalized.startsWith("~/") ? join26(home, normalized.slice(2)) : resolve14(home, ".hermes", normalized);
15048
+ const path = normalized === "~" ? home : normalized.startsWith("~/") ? join27(home, normalized.slice(2)) : resolve15(home, ".hermes", normalized);
13970
15049
  safe4(path);
13971
15050
  roots.add(path);
13972
15051
  }
@@ -13975,24 +15054,24 @@ function resolveAgentDiscovery(options) {
13975
15054
  const executable = Bun.which("gemini");
13976
15055
  if (executable) {
13977
15056
  let packageRoot;
13978
- for (let directory = dirname16(realpathSync4(executable)), depth = 0;depth < 8; directory = dirname16(directory), depth++) {
13979
- const path = join26(directory, "package.json"), raw = read2(path);
15057
+ for (let directory = dirname17(realpathSync7(executable)), depth = 0;depth < 8; directory = dirname17(directory), depth++) {
15058
+ const path = join27(directory, "package.json"), raw = read2(path);
13980
15059
  if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
13981
15060
  packageRoot = directory;
13982
15061
  witness(path);
13983
15062
  break;
13984
15063
  }
13985
- if (dirname16(directory) === directory)
15064
+ if (dirname17(directory) === directory)
13986
15065
  break;
13987
15066
  }
13988
15067
  if (!packageRoot)
13989
15068
  unresolved("installed Gemini package layout is not recognized");
13990
- const builtinRoot = join26(packageRoot, "bundle/builtin");
15069
+ const builtinRoot = join27(packageRoot, "bundle/builtin");
13991
15070
  safe4(builtinRoot);
13992
- if (!lstatSync20(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
15071
+ if (!lstatSync21(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
13993
15072
  unresolved("installed Gemini builtin root is missing");
13994
- for (const name of readdirSync9(builtinRoot).sort()) {
13995
- const text2 = witness(join26(builtinRoot, name, "SKILL.md"));
15073
+ for (const name of readdirSync10(builtinRoot).sort()) {
15074
+ const text2 = witness(join27(builtinRoot, name, "SKILL.md"));
13996
15075
  const match = text2?.match(/^---\r?\n[\s\S]*?^name:\s*([a-z0-9][a-z0-9-]*)\s*$/m);
13997
15076
  if (!match?.[1] || builtinNames.includes(match[1]))
13998
15077
  unresolved("installed Gemini builtin name is missing or ambiguous");
@@ -14005,11 +15084,12 @@ function resolveAgentDiscovery(options) {
14005
15084
  if (retained.agent !== agent || retained.method !== "reviewed")
14006
15085
  unresolved("invalid retained review");
14007
15086
  verifyAgentDiscovery(retained);
14008
- if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
15087
+ if (!retained.sources.some((source) => source.path === canonical6(configPath) && source.format === undefined && source.fields === undefined))
14009
15088
  unresolved("retained review is missing the full agent configuration source");
14010
- if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
15089
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3"))) && source.path !== canonical6(configPath)))
14011
15090
  unresolved("retained settings witness names another configuration source");
14012
- 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))
15091
+ const covered = supersedesCodexProjection(retained.sources, canonical6(configPath)) ? sources.filter((source) => !isSupersededCodexProjection(agent, canonical6(configPath), source)) : sources;
15092
+ if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual5(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
14013
15093
  unresolved("retained review is missing current runtime discovery coverage");
14014
15094
  return retained;
14015
15095
  }
@@ -14023,48 +15103,49 @@ function resolveAgentDiscovery(options) {
14023
15103
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
14024
15104
  throw new Error("Explicit discovery reviews require full source-file hashes");
14025
15105
  verifyAgentDiscovery(supplied);
14026
- if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
15106
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3"))) && source.path !== canonical6(configPath)))
14027
15107
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
14028
- if (!review.sources.some((source) => source.path === canonical4(configPath)))
15108
+ if (!review.sources.some((source) => source.path === canonical6(configPath)))
14029
15109
  throw new Error("Discovery review must include the agent configuration source");
14030
- return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: [...sources, ...review.sources], ...agent === "gemini" ? { builtinNames } : {} };
15110
+ const reviewedSources = supersedesCodexProjection(review.sources, canonical6(configPath)) ? sources.filter((source) => !isSupersededCodexProjection(agent, canonical6(configPath), source)) : sources;
15111
+ return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: [...reviewedSources, ...review.sources], ...agent === "gemini" ? { builtinNames } : {} };
14031
15112
  }
14032
15113
  function plugin(root2) {
14033
- root2 = canonical4(root2);
15114
+ root2 = canonical6(root2);
14034
15115
  safe4(root2);
14035
- const manifestPath = join26(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
15116
+ const manifestPath = join27(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
14036
15117
  const raw = witness(manifestPath);
14037
15118
  if (raw === null)
14038
15119
  unresolved("plugin manifest missing");
14039
15120
  const manifest = parseConfig(raw, manifestPath);
14040
- const hooks = witness(join26(root2, "hooks/hooks.json"));
15121
+ const hooks = witness(join27(root2, "hooks/hooks.json"));
14041
15122
  if (manifest.hooks !== undefined || hooks !== null)
14042
15123
  unresolved("plugin hooks require a separate no-skill-injection review");
14043
- const commands = join26(root2, "commands");
15124
+ const commands = join27(root2, "commands");
14044
15125
  safe4(commands);
14045
- if (agent === "claude" && (manifest.commands !== undefined || lstatSync20(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length))
15126
+ if (agent === "claude" && (manifest.commands !== undefined || lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length))
14046
15127
  unresolved("legacy command discovery requires retirement or a dedicated format adapter");
14047
- const rootSkill = witness(join26(root2, "SKILL.md"));
15128
+ const rootSkill = witness(join27(root2, "SKILL.md"));
14048
15129
  if (rootSkill !== null)
14049
15130
  roots.add(root2);
14050
15131
  const declared = manifest.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
14051
15132
  if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
14052
15133
  unresolved("unsupported plugin skill paths");
14053
15134
  if (agent === "claude")
14054
- roots.add(join26(root2, "skills"));
15135
+ roots.add(join27(root2, "skills"));
14055
15136
  for (const value of declared) {
14056
- const path = resolve14(root2, value);
14057
- if (path !== root2 && !path.startsWith(root2 + sep5))
15137
+ const path = resolve15(root2, value);
15138
+ if (path !== root2 && !path.startsWith(root2 + sep6))
14058
15139
  unresolved("plugin skill path escapes its root");
14059
- roots.add(path.endsWith(`${sep5}SKILL.md`) ? dirname16(path) : path);
15140
+ roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname17(path) : path);
14060
15141
  }
14061
15142
  }
14062
15143
  if (agent === "claude") {
14063
- const registrations = witness(join26(home, ".claude/plugins/installed_plugins.json"));
14064
- const installed = registrations === null ? {} : parseConfig(registrations, join26(home, ".claude/plugins/installed_plugins.json")).plugins;
14065
- const commands = canonical4(join26(home, ".claude/commands"));
15144
+ const registrations = witness(join27(home, ".claude/plugins/installed_plugins.json"));
15145
+ const installed = registrations === null ? {} : parseConfig(registrations, join27(home, ".claude/plugins/installed_plugins.json")).plugins;
15146
+ const commands = canonical6(join27(home, ".claude/commands"));
14066
15147
  safe4(commands);
14067
- if (lstatSync20(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length)
15148
+ if (lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
14068
15149
  unresolved("legacy home command discovery requires a dedicated format adapter");
14069
15150
  for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
14070
15151
  if (enabled === false)
@@ -14084,34 +15165,34 @@ function resolveAgentDiscovery(options) {
14084
15165
  unresolved("unsupported plugin enablement");
14085
15166
  const split = id.lastIndexOf("@"), name = id.slice(0, split), marketplace = id.slice(split + 1);
14086
15167
  const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
14087
- if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute14(market.source))
15168
+ if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute16(market.source))
14088
15169
  unresolved("enabled plugin marketplace is not an explicit local source");
14089
- const catalogText = witness(join26(market.source, ".agents/plugins/marketplace.json"));
15170
+ const catalogText = witness(join27(market.source, ".agents/plugins/marketplace.json"));
14090
15171
  if (catalogText === null)
14091
15172
  unresolved("marketplace catalog missing");
14092
- const catalog = parseConfig(catalogText, join26(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
15173
+ const catalog = parseConfig(catalogText, join27(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
14093
15174
  if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
14094
15175
  unresolved("plugin catalog source is missing or ambiguous");
14095
- plugin(resolve14(market.source, matches[0].source.path));
15176
+ plugin(resolve15(market.source, matches[0].source.path));
14096
15177
  }
14097
15178
  } else if (agent === "opencode") {
14098
- if (witness(join26(home, ".config/opencode/opencode.jsonc")) !== null || witness(join26(home, ".config/opencode/config.json")) !== null)
15179
+ if (witness(join27(home, ".config/opencode/opencode.jsonc")) !== null || witness(join27(home, ".config/opencode/config.json")) !== null)
14099
15180
  unresolved("additional config layers require review");
14100
15181
  if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
14101
15182
  unresolved("external plugin or remote skill source");
14102
15183
  for (const path of config.skills?.paths ?? []) {
14103
- if (typeof path !== "string" || !isAbsolute14(path))
15184
+ if (typeof path !== "string" || !isAbsolute16(path))
14104
15185
  unresolved("relative or malformed added skill root");
14105
15186
  roots.add(path);
14106
15187
  }
14107
- const localPlugins = join26(home, ".config/opencode/plugins");
15188
+ const localPlugins = join27(home, ".config/opencode/plugins");
14108
15189
  safe4(localPlugins);
14109
- if (lstatSync20(localPlugins, { throwIfNoEntry: false }) && readdirSync9(localPlugins).some((name) => name !== "skills-cli.js"))
15190
+ if (lstatSync21(localPlugins, { throwIfNoEntry: false }) && readdirSync10(localPlugins).some((name) => name !== "skills-cli.js"))
14110
15191
  unresolved("additional local plugin hooks require review");
14111
15192
  } else if (agent === "hermes") {
14112
- for (const directory of [join26(home, ".hermes/plugins"), join26(home, ".hermes/hermes-agent")]) {
15193
+ for (const directory of [join27(home, ".hermes/plugins"), join27(home, ".hermes/hermes-agent")]) {
14113
15194
  safe4(directory);
14114
- if (lstatSync20(directory, { throwIfNoEntry: false }))
15195
+ if (lstatSync21(directory, { throwIfNoEntry: false }))
14115
15196
  unresolved("installed runtime/plugin discovery requires reviewed source bindings");
14116
15197
  }
14117
15198
  if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
@@ -14119,24 +15200,24 @@ function resolveAgentDiscovery(options) {
14119
15200
  } else if (agent === "gemini") {
14120
15201
  if (Object.keys(config.extensions ?? {}).length || config.skills?.paths?.length)
14121
15202
  unresolved("custom extension or skill paths");
14122
- const directory = join26(home, ".gemini/extensions");
15203
+ const directory = join27(home, ".gemini/extensions");
14123
15204
  safe4(directory);
14124
- if (lstatSync20(directory, { throwIfNoEntry: false }))
14125
- for (const name of readdirSync9(directory)) {
14126
- const root2 = join26(directory, name);
15205
+ if (lstatSync21(directory, { throwIfNoEntry: false }))
15206
+ for (const name of readdirSync10(directory)) {
15207
+ const root2 = join27(directory, name);
14127
15208
  safe4(root2);
14128
- if (!lstatSync20(root2)?.isDirectory())
15209
+ if (!lstatSync21(root2)?.isDirectory())
14129
15210
  continue;
14130
- const raw = witness(join26(root2, "gemini-extension.json"));
15211
+ const raw = witness(join27(root2, "gemini-extension.json"));
14131
15212
  if (raw === null)
14132
15213
  unresolved("extension registration missing");
14133
- const manifest = parseConfig(raw, join26(root2, "gemini-extension.json"));
14134
- if (manifest.hooks !== undefined || witness(join26(root2, "hooks/hooks.json")) !== null || witness(join26(root2, ".gemini-extension-install.json")) !== null)
15214
+ const manifest = parseConfig(raw, join27(root2, "gemini-extension.json"));
15215
+ if (manifest.hooks !== undefined || witness(join27(root2, "hooks/hooks.json")) !== null || witness(join27(root2, ".gemini-extension-install.json")) !== null)
14135
15216
  unresolved("extension hooks or linked registration require review");
14136
- roots.add(join26(root2, "skills"));
15217
+ roots.add(join27(root2, "skills"));
14137
15218
  }
14138
15219
  }
14139
- return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join26(home, ".hermes/plugins"), join26(home, ".hermes/hermes-agent")].map((path) => canonical4(path))) } : {} };
15220
+ return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join27(home, ".hermes/plugins"), join27(home, ".hermes/hermes-agent")].map((path) => canonical6(path))) } : {} };
14140
15221
  }
14141
15222
  var NATIVE_DISCOVERY_ROOTS = NATIVE_SKILL_ROOTS.map(([, path]) => path.split("/"));
14142
15223
  var OFFICIAL_SKILL_NAMES = new Set(SKILLS.map((skill) => skill.name));
@@ -14191,7 +15272,7 @@ __export2(exports_external, {
14191
15272
  regexes: () => exports_regexes,
14192
15273
  regex: () => _regex,
14193
15274
  refine: () => refine,
14194
- record: () => record2,
15275
+ record: () => record3,
14195
15276
  readonly: () => readonly,
14196
15277
  property: () => _property,
14197
15278
  properties: () => _properties,
@@ -14207,7 +15288,7 @@ __export2(exports_external, {
14207
15288
  overwrite: () => _overwrite,
14208
15289
  output: () => output,
14209
15290
  optional: () => optional,
14210
- object: () => object4,
15291
+ object: () => object5,
14211
15292
  number: () => number2,
14212
15293
  nullish: () => nullish2,
14213
15294
  nullable: () => nullable,
@@ -14256,7 +15337,7 @@ __export2(exports_external, {
14256
15337
  input: () => input,
14257
15338
  includes: () => _includes,
14258
15339
  httpUrl: () => httpUrl,
14259
- hostname: () => hostname2,
15340
+ hostname: () => hostname3,
14260
15341
  hex: () => hex2,
14261
15342
  hash: () => hash2,
14262
15343
  guid: () => guid2,
@@ -14833,9 +15914,9 @@ function floatSafeRemainder(val, step) {
14833
15914
  return ratio - roundedRatio;
14834
15915
  }
14835
15916
  var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
14836
- function defineLazy(object4, key, getter) {
15917
+ function defineLazy(object5, key, getter) {
14837
15918
  let value = undefined;
14838
- Object.defineProperty(object4, key, {
15919
+ Object.defineProperty(object5, key, {
14839
15920
  get() {
14840
15921
  if (value === EVALUATING) {
14841
15922
  return;
@@ -14847,7 +15928,7 @@ function defineLazy(object4, key, getter) {
14847
15928
  return value;
14848
15929
  },
14849
15930
  set(v) {
14850
- Object.defineProperty(object4, key, {
15931
+ Object.defineProperty(object5, key, {
14851
15932
  value: v
14852
15933
  });
14853
15934
  },
@@ -16032,7 +17113,7 @@ __export2(exports_regexes, {
16032
17113
  idnEmail: () => idnEmail,
16033
17114
  httpProtocol: () => httpProtocol,
16034
17115
  html5Email: () => html5Email,
16035
- hostname: () => hostname,
17116
+ hostname: () => hostname2,
16036
17117
  hex: () => hex,
16037
17118
  guid: () => guid,
16038
17119
  extendedDuration: () => extendedDuration,
@@ -16094,7 +17175,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
16094
17175
  var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
16095
17176
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
16096
17177
  var base64url = /^[A-Za-z0-9_-]*$/;
16097
- var hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
17178
+ var hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
16098
17179
  var domain = /^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
16099
17180
  var httpProtocol = /^https?$/;
16100
17181
  var e164 = /^\+[1-9]\d{6,14}$/;
@@ -16924,9 +18005,9 @@ var asciiTabOrNewline = /[\t\n\r]/g;
16924
18005
  function stripTabAndNewline(value) {
16925
18006
  return value.replace(asciiTabOrNewline, "");
16926
18007
  }
16927
- function urlHostnameOk(url, hostname2) {
16928
- hostname2.lastIndex = 0;
16929
- return hostname2.test(url.hostname);
18008
+ function urlHostnameOk(url, hostname3) {
18009
+ hostname3.lastIndex = 0;
18010
+ return hostname3.test(url.hostname);
16930
18011
  }
16931
18012
  function urlProtocolOk(url, protocol) {
16932
18013
  protocol.lastIndex = 0;
@@ -17416,15 +18497,15 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
17416
18497
  } catch (_err) {}
17417
18498
  }
17418
18499
  const input = payload.value;
17419
- const isDate = input instanceof Date;
17420
- const isValidDate = isDate && !Number.isNaN(input.getTime());
18500
+ const isDate2 = input instanceof Date;
18501
+ const isValidDate = isDate2 && !Number.isNaN(input.getTime());
17421
18502
  if (isValidDate)
17422
18503
  return payload;
17423
18504
  payload.issues.push({
17424
18505
  expected: "date",
17425
18506
  code: "invalid_type",
17426
18507
  input,
17427
- ...isDate ? { received: "Invalid Date" } : {},
18508
+ ...isDate2 ? { received: "Invalid Date" } : {},
17428
18509
  inst
17429
18510
  });
17430
18511
  return payload;
@@ -29176,8 +30257,8 @@ function foldObjects(members2) {
29176
30257
  }
29177
30258
  const properties = {};
29178
30259
  const required2 = new Set;
29179
- for (const object4 of objects) {
29180
- for (const key in object4.properties) {
30260
+ for (const object5 of objects) {
30261
+ for (const key in object5.properties) {
29181
30262
  if (Object.prototype.hasOwnProperty.call(properties, key))
29182
30263
  continue;
29183
30264
  const parts = [];
@@ -29191,18 +30272,18 @@ function foldObjects(members2) {
29191
30272
  const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts };
29192
30273
  assignProp(properties, key, merged);
29193
30274
  }
29194
- for (const key of object4.required ?? [])
30275
+ for (const key of object5.required ?? [])
29195
30276
  required2.add(key);
29196
30277
  }
29197
30278
  const folded = { type: "object", properties };
29198
30279
  if (required2.size)
29199
30280
  folded.required = [...required2];
29200
- if (objects.every((object4) => object4.additionalProperties === false)) {
30281
+ if (objects.every((object5) => object5.additionalProperties === false)) {
29201
30282
  folded.additionalProperties = false;
29202
30283
  } else {
29203
30284
  const constraints = [];
29204
- for (const object4 of objects) {
29205
- const constraint = undeclaredConstraint(object4);
30285
+ for (const object5 of objects) {
30286
+ const constraint = undeclaredConstraint(object5);
29206
30287
  if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint)))
29207
30288
  constraints.push(constraint);
29208
30289
  }
@@ -29877,8 +30958,8 @@ function rewriteKeyNames(ctx) {
29877
30958
  bySchema.set(entry.schema, entry);
29878
30959
  }
29879
30960
  const rewrites = new Map;
29880
- for (const record2 of pendingRecords.get(ctx) ?? []) {
29881
- const seen = ctx.seen.get(record2);
30961
+ for (const record3 of pendingRecords.get(ctx) ?? []) {
30962
+ const seen = ctx.seen.get(record3);
29882
30963
  const names = (seen?.def ?? seen?.schema)?.propertyNames;
29883
30964
  if (!names || names === true || rewrites.has(names))
29884
30965
  continue;
@@ -30203,7 +31284,7 @@ __export2(exports_schemas2, {
30203
31284
  strictObject: () => strictObject,
30204
31285
  set: () => set,
30205
31286
  refine: () => refine,
30206
- record: () => record2,
31287
+ record: () => record3,
30207
31288
  readonly: () => readonly,
30208
31289
  promise: () => promise,
30209
31290
  preprocess: () => preprocess,
@@ -30211,7 +31292,7 @@ __export2(exports_schemas2, {
30211
31292
  pipe: () => pipe,
30212
31293
  partialRecord: () => partialRecord,
30213
31294
  optional: () => optional,
30214
- object: () => object4,
31295
+ object: () => object5,
30215
31296
  number: () => number2,
30216
31297
  nullish: () => nullish2,
30217
31298
  nullable: () => nullable,
@@ -30241,7 +31322,7 @@ __export2(exports_schemas2, {
30241
31322
  int: () => int,
30242
31323
  instanceof: () => _instanceof,
30243
31324
  httpUrl: () => httpUrl,
30244
- hostname: () => hostname2,
31325
+ hostname: () => hostname3,
30245
31326
  hex: () => hex2,
30246
31327
  hash: () => hash2,
30247
31328
  guid: () => guid2,
@@ -30943,7 +32024,7 @@ var ZodCustomStringFormat = /* @__PURE__ */ $constructor("ZodCustomStringFormat"
30943
32024
  function stringFormat(format, fnOrRegex, _params = {}) {
30944
32025
  return _stringFormat(ZodCustomStringFormat, format, fnOrRegex, _params);
30945
32026
  }
30946
- function hostname2(_params) {
32027
+ function hostname3(_params) {
30947
32028
  return _stringFormat(ZodCustomStringFormat, "hostname", exports_regexes.hostname, _params);
30948
32029
  }
30949
32030
  function hex2(_params) {
@@ -31249,7 +32330,7 @@ var ZodObject = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
31249
32330
  return exports_util.required(ZodNonOptional, this, args[0]);
31250
32331
  }
31251
32332
  });
31252
- function object4(shape, params) {
32333
+ function object5(shape, params) {
31253
32334
  const def = {
31254
32335
  type: "object",
31255
32336
  shape: shape ?? {},
@@ -31365,7 +32446,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
31365
32446
  inst.keyType = def.keyType;
31366
32447
  inst.valueType = def.valueType;
31367
32448
  });
31368
- function record2(keyType, valueType, params) {
32449
+ function record3(keyType, valueType, params) {
31369
32450
  if (!valueType || !valueType._zod) {
31370
32451
  return new ZodRecord({
31371
32452
  type: "record",
@@ -31831,7 +32912,7 @@ var stringbool = (...args) => _stringbool({
31831
32912
  }, ...args);
31832
32913
  function json(params) {
31833
32914
  const jsonSchema = lazy(() => {
31834
- return union([string2(params), number2(), boolean2(), _null3(), array2(jsonSchema), record2(string2(), jsonSchema)]);
32915
+ return union([string2(params), number2(), boolean2(), _null3(), array2(jsonSchema), record3(string2(), jsonSchema)]);
31835
32916
  });
31836
32917
  return jsonSchema;
31837
32918
  }
@@ -32622,7 +33703,7 @@ function date4(params) {
32622
33703
  return _coercedDate(ZodDate, params);
32623
33704
  }
32624
33705
  var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
32625
- var MISSING_API_URL_HINT = `run: skills auth login, or set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
33706
+ var MISSING_API_URL_HINT = `run: skills login, or set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
32626
33707
  var VENDOR_TERMS = [
32627
33708
  "Google Gemini",
32628
33709
  "OpenAI Sora",
@@ -33018,7 +34099,7 @@ var revocation = exports_external.object({
33018
34099
  });
33019
34100
  var page = (item) => exports_external.object({ contractVersion: exports_external.literal(1), items: exports_external.array(item).max(100), nextCursor: exports_external.string().min(1).max(2048).nullable() });
33020
34101
  var results = { preview, draft: preview.nullable(), activate: exports_external.object({ replayed: exports_external.boolean(), consent }), list: page(consent), get: consent.nullable(), occurrences: page(occurrence), revoke: revocation };
33021
- var record4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
34102
+ var record5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
33022
34103
  var uuid8 = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
33023
34104
  var text2 = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v);
33024
34105
  var role2 = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v);
@@ -33033,15 +34114,15 @@ function invalid3() {
33033
34114
  throw new Error(invalidWorkspaceResult);
33034
34115
  }
33035
34116
  function organization(v) {
33036
- if (!record4(v) || !uuid8(v.id) || !text2(v.slug) || !text2(v.name))
34117
+ if (!record5(v) || !uuid8(v.id) || !text2(v.slug) || !text2(v.name))
33037
34118
  return invalid3();
33038
34119
  return { id: v.id, slug: v.slug, name: v.name };
33039
34120
  }
33040
34121
  function parseWorkspaceIdentity(value, expectedUserId) {
33041
- if (!record4(value))
34122
+ if (!record5(value))
33042
34123
  return invalid3();
33043
34124
  const user = value.user;
33044
- if (!record4(user) || !uuid8(user.id) || !uuid8(user.membershipId) || !text2(user.email, 320) || !(user.displayName === null || text2(user.displayName)) || !role2(user.role))
34125
+ if (!record5(user) || !uuid8(user.id) || !uuid8(user.membershipId) || !text2(user.email, 320) || !(user.displayName === null || text2(user.displayName)) || !role2(user.role))
33045
34126
  return invalid3();
33046
34127
  if (user.id !== expectedUserId)
33047
34128
  throw new WorkspaceIdentityMismatchError;
@@ -33053,10 +34134,10 @@ function sessionToken(value) {
33053
34134
  return value;
33054
34135
  }
33055
34136
  function parseWorkspaceSession(value, expected) {
33056
- const identity3 = parseWorkspaceIdentity(value, expected.userId);
33057
- if (identity3.user.membershipId !== expected.membershipId)
34137
+ const identity4 = parseWorkspaceIdentity(value, expected.userId);
34138
+ if (identity4.user.membershipId !== expected.membershipId)
33058
34139
  throw new WorkspaceIdentityMismatchError;
33059
- return { token: sessionToken(value.token), ...identity3 };
34140
+ return { token: sessionToken(value.token), ...identity4 };
33060
34141
  }
33061
34142
  var SKILLS_API_KEY_REF = credentialPointerEnvKey("skills");
33062
34143
  var quoteUnavailableMessages = Object.freeze({
@@ -33111,16 +34192,16 @@ var invalid4 = () => {
33111
34192
  };
33112
34193
  var publicationUuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
33113
34194
  var hash3 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
33114
- var record8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
33115
- var exact = (v, keys) => record8(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
34195
+ var record9 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
34196
+ var exact = (v, keys) => record9(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
33116
34197
  var date5 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v));
33117
- var publicationSha256 = (bytes) => createHash20("sha256").update(bytes).digest("hex");
34198
+ var publicationSha256 = (bytes) => createHash222("sha256").update(bytes).digest("hex");
33118
34199
  function checkedPublicationDeclaration(value) {
33119
34200
  if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash3(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
33120
34201
  return bad();
33121
34202
  try {
33122
34203
  const manifest = JSON.parse(value.manifestText);
33123
- if (!record8(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash3(manifest.provenance?.content_hash))
34204
+ if (!record9(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash3(manifest.provenance?.content_hash))
33124
34205
  return bad();
33125
34206
  } catch {
33126
34207
  return bad();
@@ -33178,7 +34259,7 @@ async function boundedJson(url2, init, token, mutation, budget2 = 15000, signal)
33178
34259
  return invalid4();
33179
34260
  }
33180
34261
  if (!response.ok) {
33181
- const code = record8(body) && typeof body.code === "string" && Object.hasOwn(failures2, body.code) ? body.code : null;
34262
+ const code = record9(body) && typeof body.code === "string" && Object.hasOwn(failures2, body.code) ? body.code : null;
33182
34263
  if (code && failures2[code][0] === response.status)
33183
34264
  throw new PrivatePublicationError(code, failures2[code][1], mutation && code === "PUBLICATION_UNCERTAIN", response.status);
33184
34265
  throw new PrivatePublicationError("PUBLICATION_REQUEST_FAILED", "The publication request was refused. Inspect its status before retrying.", mutation && response.status >= 500, response.status);
@@ -33233,8 +34314,8 @@ class RemotePrivatePublicationsClient {
33233
34314
  if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
33234
34315
  return bad();
33235
34316
  const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
33236
- const p = record8(response) && response.privatePublishing;
33237
- if (!record8(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || typeof p.executionEnabled !== "boolean")
34317
+ const p = record9(response) && response.privatePublishing;
34318
+ if (!record9(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || typeof p.executionEnabled !== "boolean")
33238
34319
  throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
33239
34320
  return Object.freeze({ ...p });
33240
34321
  }
@@ -33250,7 +34331,7 @@ class RemotePrivatePublicationsClient {
33250
34331
  async#view(path, method, skillId, intentId, declaration, options = {}) {
33251
34332
  const value = await boundedJson(path, { method, ...method === "GET" ? {} : { body: JSON.stringify(declaration ?? {}) } }, this.#token, method !== "GET", options.timeoutMs, options.signal);
33252
34333
  try {
33253
- if (!record8(value) || !Object.keys(value).every((k) => k === "upload" || k === "changed") || value.changed !== undefined && typeof value.changed !== "boolean")
34334
+ if (!record9(value) || !Object.keys(value).every((k) => k === "upload" || k === "changed") || value.changed !== undefined && typeof value.changed !== "boolean")
33254
34335
  return invalid4();
33255
34336
  return checkedPublicationView(value.upload, skillId, intentId, declaration);
33256
34337
  } catch (error61) {
@@ -33359,12 +34440,12 @@ class RemotePrivatePublicationsClient {
33359
34440
  previous = view;
33360
34441
  if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
33361
34442
  return view;
33362
- await new Promise((resolve17) => {
34443
+ await new Promise((resolve18) => {
33363
34444
  const timer = setTimeout(done, Math.min(1000, until - Date.now()));
33364
34445
  function done() {
33365
34446
  clearTimeout(timer);
33366
34447
  options.signal?.removeEventListener("abort", done);
33367
- resolve17();
34448
+ resolve18();
33368
34449
  }
33369
34450
  options.signal?.addEventListener("abort", done, { once: true });
33370
34451
  });
@@ -33372,7 +34453,7 @@ class RemotePrivatePublicationsClient {
33372
34453
  }
33373
34454
  }
33374
34455
  var uuid12 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
33375
- var sha3 = exports_external.string().regex(/^[a-f0-9]{64}$/);
34456
+ var sha5 = exports_external.string().regex(/^[a-f0-9]{64}$/);
33376
34457
  var common = exports_external.object({
33377
34458
  contractVersion: exports_external.literal(1),
33378
34459
  apiOrigin: exports_external.string().min(1).max(4096),
@@ -33385,7 +34466,7 @@ var intentSchema = exports_external.discriminatedUnion("operation", [
33385
34466
  common.extend({
33386
34467
  operation: exports_external.literal("activate"),
33387
34468
  draftId: uuid12,
33388
- termsSha256: sha3,
34469
+ termsSha256: sha5,
33389
34470
  approvalDeadline: exports_external.string().datetime(),
33390
34471
  approval: recurringActivationSchema
33391
34472
  }).strict(),
@@ -33393,14 +34474,14 @@ var intentSchema = exports_external.discriminatedUnion("operation", [
33393
34474
  ]);
33394
34475
  var stateSchema = exports_external.object({
33395
34476
  contractVersion: exports_external.literal(1),
33396
- intentSha256: sha3,
34477
+ intentSha256: sha5,
33397
34478
  phase: exports_external.enum(["prepared", "pending", "observed"]),
33398
34479
  consentId: uuid12.nullable()
33399
34480
  }).strict();
33400
34481
  var uuid13 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
33401
34482
  var context = { userId: uuid13.optional(), membershipId: uuid13.optional() };
33402
34483
  var page2 = { limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().min(1).max(2048).optional() };
33403
- var directory = exports_external.string().max(4096).refine(isAbsolute16, "Use an absolute recovery directory");
34484
+ var directory = exports_external.string().max(4096).refine(isAbsolute18, "Use an absolute recovery directory");
33404
34485
  var email4 = exports_external.string().trim().toLowerCase().email().max(254);
33405
34486
  var code = exports_external.string().regex(/^\d{6}$/);
33406
34487
  var recurringSurfaceSchemas = {
@@ -34531,7 +35612,73 @@ var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
34531
35612
  var SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
34532
35613
 
34533
35614
  // src/lib/claude-skills-coordination.ts
34534
- function coordinateClaudeSettings(before, replacement, options) {
35615
+ class ClaudeSettingsRefusal extends Error {
35616
+ code;
35617
+ constructor(code2, message) {
35618
+ super(`${code2}: ${message}`);
35619
+ this.code = code2;
35620
+ this.name = "ClaudeSettingsRefusal";
35621
+ }
35622
+ }
35623
+ var SKILLS_HOOK_COMMAND = /(?:^|\s)hook user-prompt --agent (?:claude|codex|gemini)(?:\s|$)/;
35624
+ function claudeSkillsUsage(before, home) {
35625
+ const evidence = [], events2 = [];
35626
+ const hooks = before === null ? undefined : JSON.parse(before)?.hooks;
35627
+ if (hooks && typeof hooks === "object" && !Array.isArray(hooks)) {
35628
+ for (const [event, entries] of Object.entries(hooks)) {
35629
+ if (!Array.isArray(entries))
35630
+ continue;
35631
+ const commands = entries.flatMap((entry) => [entry?.command, ...Array.isArray(entry?.hooks) ? entry.hooks.map((hook) => hook?.command) : []]);
35632
+ if (commands.some((command) => typeof command === "string" && SKILLS_HOOK_COMMAND.test(command)))
35633
+ events2.push(event);
35634
+ }
35635
+ }
35636
+ if (events2.length)
35637
+ evidence.push(`Skills-managed hook entries in ${events2.join(", ")}`);
35638
+ const bridge = join10(home, ".claude", "skills", "skills-cli", "SKILL.md");
35639
+ let bridgePresent;
35640
+ try {
35641
+ bridgePresent = lstatSync7(bridge, { throwIfNoEntry: false }) !== undefined;
35642
+ } catch (error61) {
35643
+ bridgePresent = !["ENOENT", "ENOTDIR"].includes(error61.code ?? "");
35644
+ }
35645
+ if (bridgePresent)
35646
+ evidence.push(`the Skills bridge ${bridge}`);
35647
+ return evidence;
35648
+ }
35649
+ function skillsPolicyLocations(dataDir3) {
35650
+ const state = (path) => existsSync11(path) ? "present" : "absent";
35651
+ if (dataDir3 !== undefined)
35652
+ return [`${join10(resolve7(dataDir3), "agent-policy.json")} (explicit data directory, absent)`];
35653
+ const env = process.env, home = env.HOME || env.USERPROFILE || homedir6() || "/tmp", searched = [];
35654
+ let selected = false;
35655
+ for (const key of ["HASNA_SKILLS_DIR", "HASNA_SKILLS_HOME", "SKILLS_HOME"]) {
35656
+ const value = env[key]?.trim();
35657
+ if (selected)
35658
+ searched.push(`$${key} (not consulted: an earlier override is set)`);
35659
+ else if (!value)
35660
+ searched.push(`$${key} (unset)`);
35661
+ else {
35662
+ selected = true;
35663
+ searched.push(`$${key} -> ${join10(resolve7(value), "agent-policy.json")} (selected, absent)`);
35664
+ }
35665
+ }
35666
+ const override = env.HASNA_DATA_HOME;
35667
+ const base = typeof override === "string" && override.length > 0 ? override : process.platform === "darwin" ? join10(home, "Library", "Application Support", "Hasna") : join10(home, ".local", "share", "hasna");
35668
+ const xdg = join10(base, "skills"), xdgPolicy = join10(xdg, "agent-policy.json");
35669
+ const adopted = typeof override === "string" && override.trim().length > 0 || existsSync11(join10(xdg, "server.db")) || existsSync11(join10(xdg, "config.json"));
35670
+ if (selected)
35671
+ searched.push(`${xdgPolicy} (not consulted, ${state(xdgPolicy)})`);
35672
+ else if (adopted) {
35673
+ selected = true;
35674
+ searched.push(`${xdgPolicy} (selected, absent)`);
35675
+ } else
35676
+ searched.push(`${xdgPolicy} (not selected: the directory holds no server.db or config.json and HASNA_DATA_HOME is unset; ${state(xdgPolicy)})`);
35677
+ const legacy = join10(home, ".hasna", "skills", "agent-policy.json");
35678
+ searched.push(selected ? `${legacy} (not consulted, ${state(legacy)})` : `${legacy} (selected, absent)`);
35679
+ return searched;
35680
+ }
35681
+ function coordinateClaudeSettings(before, replacement, options, beforeWrite) {
34535
35682
  const prior = JSON.parse(before ?? "{}"), next = JSON.parse(replacement);
34536
35683
  const events2 = new Set([...Object.keys(prior.hooks ?? {}), ...Object.keys(next.hooks ?? {})]);
34537
35684
  const changed = [...events2].filter((event) => JSON.stringify(prior.hooks?.[event]) !== JSON.stringify(next.hooks?.[event]));
@@ -34542,15 +35689,20 @@ function coordinateClaudeSettings(before, replacement, options) {
34542
35689
  expectedSettingsSha256: before === null ? "absent" : createHash6("sha256").update(before).digest("hex"),
34543
35690
  replacement
34544
35691
  });
34545
- if (!plan)
35692
+ if (!plan) {
35693
+ const usage = claudeSkillsUsage(before, options.home);
35694
+ if (usage.length)
35695
+ throw new ClaudeSettingsRefusal("skills_policy_unresolved", `Skills manages ${join10(options.home, ".claude", "settings.json")} (${usage.join("; ")}), but no Skills agent policy resolved, ` + "so Hooks cannot coordinate this change and wrote nothing. Locations searched, in Skills resolution order: " + skillsPolicyLocations(options.dataDir).map((location, index) => `${index + 1}) ${location}`).join("; ") + ". Run hooks with the environment that selects the Skills data directory (for example HASNA_SKILLS_DIR), " + "or repair the integration with `skills hook install`.");
34546
35696
  return false;
35697
+ }
35698
+ beforeWrite?.();
34547
35699
  applyAgentIntegration(plan);
34548
35700
  return true;
34549
35701
  }
34550
35702
 
34551
35703
  // src/lib/installer.ts
34552
35704
  var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
34553
- var HOOKS_DIR = existsSync11(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
35705
+ var HOOKS_DIR = existsSync12(join11(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join11(__dirname2, "..", "..", "hooks") : join11(__dirname2, "..", "hooks");
34554
35706
  function normalizeHookName(name) {
34555
35707
  return name.startsWith("hook-") ? name : `hook-${name}`;
34556
35708
  }
@@ -34620,9 +35772,9 @@ function getTargetSettingsDir(target) {
34620
35772
  }
34621
35773
  function getGlobalSettingsPathOverride(target) {
34622
35774
  if (target === "codex")
34623
- return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH ?? (process.env.CODEX_HOME ? resolve7(process.env.CODEX_HOME, "hooks.json") : undefined);
35775
+ return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH ?? (process.env.CODEX_HOME ? resolve16(process.env.CODEX_HOME, "hooks.json") : undefined);
34624
35776
  if (target === "claude")
34625
- return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH ?? (process.env.CLAUDE_CONFIG_DIR ? resolve7(process.env.CLAUDE_CONFIG_DIR, "settings.json") : undefined);
35777
+ return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH ?? (process.env.CLAUDE_CONFIG_DIR ? resolve16(process.env.CLAUDE_CONFIG_DIR, "settings.json") : undefined);
34626
35778
  if (target === "gemini")
34627
35779
  return process.env.HASNA_HOOKS_GEMINI_SETTINGS_PATH;
34628
35780
  return;
@@ -34638,71 +35790,95 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
34638
35790
  return globalOverride;
34639
35791
  const dir = getTargetSettingsDir(target);
34640
35792
  if (target === "codex")
34641
- return join10(scope === "project" ? process.cwd() : homedir6(), dir, "hooks.json");
35793
+ return join11(scope === "project" ? process.cwd() : homedir7(), dir, "hooks.json");
34642
35794
  if (scope === "project") {
34643
- return target === "codewith" ? join10(process.cwd(), dir, "config.toml") : join10(process.cwd(), dir, "settings.json");
35795
+ return target === "codewith" ? join11(process.cwd(), dir, "config.toml") : join11(process.cwd(), dir, "settings.json");
34644
35796
  }
34645
- return target === "codewith" ? join10(homedir6(), dir, "config.toml") : join10(homedir6(), dir, "settings.json");
35797
+ return target === "codewith" ? join11(homedir7(), dir, "config.toml") : join11(homedir7(), dir, "settings.json");
34646
35798
  }
34647
35799
  function getHookPath(name) {
34648
35800
  const shortName = shortHookName(name);
34649
35801
  const resolved = resolveHookDir(shortName);
34650
35802
  if (resolved)
34651
35803
  return resolved;
34652
- return join10(HOOKS_DIR, normalizeHookName(shortName));
35804
+ return join11(HOOKS_DIR, normalizeHookName(shortName));
34653
35805
  }
34654
35806
  function hookExists(name) {
34655
- return existsSync11(getHookPath(name));
35807
+ return existsSync12(getHookPath(name));
34656
35808
  }
34657
35809
  var settingsPreimages = new WeakMap;
34658
- function readSettings2(scope = "global", target = "claude") {
35810
+ var unreadableSettings = new WeakMap;
35811
+ function readSettings(scope = "global", target = "claude") {
34659
35812
  const path = getSettingsPath(scope, target);
34660
35813
  if (target === "codex")
34661
35814
  return readCodexSettings(path);
35815
+ let reason = "cannot be read";
34662
35816
  try {
34663
- if (existsSync11(path)) {
34664
- const text3 = readFileSync7(path, "utf-8"), value = JSON.parse(text3);
35817
+ if (existsSync12(path)) {
35818
+ const text3 = readFileSync7(path, "utf-8");
35819
+ reason = "is not valid JSON";
35820
+ const value = JSON.parse(text3);
35821
+ reason = "does not hold a JSON object";
35822
+ if (!value || typeof value !== "object" || Array.isArray(value))
35823
+ throw new TypeError(reason);
34665
35824
  settingsPreimages.set(value, { path, text: text3 });
34666
35825
  return value;
34667
35826
  }
34668
35827
  } catch (error61) {
35828
+ const code2 = error61?.code;
35829
+ if (reason === "cannot be read" && typeof code2 === "string")
35830
+ reason += ` (${code2})`;
34669
35831
  console.warn("[hooks] Cannot read hook settings; repair the selected file before installation.");
34670
35832
  }
34671
35833
  const empty = {};
34672
- if (!existsSync11(path))
35834
+ if (!existsSync12(path))
34673
35835
  settingsPreimages.set(empty, { path, text: null });
35836
+ else
35837
+ unreadableSettings.set(empty, { path, reason });
34674
35838
  return empty;
34675
35839
  }
35840
+ function unreadableSettingsRefusal(settings, path) {
35841
+ const captured = settingsPreimages.get(settings);
35842
+ if (captured?.path === path && captured.text !== null || !existsSync12(path))
35843
+ return;
35844
+ const failed = unreadableSettings.get(settings);
35845
+ const reason = captured?.path === path ? "was created after Hooks read the settings" : failed?.path === path ? failed.reason : "was not read as a settings object";
35846
+ return new ClaudeSettingsRefusal("settings_unreadable", `${path} ${reason}, so Hooks wrote nothing. Repair the file or move it aside, then retry.`);
35847
+ }
34676
35848
  function settingsIdentity(path) {
34677
- if (existsSync11(path))
34678
- return realpathSync5(path);
34679
- return existsSync11(dirname7(path)) ? join10(realpathSync5(dirname7(path)), basename3(path)) : resolve7(path);
35849
+ if (existsSync12(path))
35850
+ return realpathSync8(path);
35851
+ return existsSync12(dirname7(path)) ? join11(realpathSync8(dirname7(path)), basename3(path)) : resolve16(path);
34680
35852
  }
34681
- function writeSettings(settings, scope = "global", target = "claude", expectedSettingsSha256) {
35853
+ function writeSettings(settings, scope = "global", target = "claude", expectedSettingsSha256, beforeWrite) {
34682
35854
  const path = getSettingsPath(scope, target);
34683
35855
  if (target === "codex")
34684
- return writeCodexSettings(path, settings, expectedSettingsSha256);
35856
+ return writeCodexSettings(path, settings, expectedSettingsSha256, beforeWrite);
35857
+ const refusal3 = unreadableSettingsRefusal(settings, path);
35858
+ if (refusal3)
35859
+ throw refusal3;
34685
35860
  const captured = settingsPreimages.get(settings);
34686
35861
  if (expectedSettingsSha256 !== undefined) {
34687
35862
  if (!captured || captured.path !== path)
34688
35863
  throw new Error("Hook settings require a valid captured preimage.");
34689
35864
  assertSettingsPreimage(captured.text, expectedSettingsSha256);
34690
35865
  }
34691
- if (target === "claude" && settingsIdentity(path) === settingsIdentity(join10(homedir6(), ".claude/settings.json"))) {
35866
+ if (target === "claude" && settingsIdentity(path) === settingsIdentity(join11(homedir7(), ".claude/settings.json"))) {
34692
35867
  const before = settingsPreimages.get(settings);
34693
35868
  if (!before || before.path !== path)
34694
35869
  throw new Error("Claude settings require a valid captured preimage before installation.");
34695
- const current2 = existsSync11(path) ? readFileSync7(path, "utf8") : null;
35870
+ const current2 = existsSync12(path) ? readFileSync7(path, "utf8") : null;
34696
35871
  if (current2 !== before.text)
34697
35872
  throw new Error("Claude settings changed during installation; no settings were written.");
34698
35873
  if (coordinateClaudeSettings(before.text, JSON.stringify(settings, null, 2) + `
34699
- `, { home: homedir6() }))
35874
+ `, { home: homedir7() }, beforeWrite))
34700
35875
  return;
34701
35876
  }
34702
35877
  if (expectedSettingsSha256 !== undefined)
34703
- return writeHookSettingsPreimage(path, settings, captured.text);
35878
+ return writeHookSettingsPreimage(path, settings, captured.text, beforeWrite);
35879
+ beforeWrite?.();
34704
35880
  const dir = dirname7(path);
34705
- if (!existsSync11(dir)) {
35881
+ if (!existsSync12(dir)) {
34706
35882
  mkdirSync6(dir, { recursive: true });
34707
35883
  }
34708
35884
  writeFileSync5(path, JSON.stringify(settings, null, 2) + `
@@ -34801,7 +35977,7 @@ function buildCodewithTomlFragment(name, profile, mementos) {
34801
35977
  function readCodewithConfig(scope, configPath) {
34802
35978
  const path = getSettingsPath(scope, "codewith", configPath);
34803
35979
  try {
34804
- return existsSync11(path) ? readFileSync7(path, "utf-8") : "";
35980
+ return existsSync12(path) ? readFileSync7(path, "utf-8") : "";
34805
35981
  } catch {
34806
35982
  return "";
34807
35983
  }
@@ -34809,9 +35985,9 @@ function readCodewithConfig(scope, configPath) {
34809
35985
  function appendCodewithFragment(fragment, scope, configPath) {
34810
35986
  const path = getSettingsPath(scope, "codewith", configPath);
34811
35987
  const dir = dirname7(path);
34812
- if (!existsSync11(dir))
35988
+ if (!existsSync12(dir))
34813
35989
  mkdirSync6(dir, { recursive: true });
34814
- const existing = existsSync11(path) ? readFileSync7(path, "utf-8") : "";
35990
+ const existing = existsSync12(path) ? readFileSync7(path, "utf-8") : "";
34815
35991
  const sep3 = existing.trim() ? `
34816
35992
 
34817
35993
  ` : "";
@@ -34858,7 +36034,7 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
34858
36034
  }
34859
36035
  return;
34860
36036
  }
34861
- function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256) {
36037
+ function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256, beforeWrite) {
34862
36038
  const shortName = shortHookName(name);
34863
36039
  if (isNativeSafetyName(shortName) && (profile || target === "gemini" || target === "codewith" && codewithMode === "write")) {
34864
36040
  return { hook: shortName, success: false, error: "Bundled native safety guards support Claude/Codex and managed Codewith fragments, without registry profiles.", target };
@@ -34925,7 +36101,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
34925
36101
  if (rewriteConflict) {
34926
36102
  return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
34927
36103
  }
34928
- registerHook(shortName, scope, target, profile, mementos, (settings, selectedScope, selectedTarget) => writeSettings(settings, selectedScope, selectedTarget, expectedSettingsSha256));
36104
+ registerHook(shortName, scope, target, profile, mementos, (settings, selectedScope, selectedTarget) => writeSettings(settings, selectedScope, selectedTarget, expectedSettingsSha256, beforeWrite));
34929
36105
  return {
34930
36106
  hook: shortName,
34931
36107
  success: true,
@@ -34939,12 +36115,13 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
34939
36115
  hook: shortName,
34940
36116
  success: false,
34941
36117
  error: error61 instanceof Error ? error61.message : "Unknown error",
34942
- target
36118
+ target,
36119
+ ...error61 instanceof ClaudeSettingsRefusal ? { code: error61.code } : {}
34943
36120
  };
34944
36121
  }
34945
36122
  }
34946
36123
  function installHook(name, options = {}) {
34947
- const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256 } = options;
36124
+ const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256, beforeWrite } = options;
34948
36125
  if (expectedSettingsSha256 !== undefined && (!/^(?:absent|[a-f0-9]{64})$/.test(expectedSettingsSha256) || !isNativeSafetyName(shortHookName(name)) || target !== "codex" && target !== "claude")) {
34949
36126
  return { hook: name, success: false, target, error: "An exact settings predecessor requires a Claude or Codex native safety guard." };
34950
36127
  }
@@ -34988,7 +36165,7 @@ function installHook(name, options = {}) {
34988
36165
  }
34989
36166
  return { ...claudeResult, target: "all" };
34990
36167
  }
34991
- return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos, expectedSettingsSha256);
36168
+ return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos, expectedSettingsSha256, beforeWrite);
34992
36169
  }
34993
36170
  function registerHook(name, scope = "global", target = "claude", profile, mementos, write = writeSettings) {
34994
36171
  const readiness = name === "trash-readiness";
@@ -35019,7 +36196,7 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
35019
36196
  throw new Error(`Hook '${name}' has no installable events for target '${target}'`);
35020
36197
  }
35021
36198
  const matcher = matcherFor(meta3, target);
35022
- const settings = readSettings2(scope, target);
36199
+ const settings = readSettings(scope, target);
35023
36200
  if (!settings.hooks)
35024
36201
  settings.hooks = {};
35025
36202
  if (isNativeSafetyName(name) || readiness) {
@@ -35096,7 +36273,7 @@ function removeHookFromAllEvents(settings, name) {
35096
36273
  }
35097
36274
  }
35098
36275
  function unregisterHook(name, scope = "global", target = "claude") {
35099
- const settings = readSettings2(scope, target);
36276
+ const settings = readSettings(scope, target);
35100
36277
  if (!settings.hooks)
35101
36278
  return;
35102
36279
  removeHookFromAllEvents(settings, name);
@@ -35126,7 +36303,7 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
35126
36303
  }
35127
36304
  return [...new Set(registered2)];
35128
36305
  }
35129
- const settings = readSettings2(scope, target);
36306
+ const settings = readSettings(scope, target);
35130
36307
  if (!settings.hooks)
35131
36308
  return [];
35132
36309
  const registered = [];
@@ -35168,12 +36345,12 @@ function removeHookForTarget(name, scope, target) {
35168
36345
 
35169
36346
  // src/lib/profiles.ts
35170
36347
  init_app_home();
35171
- import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
35172
- import { join as join11 } from "path";
36348
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8, readdirSync as readdirSync9, rmSync, cpSync as cpSync2 } from "fs";
36349
+ import { join as join12 } from "path";
35173
36350
  function getProfilesDir() {
35174
- const newDir = join11(getEffectiveDataRoot(), "profiles");
35175
- const oldDir = join11(getHomeDir(), ".hooks", "profiles");
35176
- if (!existsSync12(newDir) && existsSync12(oldDir)) {
36351
+ const newDir = join12(getEffectiveDataRoot(), "profiles");
36352
+ const oldDir = join12(getHomeDir(), ".hooks", "profiles");
36353
+ if (!existsSync13(newDir) && existsSync13(oldDir)) {
35177
36354
  mkdirSync9(newDir, { recursive: true });
35178
36355
  cpSync2(oldDir, newDir, { recursive: true });
35179
36356
  }
@@ -35181,12 +36358,12 @@ function getProfilesDir() {
35181
36358
  }
35182
36359
  function ensureProfilesDir() {
35183
36360
  const dir = getProfilesDir();
35184
- if (!existsSync12(dir)) {
36361
+ if (!existsSync13(dir)) {
35185
36362
  mkdirSync9(dir, { recursive: true });
35186
36363
  }
35187
36364
  }
35188
36365
  function profilePath(id) {
35189
- return join11(getProfilesDir(), `${id}.json`);
36366
+ return join12(getProfilesDir(), `${id}.json`);
35190
36367
  }
35191
36368
  function shortUuid() {
35192
36369
  return crypto.randomUUID().slice(0, 8);
@@ -35212,7 +36389,7 @@ function createProfile(input2) {
35212
36389
  function getProfile(id) {
35213
36390
  const path = profilePath(id);
35214
36391
  try {
35215
- if (!existsSync12(path))
36392
+ if (!existsSync13(path))
35216
36393
  return null;
35217
36394
  return JSON.parse(readFileSync8(path, "utf-8"));
35218
36395
  } catch {
@@ -35221,14 +36398,14 @@ function getProfile(id) {
35221
36398
  }
35222
36399
  function listProfiles() {
35223
36400
  const dir = getProfilesDir();
35224
- if (!existsSync12(dir))
36401
+ if (!existsSync13(dir))
35225
36402
  return [];
35226
36403
  try {
35227
- const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
36404
+ const files = readdirSync9(dir).filter((f) => f.endsWith(".json"));
35228
36405
  const profiles = [];
35229
36406
  for (const file2 of files) {
35230
36407
  try {
35231
- const content = readFileSync8(join11(dir, file2), "utf-8");
36408
+ const content = readFileSync8(join12(dir, file2), "utf-8");
35232
36409
  profiles.push(JSON.parse(content));
35233
36410
  } catch {}
35234
36411
  }
@@ -35763,12 +36940,12 @@ function coerceForSqlite(value) {
35763
36940
  return String(value);
35764
36941
  }
35765
36942
  // src/mcp/server.ts
35766
- var __dirname3 = dirname19(fileURLToPath3(import.meta.url));
36943
+ var __dirname3 = dirname20(fileURLToPath3(import.meta.url));
35767
36944
  var pkg = { name: "@hasna/hooks", version: "0.0.0" };
35768
36945
  try {
35769
36946
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
35770
- const p = join13(__dirname3, rel);
35771
- if (existsSync14(p)) {
36947
+ const p = join18(__dirname3, rel);
36948
+ if (existsSync15(p)) {
35772
36949
  pkg = JSON.parse(readFileSync9(p, "utf-8"));
35773
36950
  break;
35774
36951
  }
@@ -35778,12 +36955,12 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
35778
36955
  const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
35779
36956
  const resolved = resolveHook2(name);
35780
36957
  const hookScript = resolveScriptPath2(name);
35781
- if (!resolved || !hookScript || !existsSync14(hookScript)) {
36958
+ if (!resolved || !hookScript || !existsSync15(hookScript)) {
35782
36959
  throw new Error(`Hook '${name}' not found`);
35783
36960
  }
35784
36961
  const content = readFileSync9(hookScript);
35785
- const sha4 = sha256Of(content);
35786
- const check2 = checkScriptHash(name, sha4);
36962
+ const sha6 = sha256Of(content);
36963
+ const check2 = checkScriptHash(name, sha6);
35787
36964
  if (!check2.ok) {
35788
36965
  throw new Error(`Hook '${name}' script changed since it was trusted (sha256 ${check2.expected} != ${check2.actual}). Run 'hooks trust ${name}' to trust the new content.`);
35789
36966
  }
@@ -35803,7 +36980,7 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
35803
36980
  hookName: name,
35804
36981
  eventType: resolveEventType2(inputJson.hook_event_name, resolved.events[0] ?? "PostToolUse"),
35805
36982
  version: resolved.version,
35806
- sha256: sha4,
36983
+ sha256: sha6,
35807
36984
  sessionId: typeof inputJson.session_id === "string" ? inputJson.session_id : null,
35808
36985
  toolName: typeof inputJson.tool_name === "string" ? inputJson.tool_name : null,
35809
36986
  toolInput: inputJson.tool_input,
@@ -35833,7 +37010,7 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
35833
37010
  hookName: name,
35834
37011
  eventType: resolveEventType2(inputJson.hook_event_name, resolved.events[0] ?? "PostToolUse"),
35835
37012
  version: resolved.version,
35836
- sha256: sha4,
37013
+ sha256: sha6,
35837
37014
  sessionId: typeof inputJson.session_id === "string" ? inputJson.session_id : null,
35838
37015
  toolName: typeof inputJson.tool_name === "string" ? inputJson.tool_name : null,
35839
37016
  toolInput: inputJson.tool_input,
@@ -35856,6 +37033,12 @@ function formatInstallResults(results2, extra) {
35856
37033
  }]
35857
37034
  };
35858
37035
  }
37036
+ function settingsChangeError(hook, scope, error61, code2) {
37037
+ return {
37038
+ content: [{ type: "text", text: JSON.stringify({ hook, scope, error: error61, ...code2 ? { code: code2 } : {} }) }],
37039
+ isError: true
37040
+ };
37041
+ }
35859
37042
  function truncateText(value, max = 160) {
35860
37043
  const text3 = String(value ?? "").replace(/\s+/g, " ").trim();
35861
37044
  if (text3.length <= max)
@@ -35987,7 +37170,7 @@ function createHooksServer() {
35987
37170
  const settingsPath = getSettingsPath(scope);
35988
37171
  const issues = [];
35989
37172
  const healthy = [];
35990
- const settingsExist = existsSync14(settingsPath);
37173
+ const settingsExist = existsSync15(settingsPath);
35991
37174
  if (!settingsExist) {
35992
37175
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
35993
37176
  }
@@ -35997,7 +37180,7 @@ function createHooksServer() {
35997
37180
  const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
35998
37181
  let hookHealthy = true;
35999
37182
  if (custom2) {
36000
- if (!existsSync14(custom2.scriptPath)) {
37183
+ if (!existsSync15(custom2.scriptPath)) {
36001
37184
  issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
36002
37185
  hookHealthy = false;
36003
37186
  }
@@ -36007,7 +37190,7 @@ function createHooksServer() {
36007
37190
  continue;
36008
37191
  }
36009
37192
  const hookDir = getHookPath(name);
36010
- if (!existsSync14(join13(hookDir, "src", "hook.ts"))) {
37193
+ if (!existsSync15(join18(hookDir, "src", "hook.ts"))) {
36011
37194
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
36012
37195
  hookHealthy = false;
36013
37196
  }
@@ -36051,9 +37234,9 @@ function createHooksServer() {
36051
37234
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
36052
37235
  }
36053
37236
  const hookPath = getHookPath(name);
36054
- const readmePath = join13(hookPath, "README.md");
37237
+ const readmePath = join18(hookPath, "README.md");
36055
37238
  let readme = "";
36056
- if (existsSync14(readmePath)) {
37239
+ if (existsSync15(readmePath)) {
36057
37240
  readme = readFileSync9(readmePath, "utf-8");
36058
37241
  }
36059
37242
  if (verbose) {
@@ -36237,7 +37420,7 @@ function createHooksServer() {
36237
37420
  const ctx = {
36238
37421
  scope,
36239
37422
  settings_path: settingsPath,
36240
- settings_exists: existsSync14(settingsPath),
37423
+ settings_exists: existsSync15(settingsPath),
36241
37424
  registered_hooks: hooks,
36242
37425
  hook_count: hooks.length,
36243
37426
  healthy,
@@ -36376,48 +37559,31 @@ function createHooksServer() {
36376
37559
  }));
36377
37560
  return { content: [{ type: "text", text: JSON.stringify({ results: results2, count: results2.length }) }] };
36378
37561
  });
36379
- defineTool("hooks_disable", "Temporarily disable a registered hook without removing it. Stores disabled list in settings under hooks.__disabled.", {
37562
+ defineTool("hooks_disable", "Disable a registered hook: remove its Claude registration through the same coordinated settings writer as hooks_remove. The hook stays installed (trust pin, custom manifest), so hooks_enable can register it again with default options, unless it is outside the installable registry (trash-readiness, stale registrations). For trash-guard and workspace-repos-guard this removes their protection until re-enabled. Refusals are errors with a code, for example skills_policy_unresolved; nothing is written then.", {
36380
37563
  name: z3.string().describe("Hook name to disable"),
36381
37564
  scope: z3.enum(["global", "project"]).default("global").describe("Scope")
36382
37565
  }, async ({ name, scope }) => {
36383
- const settingsPath = getSettingsPath(scope);
36384
- let settings = {};
37566
+ let changed;
36385
37567
  try {
36386
- if (existsSync14(settingsPath))
36387
- settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
36388
- } catch {}
36389
- if (!settings.hooks)
36390
- settings.hooks = {};
36391
- const disabled = settings.hooks.__disabled ?? [];
36392
- if (!disabled.includes(name))
36393
- disabled.push(name);
36394
- settings.hooks.__disabled = disabled;
36395
- const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync7 } = await import("fs");
36396
- const { dirname: dirname8 } = await import("path");
36397
- mkdirSync7(dirname8(settingsPath), { recursive: true });
36398
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
36399
- `);
36400
- return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
37568
+ changed = removeHook(name, scope, "claude");
37569
+ } catch (error61) {
37570
+ return settingsChangeError(name, scope, error61 instanceof Error ? error61.message : String(error61), error61 instanceof ClaudeSettingsRefusal ? error61.code : undefined);
37571
+ }
37572
+ return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, changed, scope }) }] };
36401
37573
  });
36402
- defineTool("hooks_enable", "Re-enable a previously disabled hook.", {
37574
+ defineTool("hooks_enable", "Enable a hook: register it in Claude settings, as hooks_install does, unless it is already registered. Writes go through the coordinated settings writer; refusals are errors with a code, for example skills_policy_unresolved or settings_unreadable, and nothing is written then.", {
36403
37575
  name: z3.string().describe("Hook name to enable"),
36404
- scope: z3.enum(["global", "project"]).default("global").describe("Scope")
36405
- }, async ({ name, scope }) => {
36406
- const settingsPath = getSettingsPath(scope);
36407
- let settings = {};
36408
- try {
36409
- if (existsSync14(settingsPath))
36410
- settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
36411
- } catch {}
36412
- if (settings.hooks?.__disabled) {
36413
- settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
36414
- if (settings.hooks.__disabled.length === 0)
36415
- delete settings.hooks.__disabled;
36416
- const { writeFileSync: writeFileSync6 } = await import("fs");
36417
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
36418
- `);
36419
- }
36420
- return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
37576
+ scope: z3.enum(["global", "project"]).default("global").describe("Scope"),
37577
+ profile: z3.string().optional().describe("Agent profile ID to scope the registration to, as in hooks_install")
37578
+ }, async ({ name, scope, profile }) => {
37579
+ const shortName = name.startsWith("hook-") ? name.slice("hook-".length) : name;
37580
+ if (getRegisteredHooks(scope).includes(shortName)) {
37581
+ return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, changed: false, scope }) }] };
37582
+ }
37583
+ const result = installHook(name, { scope, profile });
37584
+ if (!result.success)
37585
+ return settingsChangeError(name, scope, result.error ?? "Hook registration failed", result.code);
37586
+ return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, changed: true, scope }) }] };
36421
37587
  });
36422
37588
  defineTool("hooks_init", "Register a new agent profile \u2014 returns a unique agent_id for use with hook installation and execution", {
36423
37589
  agent_type: z3.enum(["claude", "gemini", "custom"]).default("claude").describe("Type of AI agent"),