@hasna/skills 0.10.34 → 0.10.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +543 -333
- package/bin/mcp.js +124 -123
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +398 -214
- package/dist/lib/agent-integration.d.ts +27 -0
- package/dist/sdk/index.js +189 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9655,8 +9655,8 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
9655
9655
|
}
|
|
9656
9656
|
|
|
9657
9657
|
// src/lib/plugin-discovery.ts
|
|
9658
|
-
import { closeSync as
|
|
9659
|
-
import { basename as basename7, dirname as
|
|
9658
|
+
import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync20, openSync as openSync11, opendirSync as opendirSync5, readSync as readSync9 } from "fs";
|
|
9659
|
+
import { basename as basename7, dirname as dirname15, isAbsolute as isAbsolute14, join as join27, resolve as resolve14 } from "path";
|
|
9660
9660
|
|
|
9661
9661
|
// src/lib/plugin-admission.ts
|
|
9662
9662
|
import { homedir as homedir5 } from "os";
|
|
@@ -10975,7 +10975,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
10975
10975
|
// package.json
|
|
10976
10976
|
var package_default = {
|
|
10977
10977
|
name: "@hasna/skills",
|
|
10978
|
-
version: "0.10.
|
|
10978
|
+
version: "0.10.35",
|
|
10979
10979
|
description: "Skills library for AI coding agents",
|
|
10980
10980
|
type: "module",
|
|
10981
10981
|
bin: {
|
|
@@ -12797,7 +12797,7 @@ import { homedir as homedir4 } from "os";
|
|
|
12797
12797
|
import {
|
|
12798
12798
|
cpSync as cpSync2,
|
|
12799
12799
|
existsSync as existsSync10,
|
|
12800
|
-
lstatSync as
|
|
12800
|
+
lstatSync as lstatSync18,
|
|
12801
12801
|
mkdirSync as mkdirSync6,
|
|
12802
12802
|
mkdtempSync,
|
|
12803
12803
|
readFileSync as readFileSync13,
|
|
@@ -12805,10 +12805,10 @@ import {
|
|
|
12805
12805
|
renameSync as renameSync3,
|
|
12806
12806
|
rmSync,
|
|
12807
12807
|
statSync as statSync9,
|
|
12808
|
-
writeFileSync as
|
|
12808
|
+
writeFileSync as writeFileSync6
|
|
12809
12809
|
} from "fs";
|
|
12810
12810
|
import { homedir as homedir3 } from "os";
|
|
12811
|
-
import { basename as basename6, dirname as
|
|
12811
|
+
import { basename as basename6, dirname as dirname13, join as join19 } from "path";
|
|
12812
12812
|
|
|
12813
12813
|
// src/lib/codex-local-mcp-controls.ts
|
|
12814
12814
|
function isCodexLocalStdioMcpServer(name, server) {
|
|
@@ -14208,10 +14208,10 @@ function upgradeCodexSettingsWitnessV3(previous, reviewedSettingsPath) {
|
|
|
14208
14208
|
}
|
|
14209
14209
|
|
|
14210
14210
|
// src/lib/agent-integration.ts
|
|
14211
|
-
import { createHash as
|
|
14211
|
+
import { createHash as createHash13, randomUUID as randomUUID2 } from "crypto";
|
|
14212
14212
|
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
14213
|
-
import { existsSync as existsSync9, lstatSync as
|
|
14214
|
-
import { dirname as
|
|
14213
|
+
import { existsSync as existsSync9, lstatSync as lstatSync17, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync7, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, chmodSync, openSync as openSync9, closeSync as closeSync9, fsyncSync as fsyncSync3, fstatSync as fstatSync9, readSync as readSync7, constants as constants8, linkSync as linkSync2 } from "fs";
|
|
14214
|
+
import { dirname as dirname12, join as join18, resolve as resolve11, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
|
|
14215
14215
|
import { homedir as homedir2 } from "os";
|
|
14216
14216
|
|
|
14217
14217
|
// src/lib/managed-policy.ts
|
|
@@ -14713,15 +14713,66 @@ var SUMI_SKILL_PERMISSIONS = [
|
|
|
14713
14713
|
{ action: "skill", resource: "skills-cli", effect: "allow" }
|
|
14714
14714
|
];
|
|
14715
14715
|
|
|
14716
|
+
// src/lib/codex-hook-trust-files.ts
|
|
14717
|
+
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync8, fsyncSync as fsyncSync2, lstatSync as lstatSync16, openSync as openSync8, readSync as readSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
14718
|
+
import { createHash as createHash12 } from "crypto";
|
|
14719
|
+
import { dirname as dirname11 } from "path";
|
|
14720
|
+
function need5(value, code) {
|
|
14721
|
+
if (!value)
|
|
14722
|
+
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
14723
|
+
}
|
|
14724
|
+
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
14725
|
+
function safeParents(file) {
|
|
14726
|
+
for (let p = dirname11(file);; p = dirname11(p)) {
|
|
14727
|
+
const s = lstatSync16(p);
|
|
14728
|
+
need5(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
14729
|
+
need5((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
14730
|
+
if (dirname11(p) === p)
|
|
14731
|
+
break;
|
|
14732
|
+
}
|
|
14733
|
+
}
|
|
14734
|
+
function snapshot(file, privateFile = false, options = {}) {
|
|
14735
|
+
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
14736
|
+
safeParents(file);
|
|
14737
|
+
const initial = lstatSync16(file, { bigint: true });
|
|
14738
|
+
need5(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");
|
|
14739
|
+
need5(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
14740
|
+
const fd = openSync8(file, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
|
|
14741
|
+
try {
|
|
14742
|
+
const before = fstatSync8(fd, { bigint: true });
|
|
14743
|
+
need5(sameStat(initial, before), "INPUT_CHANGED");
|
|
14744
|
+
const bytes = Buffer.alloc(Number(before.size) + 1);
|
|
14745
|
+
let length = 0;
|
|
14746
|
+
while (length < bytes.length) {
|
|
14747
|
+
const n2 = readSync6(fd, bytes, length, bytes.length - length, null);
|
|
14748
|
+
if (!n2)
|
|
14749
|
+
break;
|
|
14750
|
+
length += n2;
|
|
14751
|
+
}
|
|
14752
|
+
need5(length === Number(before.size) && sameStat(before, fstatSync8(fd, { bigint: true })) && sameStat(before, lstatSync16(file, { bigint: true })), "INPUT_CHANGED");
|
|
14753
|
+
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
14754
|
+
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash12("sha256").update(content).digest("hex") };
|
|
14755
|
+
} finally {
|
|
14756
|
+
closeSync8(fd);
|
|
14757
|
+
}
|
|
14758
|
+
}
|
|
14759
|
+
function unchanged2(before) {
|
|
14760
|
+
const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
14761
|
+
need5(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
|
|
14762
|
+
}
|
|
14763
|
+
|
|
14716
14764
|
// src/lib/agent-integration.ts
|
|
14717
|
-
var sha3 = (data) =>
|
|
14765
|
+
var sha3 = (data) => createHash13("sha256").update(data).digest("hex");
|
|
14718
14766
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
14719
|
-
function
|
|
14767
|
+
function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
|
|
14720
14768
|
try {
|
|
14721
|
-
const { dlopen, read: read2 } = __require("bun:ffi");
|
|
14769
|
+
const { dlopen, read: read2, ptr } = __require("bun:ffi");
|
|
14722
14770
|
const library = dlopen("/usr/lib/libSystem.B.dylib", {
|
|
14723
14771
|
acl_get_fd: { args: ["i32"], returns: "ptr" },
|
|
14724
14772
|
acl_free: { args: ["ptr"], returns: "i32" },
|
|
14773
|
+
acl_valid: { args: ["ptr"], returns: "i32" },
|
|
14774
|
+
acl_get_entry: { args: ["ptr", "i32", "ptr"], returns: "i32" },
|
|
14775
|
+
acl_get_tag_type: { args: ["ptr", "ptr"], returns: "i32" },
|
|
14725
14776
|
__error: { args: [], returns: "ptr" }
|
|
14726
14777
|
});
|
|
14727
14778
|
try {
|
|
@@ -14730,8 +14781,23 @@ function hasNoDarwinAcl(fd) {
|
|
|
14730
14781
|
return false;
|
|
14731
14782
|
const acl = library.symbols.acl_get_fd(fd);
|
|
14732
14783
|
if (acl) {
|
|
14733
|
-
|
|
14734
|
-
|
|
14784
|
+
try {
|
|
14785
|
+
if (!allowDenyOnly || library.symbols.acl_valid(acl) !== 0)
|
|
14786
|
+
return false;
|
|
14787
|
+
const entry = new BigUint64Array(1), tag = new Int32Array(1);
|
|
14788
|
+
for (let count = 0;count <= 128; count++) {
|
|
14789
|
+
const status = library.symbols.acl_get_entry(acl, count === 0 ? 0 : -1, ptr(entry));
|
|
14790
|
+
if (status !== 0) {
|
|
14791
|
+
return status === -1 && read2.i32(errno) === 22 && library.symbols.acl_valid(acl) === 0;
|
|
14792
|
+
}
|
|
14793
|
+
const value = read2.ptr(ptr(entry));
|
|
14794
|
+
if (count === 128 || !value || library.symbols.acl_get_tag_type(value, ptr(tag)) !== 0 || tag[0] !== 2)
|
|
14795
|
+
return false;
|
|
14796
|
+
}
|
|
14797
|
+
return false;
|
|
14798
|
+
} finally {
|
|
14799
|
+
library.symbols.acl_free(acl);
|
|
14800
|
+
}
|
|
14735
14801
|
}
|
|
14736
14802
|
return (acl === null || acl === 0) && read2.i32(errno) === 2;
|
|
14737
14803
|
} finally {
|
|
@@ -14747,11 +14813,11 @@ function isSystemRootAlias(path) {
|
|
|
14747
14813
|
const target = `/private${path}`, link = `private${path}`;
|
|
14748
14814
|
const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
|
|
14749
14815
|
try {
|
|
14750
|
-
const before =
|
|
14816
|
+
const before = lstatSync17(path, { bigint: true });
|
|
14751
14817
|
if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
|
|
14752
14818
|
return false;
|
|
14753
14819
|
const directories = ["/", "/private", target].map((directory) => {
|
|
14754
|
-
const stat =
|
|
14820
|
+
const stat = lstatSync17(directory, { bigint: true });
|
|
14755
14821
|
if (!stat.isDirectory() || stat.uid !== 0n)
|
|
14756
14822
|
throw new Error("Untrusted system alias directory");
|
|
14757
14823
|
if (directory !== "/private/tmp") {
|
|
@@ -14759,23 +14825,23 @@ function isSystemRootAlias(path) {
|
|
|
14759
14825
|
throw new Error("Writable system alias directory");
|
|
14760
14826
|
} else if ((stat.mode & 0o1000n) === 0n)
|
|
14761
14827
|
throw new Error("Unprotected system temporary directory");
|
|
14762
|
-
const fd =
|
|
14828
|
+
const fd = openSync9(directory, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW);
|
|
14763
14829
|
try {
|
|
14764
|
-
if (identity4(
|
|
14830
|
+
if (identity4(fstatSync9(fd, { bigint: true })) !== identity4(stat) || !hasTrustedDarwinAcl(fd) || identity4(fstatSync9(fd, { bigint: true })) !== identity4(stat))
|
|
14765
14831
|
throw new Error("Unverified system alias directory ACL");
|
|
14766
14832
|
} finally {
|
|
14767
|
-
|
|
14833
|
+
closeSync9(fd);
|
|
14768
14834
|
}
|
|
14769
14835
|
return { directory, identity: identity4(stat) };
|
|
14770
14836
|
});
|
|
14771
|
-
return realpathSync7(path) === target && readlinkSync4(path) === link && identity4(
|
|
14837
|
+
return realpathSync7(path) === target && readlinkSync4(path) === link && identity4(lstatSync17(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync17(item.directory, { bigint: true })) === item.identity);
|
|
14772
14838
|
} catch {
|
|
14773
14839
|
return false;
|
|
14774
14840
|
}
|
|
14775
14841
|
}
|
|
14776
14842
|
function canonicalSystemPath(path) {
|
|
14777
14843
|
const absolute = resolve11(path), root = `/${absolute.split(sep5)[1]}`;
|
|
14778
|
-
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !
|
|
14844
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync17(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14779
14845
|
return absolute;
|
|
14780
14846
|
if (!isSystemRootAlias(root))
|
|
14781
14847
|
throw new Error(`Refusing symlink path: ${root}`);
|
|
@@ -14784,9 +14850,9 @@ function canonicalSystemPath(path) {
|
|
|
14784
14850
|
function assertSafePath(path) {
|
|
14785
14851
|
let cursor = resolve11(path);
|
|
14786
14852
|
while (true) {
|
|
14787
|
-
if (
|
|
14853
|
+
if (lstatSync17(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
14788
14854
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
14789
|
-
const parent =
|
|
14855
|
+
const parent = dirname12(cursor);
|
|
14790
14856
|
if (parent === cursor)
|
|
14791
14857
|
return;
|
|
14792
14858
|
cursor = parent;
|
|
@@ -14795,12 +14861,12 @@ function assertSafePath(path) {
|
|
|
14795
14861
|
function readRootAlias(home, agent) {
|
|
14796
14862
|
assertSafePath(home);
|
|
14797
14863
|
const realHome = realpathSync7(home), alias = join18(realHome, `.${agent}`);
|
|
14798
|
-
const stat =
|
|
14864
|
+
const stat = lstatSync17(alias, { throwIfNoEntry: false, bigint: true });
|
|
14799
14865
|
if (!stat?.isSymbolicLink())
|
|
14800
14866
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
14801
|
-
const link = readlinkSync4(alias), targetPath = resolve11(
|
|
14867
|
+
const link = readlinkSync4(alias), targetPath = resolve11(dirname12(alias), link);
|
|
14802
14868
|
assertSafePath(targetPath);
|
|
14803
|
-
const targetStat =
|
|
14869
|
+
const targetStat = lstatSync17(targetPath, { throwIfNoEntry: false });
|
|
14804
14870
|
if (!targetStat?.isDirectory())
|
|
14805
14871
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
14806
14872
|
const target = realpathSync7(targetPath), inside = relative5(realHome, target);
|
|
@@ -14813,7 +14879,7 @@ function rootAliases(home, allowed = false) {
|
|
|
14813
14879
|
return [];
|
|
14814
14880
|
const result = [];
|
|
14815
14881
|
for (const agent of ["claude", "codex"])
|
|
14816
|
-
if (
|
|
14882
|
+
if (lstatSync17(join18(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14817
14883
|
result.push(readRootAlias(home, agent));
|
|
14818
14884
|
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))))
|
|
14819
14885
|
throw new Error("Agent root alias targets must not overlap");
|
|
@@ -14848,7 +14914,7 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
14848
14914
|
continue;
|
|
14849
14915
|
settings.set(document2, [...settings.get(document2) ?? [], entry.enabled === false]);
|
|
14850
14916
|
}
|
|
14851
|
-
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) =>
|
|
14917
|
+
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) => dirname12(document2));
|
|
14852
14918
|
}
|
|
14853
14919
|
function recheckRootAliases(aliases) {
|
|
14854
14920
|
for (const binding of aliases) {
|
|
@@ -14861,35 +14927,35 @@ function recheckRootAliases(aliases) {
|
|
|
14861
14927
|
}
|
|
14862
14928
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
14863
14929
|
assertSafePath(path);
|
|
14864
|
-
const before =
|
|
14930
|
+
const before = lstatSync17(path);
|
|
14865
14931
|
if (!before.isFile() || before.size > maximum)
|
|
14866
14932
|
throw new Error("Unsupported or oversized native skill file");
|
|
14867
|
-
const descriptor =
|
|
14933
|
+
const descriptor = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
14868
14934
|
try {
|
|
14869
|
-
const opened =
|
|
14935
|
+
const opened = fstatSync9(descriptor);
|
|
14870
14936
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size)
|
|
14871
14937
|
throw new Error("Native skill file changed while reading");
|
|
14872
14938
|
const bytes = Buffer.allocUnsafe(before.size + 1);
|
|
14873
14939
|
let length = 0;
|
|
14874
14940
|
while (length < bytes.length) {
|
|
14875
|
-
const count =
|
|
14941
|
+
const count = readSync7(descriptor, bytes, length, bytes.length - length, null);
|
|
14876
14942
|
if (!count)
|
|
14877
14943
|
break;
|
|
14878
14944
|
length += count;
|
|
14879
14945
|
}
|
|
14880
|
-
const after =
|
|
14946
|
+
const after = fstatSync9(descriptor), current = lstatSync17(path);
|
|
14881
14947
|
if (current.dev !== opened.dev || current.ino !== opened.ino || current.size !== after.size || current.mtimeMs !== after.mtimeMs || current.ctimeMs !== after.ctimeMs || length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
14882
14948
|
throw new Error("Native skill file changed while reading");
|
|
14883
14949
|
return bytes.subarray(0, length);
|
|
14884
14950
|
} finally {
|
|
14885
|
-
|
|
14951
|
+
closeSync9(descriptor);
|
|
14886
14952
|
}
|
|
14887
14953
|
}
|
|
14888
14954
|
function treeHash(root) {
|
|
14889
|
-
const hash2 =
|
|
14955
|
+
const hash2 = createHash13("sha256");
|
|
14890
14956
|
let bytes = 0, files = 0;
|
|
14891
14957
|
function visit2(path) {
|
|
14892
|
-
const stat =
|
|
14958
|
+
const stat = lstatSync17(path);
|
|
14893
14959
|
if (stat.isSymbolicLink())
|
|
14894
14960
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
14895
14961
|
const rel = relative5(root, path);
|
|
@@ -14921,7 +14987,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
14921
14987
|
if (!isAbsolute11(target.projectRoot) || resolve11(target.projectRoot) !== target.projectRoot)
|
|
14922
14988
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14923
14989
|
const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve11(projectRoot, target.path));
|
|
14924
|
-
if (!existsSync9(projectRoot) || !
|
|
14990
|
+
if (!existsSync9(projectRoot) || !lstatSync17(projectRoot).isDirectory())
|
|
14925
14991
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14926
14992
|
assertSafePath(projectRoot);
|
|
14927
14993
|
if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
@@ -14950,7 +15016,7 @@ function projectAncestorDirectories(projects) {
|
|
|
14950
15016
|
if (depth >= 100)
|
|
14951
15017
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
14952
15018
|
directories.add(path);
|
|
14953
|
-
const parent =
|
|
15019
|
+
const parent = dirname12(path);
|
|
14954
15020
|
if (parent === path)
|
|
14955
15021
|
break;
|
|
14956
15022
|
path = parent;
|
|
@@ -14996,12 +15062,12 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14996
15062
|
let reviewedCacheAliasFound = false;
|
|
14997
15063
|
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
14998
15064
|
const link = readlinkSync4(path), lexicalTarget = resolve11(parent, link);
|
|
14999
|
-
if (isAbsolute11(link) ? link !== lexicalTarget :
|
|
15065
|
+
if (isAbsolute11(link) ? link !== lexicalTarget : dirname12(link) !== ".")
|
|
15000
15066
|
return false;
|
|
15001
15067
|
const target = canonicalSystemPath(lexicalTarget);
|
|
15002
|
-
if (
|
|
15068
|
+
if (dirname12(target) !== parent)
|
|
15003
15069
|
return false;
|
|
15004
|
-
if (!
|
|
15070
|
+
if (!lstatSync17(target, { throwIfNoEntry: false })?.isDirectory())
|
|
15005
15071
|
return false;
|
|
15006
15072
|
const scan = cacheScans.get(target);
|
|
15007
15073
|
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
@@ -15026,7 +15092,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
15026
15092
|
return scan;
|
|
15027
15093
|
if (!existsSync9(path))
|
|
15028
15094
|
return scan;
|
|
15029
|
-
const stat =
|
|
15095
|
+
const stat = lstatSync17(path);
|
|
15030
15096
|
if (stat.isFile()) {
|
|
15031
15097
|
if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
|
|
15032
15098
|
throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
|
|
@@ -15075,7 +15141,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
15075
15141
|
if (isVendor && !options.includeVendor)
|
|
15076
15142
|
continue;
|
|
15077
15143
|
const child = join18(path, name);
|
|
15078
|
-
if (isVendor &&
|
|
15144
|
+
if (isVendor && lstatSync17(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
15079
15145
|
const target = statSync8(child, { throwIfNoEntry: false });
|
|
15080
15146
|
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
|
|
15081
15147
|
scan.entries++;
|
|
@@ -15409,6 +15475,171 @@ function planAgentIntegration(options) {
|
|
|
15409
15475
|
recheckRootAliases(aliases);
|
|
15410
15476
|
return { dataDir: dataDir2, profileId, changes, nativeSkills, ...observedNativeSources.length ? { observedNativeSources } : {}, ...nextPolicy.bridge.codexPluginSkillReview ? { codexPluginSkillReview: nextPolicy.bridge.codexPluginSkillReview } : {}, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
|
|
15411
15477
|
}
|
|
15478
|
+
function hookProjectionRefusal(reason) {
|
|
15479
|
+
throw new Error(`CLAUDE_MANAGED_HOOK_${reason}`);
|
|
15480
|
+
}
|
|
15481
|
+
function projectedHookAcl(path, observed) {
|
|
15482
|
+
if (process.platform !== "darwin")
|
|
15483
|
+
return;
|
|
15484
|
+
const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}:${stat.nlink}:${stat.size}:${stat.mtimeNs}`;
|
|
15485
|
+
const fd = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK | (observed.isDirectory() ? constants8.O_DIRECTORY : 0));
|
|
15486
|
+
try {
|
|
15487
|
+
if (identity4(fstatSync9(fd, { bigint: true })) !== identity4(observed) || !hasTrustedDarwinAcl(fd, true) || identity4(fstatSync9(fd, { bigint: true })) !== identity4(observed) || identity4(lstatSync17(path, { bigint: true })) !== identity4(observed))
|
|
15488
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15489
|
+
} finally {
|
|
15490
|
+
closeSync9(fd);
|
|
15491
|
+
}
|
|
15492
|
+
}
|
|
15493
|
+
function projectedHookExecutable(command, expectedResolved) {
|
|
15494
|
+
try {
|
|
15495
|
+
if (!isAbsolute11(command) || /[\0\r\n]/.test(command) || resolve11(command) !== command)
|
|
15496
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15497
|
+
command = canonicalSystemPath(command);
|
|
15498
|
+
const parents2 = (file = command) => {
|
|
15499
|
+
const result = [];
|
|
15500
|
+
for (let path = dirname12(file);; path = dirname12(path)) {
|
|
15501
|
+
const stat = lstatSync17(path, { bigint: true });
|
|
15502
|
+
if (!stat.isDirectory() || (stat.mode & 0o022n) !== 0n && !(stat.uid === 0n && (stat.mode & 0o1000n) !== 0n))
|
|
15503
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15504
|
+
projectedHookAcl(path, stat);
|
|
15505
|
+
result.push(`${path}:${stat.dev}:${stat.ino}:${stat.uid}:${stat.mode}`);
|
|
15506
|
+
if (dirname12(path) === path)
|
|
15507
|
+
break;
|
|
15508
|
+
}
|
|
15509
|
+
return result;
|
|
15510
|
+
};
|
|
15511
|
+
const parentIdentity = parents2(), link = lstatSync17(command, { bigint: true });
|
|
15512
|
+
if (link.uid !== BigInt(process.getuid()) || !link.isFile() && !link.isSymbolicLink())
|
|
15513
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15514
|
+
const linkIdentity = (stat) => `${stat.dev}:${stat.ino}:${stat.mode}:${stat.uid}:${stat.ctimeNs}`;
|
|
15515
|
+
const resolved = realpathSync7(command);
|
|
15516
|
+
if (expectedResolved !== undefined && resolved !== expectedResolved)
|
|
15517
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15518
|
+
const resolvedParentIdentity = parents2(resolved);
|
|
15519
|
+
const manifest = snapshot(join18(dirname12(dirname12(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
15520
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
15521
|
+
const pkg = JSON.parse(manifest.text);
|
|
15522
|
+
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join18(dirname12(manifest.file), "bin/index.js"))
|
|
15523
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15524
|
+
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
15525
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
15526
|
+
if ((cli.stat.mode & 0o100n) === 0n)
|
|
15527
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15528
|
+
const recheck = () => {
|
|
15529
|
+
try {
|
|
15530
|
+
if (realpathSync7(command) !== resolved || linkIdentity(lstatSync17(command, { bigint: true })) !== linkIdentity(link) || !isDeepStrictEqual4(parents2(), parentIdentity) || !isDeepStrictEqual4(parents2(resolved), resolvedParentIdentity))
|
|
15531
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15532
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
15533
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
15534
|
+
unchanged2(cli);
|
|
15535
|
+
unchanged2(manifest);
|
|
15536
|
+
} catch {
|
|
15537
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15538
|
+
}
|
|
15539
|
+
};
|
|
15540
|
+
recheck();
|
|
15541
|
+
return { resolved, sha256: cli.sha256, manifestSha256: manifest.sha256, recheck };
|
|
15542
|
+
} catch {
|
|
15543
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15544
|
+
}
|
|
15545
|
+
}
|
|
15546
|
+
function planClaudeManagedHookProjection(options) {
|
|
15547
|
+
if (typeof options.targetSettings !== "string" || Buffer.byteLength(options.targetSettings) > 1024 * 1024)
|
|
15548
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15549
|
+
const targetSha256 = sha3(options.targetSettings);
|
|
15550
|
+
if (!/^[a-f0-9]{64}$/.test(options.expectedTargetSha256) || options.expectedTargetSha256 !== targetSha256)
|
|
15551
|
+
hookProjectionRefusal("TARGET_CHANGED");
|
|
15552
|
+
let target;
|
|
15553
|
+
try {
|
|
15554
|
+
target = jsonObject(options.targetSettings, "target");
|
|
15555
|
+
} catch {
|
|
15556
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15557
|
+
}
|
|
15558
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15559
|
+
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15560
|
+
let policyBytes, settingsBytes, settingsPath, command, profile;
|
|
15561
|
+
try {
|
|
15562
|
+
policyBytes = readNativeBytes(policyPath, 1024 * 1024);
|
|
15563
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
15564
|
+
if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
|
|
15565
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
15566
|
+
const binding = snapshot2.value.bridge;
|
|
15567
|
+
settingsPath = canonicalAgentPath(join18(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
|
|
15568
|
+
settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
|
|
15569
|
+
command = binding?.commands?.claude;
|
|
15570
|
+
profile = binding?.profiles?.claude;
|
|
15571
|
+
if (typeof command !== "string" || typeof profile !== "string")
|
|
15572
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
15573
|
+
assertManagedAgentBridge("claude", { home, dataDir: dataDir2, projectDir: options.projectDir, profileId: profile });
|
|
15574
|
+
} catch {
|
|
15575
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
15576
|
+
}
|
|
15577
|
+
const executable = projectedHookExecutable(command);
|
|
15578
|
+
const sourceSha256 = sha3(JSON.stringify({
|
|
15579
|
+
home,
|
|
15580
|
+
policyPath,
|
|
15581
|
+
policy: sha3(policyBytes),
|
|
15582
|
+
settingsPath,
|
|
15583
|
+
settings: sha3(settingsBytes),
|
|
15584
|
+
executable: { path: executable.resolved, sha256: executable.sha256, manifestSha256: executable.manifestSha256 }
|
|
15585
|
+
}));
|
|
15586
|
+
if (options.expectedSourceSha256 !== undefined && options.expectedSourceSha256 !== sourceSha256)
|
|
15587
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
15588
|
+
const replacements = [];
|
|
15589
|
+
const observed = [executable];
|
|
15590
|
+
if (target.hooks !== undefined) {
|
|
15591
|
+
if (!target.hooks || typeof target.hooks !== "object" || Array.isArray(target.hooks))
|
|
15592
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15593
|
+
for (const event of [...AGENT_ADAPTERS.claude.events, "PreToolUse"]) {
|
|
15594
|
+
const groups = target.hooks[event];
|
|
15595
|
+
if (groups === undefined)
|
|
15596
|
+
continue;
|
|
15597
|
+
if (!Array.isArray(groups))
|
|
15598
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15599
|
+
let recognized = 0;
|
|
15600
|
+
for (const [groupIndex, group] of groups.entries()) {
|
|
15601
|
+
if (!group || !Array.isArray(group.hooks))
|
|
15602
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15603
|
+
for (const [hookIndex, hook] of group.hooks.entries()) {
|
|
15604
|
+
if (hook?.type !== "command" || typeof hook.command !== "string")
|
|
15605
|
+
continue;
|
|
15606
|
+
if (!hook.command.includes("hook user-prompt --agent claude"))
|
|
15607
|
+
continue;
|
|
15608
|
+
const match = /^('(?:[^']|'\\'')*') hook user-prompt --agent claude --selection-profile ([A-Za-z0-9][A-Za-z0-9._-]{0,127}) --event ([A-Za-z]+)$/.exec(hook.command);
|
|
15609
|
+
if (!match || match[3] !== event)
|
|
15610
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
15611
|
+
const oldCommand = match[1].slice(1, -1).replaceAll("'\\''", "'");
|
|
15612
|
+
let rendered;
|
|
15613
|
+
try {
|
|
15614
|
+
rendered = renderAgentHookCommand(oldCommand, "claude", match[2], event);
|
|
15615
|
+
} catch {
|
|
15616
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
15617
|
+
}
|
|
15618
|
+
if (rendered !== hook.command)
|
|
15619
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
15620
|
+
const legacy = projectedHookExecutable(oldCommand, executable.resolved);
|
|
15621
|
+
if (legacy.resolved !== executable.resolved || legacy.sha256 !== executable.sha256 || legacy.manifestSha256 !== executable.manifestSha256)
|
|
15622
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15623
|
+
observed.push(legacy);
|
|
15624
|
+
if (++recognized > 1)
|
|
15625
|
+
hookProjectionRefusal("AMBIGUOUS");
|
|
15626
|
+
const replacement = renderAgentHookCommand(command, "claude", profile, event);
|
|
15627
|
+
if (replacement !== hook.command)
|
|
15628
|
+
replacements.push({ event, groupIndex, hookIndex, beforeSha256: sha3(hook.command), command: replacement });
|
|
15629
|
+
}
|
|
15630
|
+
}
|
|
15631
|
+
}
|
|
15632
|
+
}
|
|
15633
|
+
try {
|
|
15634
|
+
if (!readNativeBytes(policyPath, 1024 * 1024).equals(policyBytes) || !readNativeBytes(settingsPath, 1024 * 1024).equals(settingsBytes))
|
|
15635
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
15636
|
+
} catch {
|
|
15637
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
15638
|
+
}
|
|
15639
|
+
for (const item of observed)
|
|
15640
|
+
item.recheck();
|
|
15641
|
+
return { schema: "hasna.claude-managed-hook-projection.v1", targetSha256, sourceSha256, replacements };
|
|
15642
|
+
}
|
|
15412
15643
|
function planClaudeStopHookUpdate(options) {
|
|
15413
15644
|
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
15414
15645
|
}
|
|
@@ -15441,10 +15672,10 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15441
15672
|
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15442
15673
|
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15443
15674
|
assertSafePath(policyPath);
|
|
15444
|
-
const
|
|
15445
|
-
if (!
|
|
15675
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
15676
|
+
if (!snapshot2)
|
|
15446
15677
|
return null;
|
|
15447
|
-
const policy =
|
|
15678
|
+
const policy = snapshot2.value, bridge = policy.bridge;
|
|
15448
15679
|
if (!bridge || bridge.home !== home || bridge.version !== CLI_BRIDGE_VERSION || bridge.digest !== CLI_BRIDGE_DIGEST || !Array.isArray(bridge.agents) || !bridge.agents.length || bridge.agents.some((agent) => !INTEGRATION_AGENTS.includes(agent)) || new Set(bridge.agents).size !== bridge.agents.length || !bridge.discovery || typeof bridge.discovery !== "object" || Array.isArray(bridge.discovery)) {
|
|
15449
15680
|
throw new Error("Invalid managed bridge for Claude settings update");
|
|
15450
15681
|
}
|
|
@@ -15452,7 +15683,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15452
15683
|
recheckRootAliases(aliases);
|
|
15453
15684
|
const path = canonicalAgentPath(join18(home, ".claude", "settings.json"), aliases);
|
|
15454
15685
|
assertSafePath(path);
|
|
15455
|
-
const before =
|
|
15686
|
+
const before = lstatSync17(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
15456
15687
|
if ((before === null ? "absent" : sha3(before)) !== options.expectedSettingsSha256)
|
|
15457
15688
|
throw new Error("Claude settings preimage changed");
|
|
15458
15689
|
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
@@ -15482,7 +15713,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15482
15713
|
const nextPolicy = { ...policy, bridge: { ...bridge, discovery: Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) } };
|
|
15483
15714
|
const changes = before === options.replacement ? [] : [{ path, before, after: options.replacement }];
|
|
15484
15715
|
if (JSON.stringify(nextPolicy) !== JSON.stringify(policy))
|
|
15485
|
-
changes.push({ path: policyPath, before:
|
|
15716
|
+
changes.push({ path: policyPath, before: snapshot2.text, after: serializeManagedSkillPolicy(nextPolicy) });
|
|
15486
15717
|
recheckRootAliases(aliases);
|
|
15487
15718
|
const nativeSkills = inventoryNativeSkills(home, {
|
|
15488
15719
|
agents: consumers,
|
|
@@ -15497,7 +15728,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15497
15728
|
profileId: policy.profileId ?? "default",
|
|
15498
15729
|
changes,
|
|
15499
15730
|
nativeSkills,
|
|
15500
|
-
observedPolicy: { path: policyPath, before:
|
|
15731
|
+
observedPolicy: { path: policyPath, before: snapshot2.text },
|
|
15501
15732
|
discoveryBefore: discoveries,
|
|
15502
15733
|
discoveryAfter,
|
|
15503
15734
|
rootAliases: aliases,
|
|
@@ -15506,10 +15737,10 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15506
15737
|
}
|
|
15507
15738
|
function atomicWrite2(path, content) {
|
|
15508
15739
|
assertSafePath(path);
|
|
15509
|
-
mkdirSync5(
|
|
15740
|
+
mkdirSync5(dirname12(path), { recursive: true, mode: 448 });
|
|
15510
15741
|
const temporary = `${path}.skills-${randomUUID2()}`;
|
|
15511
15742
|
try {
|
|
15512
|
-
|
|
15743
|
+
writeFileSync5(temporary, content, { mode: 384, flag: "wx" });
|
|
15513
15744
|
renameSync2(temporary, path);
|
|
15514
15745
|
} finally {
|
|
15515
15746
|
if (existsSync9(temporary))
|
|
@@ -15520,10 +15751,10 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15520
15751
|
if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
|
|
15521
15752
|
throw new Error("Invalid Codex witness target version");
|
|
15522
15753
|
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15523
|
-
const
|
|
15524
|
-
if (!
|
|
15754
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
15755
|
+
if (!snapshot2 || sha3(snapshot2.text) !== options.expectedPolicySha256)
|
|
15525
15756
|
throw new Error("Managed policy preimage changed");
|
|
15526
|
-
const policy =
|
|
15757
|
+
const policy = snapshot2.value, aliases = policy.bridge?.rootAliases ?? [];
|
|
15527
15758
|
recheckRootAliases(aliases);
|
|
15528
15759
|
const binding = policy.bridge?.discovery?.[options.agent];
|
|
15529
15760
|
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
@@ -15567,7 +15798,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15567
15798
|
const replacement = { ...binding, sources: rebound };
|
|
15568
15799
|
verifyAgentDiscovery(replacement);
|
|
15569
15800
|
const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
15570
|
-
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join18(dataDir2, "agent-policy.json"), before:
|
|
15801
|
+
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join18(dataDir2, "agent-policy.json"), before: snapshot2.text, after }], nativeSkills: [], observedPolicy: { path: join18(dataDir2, "agent-policy.json"), before: snapshot2.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 })) } };
|
|
15571
15802
|
}
|
|
15572
15803
|
function applyAgentIntegration(plan) {
|
|
15573
15804
|
for (const source of plan.observedNativeSources ?? [])
|
|
@@ -15620,7 +15851,7 @@ function applyAgentIntegration(plan) {
|
|
|
15620
15851
|
for (const [index, change] of plan.changes.entries())
|
|
15621
15852
|
if (change.before !== null) {
|
|
15622
15853
|
const backup = join18(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
|
|
15623
|
-
|
|
15854
|
+
writeFileSync5(backup, change.before, { mode: 384, flag: "wx" });
|
|
15624
15855
|
if (readFileSync12(backup, "utf8") !== change.before)
|
|
15625
15856
|
throw new Error("Native configuration preservation readback failed");
|
|
15626
15857
|
backups.push(backup);
|
|
@@ -15630,7 +15861,7 @@ function applyAgentIntegration(plan) {
|
|
|
15630
15861
|
recheckRootAliases(aliases);
|
|
15631
15862
|
if (currentText(change.path) !== change.before)
|
|
15632
15863
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15633
|
-
for (let directory =
|
|
15864
|
+
for (let directory = dirname12(change.path);!existsSync9(directory); directory = dirname12(directory))
|
|
15634
15865
|
createdDirectories.add(directory);
|
|
15635
15866
|
const after = change.after;
|
|
15636
15867
|
atomicWrite2(change.path, after);
|
|
@@ -15681,11 +15912,11 @@ function applyAgentIntegration(plan) {
|
|
|
15681
15912
|
}
|
|
15682
15913
|
function syncArchiveDirectory(path) {
|
|
15683
15914
|
assertSafePath(path);
|
|
15684
|
-
const descriptor =
|
|
15915
|
+
const descriptor = openSync9(path, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
15685
15916
|
try {
|
|
15686
|
-
|
|
15917
|
+
fsyncSync3(descriptor);
|
|
15687
15918
|
} finally {
|
|
15688
|
-
|
|
15919
|
+
closeSync9(descriptor);
|
|
15689
15920
|
}
|
|
15690
15921
|
}
|
|
15691
15922
|
function writeArchiveJournal(path, value) {
|
|
@@ -15693,18 +15924,18 @@ function writeArchiveJournal(path, value) {
|
|
|
15693
15924
|
const temporary = `${path}.skills-${randomUUID2()}`;
|
|
15694
15925
|
let descriptor;
|
|
15695
15926
|
try {
|
|
15696
|
-
descriptor =
|
|
15697
|
-
|
|
15927
|
+
descriptor = openSync9(temporary, constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW, 384);
|
|
15928
|
+
writeFileSync5(descriptor, `${JSON.stringify(value)}
|
|
15698
15929
|
`);
|
|
15699
|
-
|
|
15700
|
-
|
|
15930
|
+
fsyncSync3(descriptor);
|
|
15931
|
+
closeSync9(descriptor);
|
|
15701
15932
|
descriptor = undefined;
|
|
15702
15933
|
renameSync2(temporary, path);
|
|
15703
|
-
syncArchiveDirectory(
|
|
15934
|
+
syncArchiveDirectory(dirname12(path));
|
|
15704
15935
|
} finally {
|
|
15705
15936
|
if (descriptor !== undefined)
|
|
15706
|
-
|
|
15707
|
-
if (
|
|
15937
|
+
closeSync9(descriptor);
|
|
15938
|
+
if (lstatSync17(temporary, { throwIfNoEntry: false }))
|
|
15708
15939
|
unlinkSync3(temporary);
|
|
15709
15940
|
}
|
|
15710
15941
|
}
|
|
@@ -15738,7 +15969,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15738
15969
|
const moved = [];
|
|
15739
15970
|
const verifyArchive = (entry) => {
|
|
15740
15971
|
assertSafePath(entry.archive);
|
|
15741
|
-
const stat =
|
|
15972
|
+
const stat = lstatSync17(entry.archive);
|
|
15742
15973
|
if (entry.discoveryOnly ? !stat.isFile() : !stat.isDirectory())
|
|
15743
15974
|
throw new Error("Archive type changed");
|
|
15744
15975
|
if ((entry.discoveryOnly ? sha3(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -15746,14 +15977,14 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15746
15977
|
};
|
|
15747
15978
|
assertSafePath(archiveRoot);
|
|
15748
15979
|
const created = [];
|
|
15749
|
-
for (let path = archiveRoot;!existsSync9(path); path =
|
|
15980
|
+
for (let path = archiveRoot;!existsSync9(path); path = dirname12(path))
|
|
15750
15981
|
created.push(path);
|
|
15751
15982
|
mkdirSync5(archiveRoot, { recursive: true, mode: 448 });
|
|
15752
15983
|
try {
|
|
15753
15984
|
for (const directory of created)
|
|
15754
15985
|
syncArchiveDirectory(directory);
|
|
15755
15986
|
if (created.length)
|
|
15756
|
-
syncArchiveDirectory(
|
|
15987
|
+
syncArchiveDirectory(dirname12(created.at(-1)));
|
|
15757
15988
|
writeArchiveJournal(receiptPath, journal);
|
|
15758
15989
|
for (const [index, entry] of selected.entries()) {
|
|
15759
15990
|
recheckRootAliases(aliases);
|
|
@@ -15765,7 +15996,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15765
15996
|
writeArchiveJournal(receiptPath, journal);
|
|
15766
15997
|
renameSync2(move.source, move.archive);
|
|
15767
15998
|
moved.push(move);
|
|
15768
|
-
syncArchiveDirectory(
|
|
15999
|
+
syncArchiveDirectory(dirname12(move.source));
|
|
15769
16000
|
syncArchiveDirectory(archiveRoot);
|
|
15770
16001
|
verifyArchive(move);
|
|
15771
16002
|
move.status = "archived";
|
|
@@ -15786,7 +16017,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15786
16017
|
recheckRootAliases(aliases);
|
|
15787
16018
|
assertSafePath(entry.source);
|
|
15788
16019
|
assertSafePath(entry.archive);
|
|
15789
|
-
if (
|
|
16020
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
15790
16021
|
entry.status = "recovery-required";
|
|
15791
16022
|
entry.conflict = "source-occupied";
|
|
15792
16023
|
continue;
|
|
@@ -15800,10 +16031,10 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15800
16031
|
}
|
|
15801
16032
|
if (entry.discoveryOnly) {
|
|
15802
16033
|
linkSync2(entry.archive, entry.source);
|
|
15803
|
-
syncArchiveDirectory(
|
|
16034
|
+
syncArchiveDirectory(dirname12(entry.source));
|
|
15804
16035
|
unlinkSync3(entry.archive);
|
|
15805
16036
|
} else {
|
|
15806
|
-
if (
|
|
16037
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
15807
16038
|
entry.status = "recovery-required";
|
|
15808
16039
|
entry.conflict = "source-occupied";
|
|
15809
16040
|
continue;
|
|
@@ -15811,7 +16042,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15811
16042
|
renameSync2(entry.archive, entry.source);
|
|
15812
16043
|
}
|
|
15813
16044
|
entry.status = "restored";
|
|
15814
|
-
syncArchiveDirectory(
|
|
16045
|
+
syncArchiveDirectory(dirname12(entry.source));
|
|
15815
16046
|
syncArchiveDirectory(archiveRoot);
|
|
15816
16047
|
} catch {
|
|
15817
16048
|
entry.status = "recovery-required";
|
|
@@ -15836,10 +16067,10 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
15836
16067
|
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
15837
16068
|
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15838
16069
|
assertSafePath(join18(dataDir2, "agent-policy.json"));
|
|
15839
|
-
const
|
|
15840
|
-
if (!
|
|
16070
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
16071
|
+
if (!snapshot2)
|
|
15841
16072
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
15842
|
-
const binding =
|
|
16073
|
+
const binding = snapshot2.value.bridge;
|
|
15843
16074
|
if (!binding || binding.version !== CLI_BRIDGE_VERSION || binding.digest !== CLI_BRIDGE_DIGEST || binding.home !== home || !Array.isArray(binding.agents) || !binding.agents.includes(agent))
|
|
15844
16075
|
throw new Error("NATIVE_SKILL_DRIFT: the managed Skills bridge binding is missing or incompatible; run skills hook install");
|
|
15845
16076
|
const aliases = binding.rootAliases ?? [];
|
|
@@ -15980,7 +16211,7 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
15980
16211
|
function hasSkillsOwnershipMarker(dir) {
|
|
15981
16212
|
const path = join19(dir, SYNC_MARKER_FILE);
|
|
15982
16213
|
try {
|
|
15983
|
-
if (!
|
|
16214
|
+
if (!lstatSync18(path).isFile())
|
|
15984
16215
|
return false;
|
|
15985
16216
|
const marker = JSON.parse(readFileSync13(path, "utf8"));
|
|
15986
16217
|
return isSkillsOwnershipMarker(marker);
|
|
@@ -16206,7 +16437,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16206
16437
|
const action = dirExists ? "update" : "create";
|
|
16207
16438
|
if (options.dryRun)
|
|
16208
16439
|
return { action, path: skillMdPath };
|
|
16209
|
-
const parentDir =
|
|
16440
|
+
const parentDir = dirname13(dir);
|
|
16210
16441
|
mkdirSync6(parentDir, { recursive: true });
|
|
16211
16442
|
const transactionDir = mkdtempSync(join19(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
|
|
16212
16443
|
const candidateDir = join19(transactionDir, "candidate");
|
|
@@ -16228,10 +16459,10 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16228
16459
|
} else {
|
|
16229
16460
|
mkdirSync6(candidateDir, { recursive: true });
|
|
16230
16461
|
}
|
|
16231
|
-
|
|
16462
|
+
writeFileSync6(candidateSkillMdPath, skillMd.endsWith(`
|
|
16232
16463
|
`) ? skillMd : `${skillMd}
|
|
16233
16464
|
`);
|
|
16234
|
-
|
|
16465
|
+
writeFileSync6(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
|
|
16235
16466
|
`);
|
|
16236
16467
|
if (dirExists) {
|
|
16237
16468
|
originalMoved = true;
|
|
@@ -16311,7 +16542,7 @@ function resolveSkillAlias(name) {
|
|
|
16311
16542
|
}
|
|
16312
16543
|
|
|
16313
16544
|
// src/lib/project-state.ts
|
|
16314
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as
|
|
16545
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
16315
16546
|
import { join as join20 } from "path";
|
|
16316
16547
|
var VALID_PIN_SOURCES = [
|
|
16317
16548
|
"official",
|
|
@@ -16360,7 +16591,7 @@ function saveProjectConfig(config, targetDir = process.cwd()) {
|
|
|
16360
16591
|
const dir = getProjectStateDir(targetDir);
|
|
16361
16592
|
mkdirSync7(dir, { recursive: true });
|
|
16362
16593
|
const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
|
|
16363
|
-
|
|
16594
|
+
writeFileSync7(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
|
|
16364
16595
|
`);
|
|
16365
16596
|
}
|
|
16366
16597
|
function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
|
|
@@ -17179,7 +17410,7 @@ function exactProfileSelection(spec, profile) {
|
|
|
17179
17410
|
}
|
|
17180
17411
|
|
|
17181
17412
|
// src/lib/plugin-projection.ts
|
|
17182
|
-
import { createHash as
|
|
17413
|
+
import { createHash as createHash14 } from "crypto";
|
|
17183
17414
|
import { posix } from "path";
|
|
17184
17415
|
var PLUGIN_PROJECTION_MANIFEST = "plugin-projection.json";
|
|
17185
17416
|
var PLUGIN_PROJECTION_LIMITS = Object.freeze({ files: 1024, bytes: 64 * 1024 * 1024, fileBytes: 16 * 1024 * 1024, metadataBytes: 512 * 1024, registrations: 64, history: 128 });
|
|
@@ -17190,7 +17421,7 @@ function pluginNeed(value, message) {
|
|
|
17190
17421
|
if (!value)
|
|
17191
17422
|
pluginRefusal(message);
|
|
17192
17423
|
}
|
|
17193
|
-
var pluginHash = (bytes) =>
|
|
17424
|
+
var pluginHash = (bytes) => createHash14("sha256").update(bytes).digest("hex");
|
|
17194
17425
|
function pluginObject(value) {
|
|
17195
17426
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17196
17427
|
}
|
|
@@ -17378,16 +17609,16 @@ function buildPluginProjection(entries) {
|
|
|
17378
17609
|
}
|
|
17379
17610
|
|
|
17380
17611
|
// src/lib/plugin-projection-store.ts
|
|
17381
|
-
import { createHash as
|
|
17382
|
-
import { constants as
|
|
17383
|
-
import { dirname as
|
|
17384
|
-
var READ_FLAGS =
|
|
17385
|
-
var WRITE_FLAGS =
|
|
17612
|
+
import { createHash as createHash15, randomUUID as randomUUID3 } from "crypto";
|
|
17613
|
+
import { constants as constants9, closeSync as closeSync10, fchmodSync, fstatSync as fstatSync10, fsyncSync as fsyncSync4, linkSync as linkSync3, lstatSync as lstatSync19, mkdirSync as mkdirSync8, openSync as openSync10, opendirSync as opendirSync4, readSync as readSync8, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
|
|
17614
|
+
import { dirname as dirname14, isAbsolute as isAbsolute12, join as join25, parse as parse5, resolve as resolve12 } from "path";
|
|
17615
|
+
var READ_FLAGS = constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK;
|
|
17616
|
+
var WRITE_FLAGS = constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW | constants9.O_NONBLOCK;
|
|
17386
17617
|
var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined;
|
|
17387
17618
|
var missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
17388
17619
|
function stat(path) {
|
|
17389
17620
|
try {
|
|
17390
|
-
return
|
|
17621
|
+
return lstatSync19(path, { bigint: true });
|
|
17391
17622
|
} catch (error) {
|
|
17392
17623
|
if (missing(error))
|
|
17393
17624
|
return null;
|
|
@@ -17406,7 +17637,7 @@ function same3(left, right, identityOnly = false) {
|
|
|
17406
17637
|
}
|
|
17407
17638
|
function ancestors(path, create = false) {
|
|
17408
17639
|
canonical5(path);
|
|
17409
|
-
const result = [], parent =
|
|
17640
|
+
const result = [], parent = dirname14(path), filesystemRoot = parse5(parent).root;
|
|
17410
17641
|
let current = filesystemRoot;
|
|
17411
17642
|
for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
|
|
17412
17643
|
if (segment)
|
|
@@ -17434,7 +17665,7 @@ function verifyAncestors(chain, identityOnly = true) {
|
|
|
17434
17665
|
}
|
|
17435
17666
|
function privateParent(path) {
|
|
17436
17667
|
const chain = ancestors(path, true), parent = chain.at(-1);
|
|
17437
|
-
pluginNeed(parent && parent.path ===
|
|
17668
|
+
pluginNeed(parent && parent.path === dirname14(path), "Plugin storage parent is unavailable");
|
|
17438
17669
|
owner(parent.stat);
|
|
17439
17670
|
pluginNeed((parent.stat.mode & 0o7777n) === 0o700n, "Plugin storage parent must have mode 0700");
|
|
17440
17671
|
return chain;
|
|
@@ -17463,19 +17694,19 @@ function readBytes(path, maximum, kind) {
|
|
|
17463
17694
|
fileShape(before, maximum, kind);
|
|
17464
17695
|
let fd;
|
|
17465
17696
|
try {
|
|
17466
|
-
fd =
|
|
17467
|
-
const opened =
|
|
17697
|
+
fd = openSync10(path, READ_FLAGS);
|
|
17698
|
+
const opened = fstatSync10(fd, { bigint: true });
|
|
17468
17699
|
same3(before, opened);
|
|
17469
17700
|
fileShape(opened, maximum, kind);
|
|
17470
17701
|
const bytes = new Uint8Array(Number(opened.size));
|
|
17471
17702
|
let offset = 0;
|
|
17472
17703
|
while (offset < bytes.byteLength) {
|
|
17473
|
-
const count =
|
|
17704
|
+
const count = readSync8(fd, bytes, offset, bytes.byteLength - offset, null);
|
|
17474
17705
|
pluginNeed(count > 0, "Plugin file changed while being read");
|
|
17475
17706
|
offset += count;
|
|
17476
17707
|
}
|
|
17477
|
-
pluginNeed(
|
|
17478
|
-
same3(opened,
|
|
17708
|
+
pluginNeed(readSync8(fd, new Uint8Array(1), 0, 1, null) === 0, "Plugin file grew while being read");
|
|
17709
|
+
same3(opened, fstatSync10(fd, { bigint: true }));
|
|
17479
17710
|
same3(opened, stat(path));
|
|
17480
17711
|
verifyAncestors(chain);
|
|
17481
17712
|
return bytes;
|
|
@@ -17485,7 +17716,7 @@ function readBytes(path, maximum, kind) {
|
|
|
17485
17716
|
return pluginRefusal("Cannot safely read plugin file");
|
|
17486
17717
|
} finally {
|
|
17487
17718
|
if (fd !== undefined)
|
|
17488
|
-
|
|
17719
|
+
closeSync10(fd);
|
|
17489
17720
|
}
|
|
17490
17721
|
}
|
|
17491
17722
|
function exactPaths(entries) {
|
|
@@ -17595,16 +17826,16 @@ function verifyPluginTree(root, witnesses) {
|
|
|
17595
17826
|
function writeExclusive(path, bytes, mode) {
|
|
17596
17827
|
let fd, created;
|
|
17597
17828
|
try {
|
|
17598
|
-
fd =
|
|
17829
|
+
fd = openSync10(path, WRITE_FLAGS, mode);
|
|
17599
17830
|
fchmodSync(fd, mode);
|
|
17600
|
-
created =
|
|
17831
|
+
created = fstatSync10(fd, { bigint: true });
|
|
17601
17832
|
let offset = 0;
|
|
17602
17833
|
while (offset < bytes.byteLength) {
|
|
17603
17834
|
const count = writeSync2(fd, bytes, offset, bytes.byteLength - offset);
|
|
17604
17835
|
pluginNeed(count > 0, "Plugin file write did not progress");
|
|
17605
17836
|
offset += count;
|
|
17606
17837
|
}
|
|
17607
|
-
|
|
17838
|
+
fsyncSync4(fd);
|
|
17608
17839
|
} catch (error) {
|
|
17609
17840
|
if (created) {
|
|
17610
17841
|
same3(created, stat(path), true);
|
|
@@ -17613,7 +17844,7 @@ function writeExclusive(path, bytes, mode) {
|
|
|
17613
17844
|
throw error;
|
|
17614
17845
|
} finally {
|
|
17615
17846
|
if (fd !== undefined)
|
|
17616
|
-
|
|
17847
|
+
closeSync10(fd);
|
|
17617
17848
|
}
|
|
17618
17849
|
}
|
|
17619
17850
|
function materializePluginTree(root, entries) {
|
|
@@ -17716,20 +17947,20 @@ function pluginExecutableDigest(path) {
|
|
|
17716
17947
|
fileShape(before, maximum, "executable");
|
|
17717
17948
|
let fd;
|
|
17718
17949
|
try {
|
|
17719
|
-
fd =
|
|
17720
|
-
const opened =
|
|
17950
|
+
fd = openSync10(path, READ_FLAGS);
|
|
17951
|
+
const opened = fstatSync10(fd, { bigint: true });
|
|
17721
17952
|
same3(before, opened);
|
|
17722
17953
|
fileShape(opened, maximum, "executable");
|
|
17723
|
-
const hash2 =
|
|
17954
|
+
const hash2 = createHash15("sha256"), buffer = new Uint8Array(64 * 1024);
|
|
17724
17955
|
let remaining = Number(opened.size);
|
|
17725
17956
|
while (remaining > 0) {
|
|
17726
|
-
const count =
|
|
17957
|
+
const count = readSync8(fd, buffer, 0, Math.min(buffer.byteLength, remaining), null);
|
|
17727
17958
|
pluginNeed(count > 0, "Plugin executable changed during hashing");
|
|
17728
17959
|
hash2.update(buffer.subarray(0, count));
|
|
17729
17960
|
remaining -= count;
|
|
17730
17961
|
}
|
|
17731
|
-
pluginNeed(
|
|
17732
|
-
same3(opened,
|
|
17962
|
+
pluginNeed(readSync8(fd, buffer, 0, 1, null) === 0, "Plugin executable grew during hashing");
|
|
17963
|
+
same3(opened, fstatSync10(fd, { bigint: true }));
|
|
17733
17964
|
same3(opened, stat(path));
|
|
17734
17965
|
verifyAncestors(chain);
|
|
17735
17966
|
return `sha256:${hash2.digest("hex")}`;
|
|
@@ -17739,7 +17970,7 @@ function pluginExecutableDigest(path) {
|
|
|
17739
17970
|
return pluginRefusal("Cannot safely hash plugin executable");
|
|
17740
17971
|
} finally {
|
|
17741
17972
|
if (fd !== undefined)
|
|
17742
|
-
|
|
17973
|
+
closeSync10(fd);
|
|
17743
17974
|
}
|
|
17744
17975
|
}
|
|
17745
17976
|
|
|
@@ -18149,29 +18380,29 @@ function validateManagedPluginWitnesses(value) {
|
|
|
18149
18380
|
function safe3(path) {
|
|
18150
18381
|
pluginText(path);
|
|
18151
18382
|
pluginNeed(isAbsolute14(path) && resolve14(path) === path, "Invalid plugin registry path");
|
|
18152
|
-
for (let at = path;; at =
|
|
18153
|
-
pluginNeed(!
|
|
18154
|
-
if (at ===
|
|
18383
|
+
for (let at = path;; at = dirname15(at)) {
|
|
18384
|
+
pluginNeed(!lstatSync20(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
18385
|
+
if (at === dirname15(at))
|
|
18155
18386
|
break;
|
|
18156
18387
|
}
|
|
18157
18388
|
}
|
|
18158
18389
|
function readRegistry(path) {
|
|
18159
18390
|
safe3(path);
|
|
18160
|
-
const initial =
|
|
18391
|
+
const initial = lstatSync20(path);
|
|
18161
18392
|
pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
18162
|
-
const fd =
|
|
18393
|
+
const fd = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
|
|
18163
18394
|
try {
|
|
18164
|
-
const opened =
|
|
18395
|
+
const opened = fstatSync11(fd);
|
|
18165
18396
|
pluginNeed(opened.isFile() && opened.ino === initial.ino && opened.dev === initial.dev, "Plugin registry changed during open");
|
|
18166
18397
|
const bytes = Buffer.alloc(1024 * 1024 + 1);
|
|
18167
18398
|
let length = 0;
|
|
18168
18399
|
while (length < bytes.length) {
|
|
18169
|
-
const count =
|
|
18400
|
+
const count = readSync9(fd, bytes, length, bytes.length - length, null);
|
|
18170
18401
|
if (!count)
|
|
18171
18402
|
break;
|
|
18172
18403
|
length += count;
|
|
18173
18404
|
}
|
|
18174
|
-
const after =
|
|
18405
|
+
const after = fstatSync11(fd), current = lstatSync20(path);
|
|
18175
18406
|
safe3(path);
|
|
18176
18407
|
pluginNeed(length <= 1024 * 1024 && length === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
|
|
18177
18408
|
let value;
|
|
@@ -18183,7 +18414,7 @@ function readRegistry(path) {
|
|
|
18183
18414
|
pluginNeed(pluginObject(value) && value.version === 2 && pluginObject(value.plugins), "Unsupported native plugin registry schema");
|
|
18184
18415
|
return { value, rawDigest: pluginHash(bytes.subarray(0, length)) };
|
|
18185
18416
|
} finally {
|
|
18186
|
-
|
|
18417
|
+
closeSync11(fd);
|
|
18187
18418
|
}
|
|
18188
18419
|
}
|
|
18189
18420
|
function nativeVersionMatches(version, receipt) {
|
|
@@ -18192,7 +18423,7 @@ function nativeVersionMatches(version, receipt) {
|
|
|
18192
18423
|
}
|
|
18193
18424
|
function hashManagedPluginRegistry(path, managed) {
|
|
18194
18425
|
validateManagedPluginWitnesses(managed);
|
|
18195
|
-
const
|
|
18426
|
+
const snapshot2 = readRegistry(path), plugins = snapshot2.value.plugins;
|
|
18196
18427
|
const seen = new Set, verified = new Set;
|
|
18197
18428
|
let verifiedBytes = 0;
|
|
18198
18429
|
for (const witness of managed) {
|
|
@@ -18201,7 +18432,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18201
18432
|
seen.add(target.pluginId);
|
|
18202
18433
|
const rows = plugins[target.pluginId];
|
|
18203
18434
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
18204
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot = join27(
|
|
18435
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join27(dirname15(path), "cache", market, name);
|
|
18205
18436
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
18206
18437
|
plugins[target.pluginId] = rows.map((row) => {
|
|
18207
18438
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -18244,7 +18475,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18244
18475
|
return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
|
|
18245
18476
|
});
|
|
18246
18477
|
safe3(cacheRoot);
|
|
18247
|
-
const before =
|
|
18478
|
+
const before = lstatSync20(cacheRoot);
|
|
18248
18479
|
pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
|
|
18249
18480
|
const directory = opendirSync5(cacheRoot), names = [];
|
|
18250
18481
|
try {
|
|
@@ -18267,64 +18498,64 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18267
18498
|
}
|
|
18268
18499
|
for (const row of rows)
|
|
18269
18500
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
18270
|
-
const after =
|
|
18501
|
+
const after = lstatSync20(cacheRoot);
|
|
18271
18502
|
safe3(cacheRoot);
|
|
18272
18503
|
pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
|
|
18273
18504
|
}
|
|
18274
|
-
pluginNeed(readRegistry(path).rawDigest ===
|
|
18275
|
-
return pluginHash(JSON.stringify(
|
|
18505
|
+
pluginNeed(readRegistry(path).rawDigest === snapshot2.rawDigest, "Native plugin registry changed during content verification");
|
|
18506
|
+
return pluginHash(JSON.stringify(snapshot2.value));
|
|
18276
18507
|
}
|
|
18277
18508
|
function captureManagedPluginRegistry(path, managedPlugins) {
|
|
18278
18509
|
return { path, hashMode: "claude-plugin-registry", managedPlugins, sha256: hashManagedPluginRegistry(path, managedPlugins) };
|
|
18279
18510
|
}
|
|
18280
18511
|
|
|
18281
18512
|
// src/lib/claude-marketplace-registry.ts
|
|
18282
|
-
import { createHash as
|
|
18283
|
-
import { closeSync as
|
|
18284
|
-
import { basename as basename8, dirname as
|
|
18513
|
+
import { createHash as createHash16 } from "crypto";
|
|
18514
|
+
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10 } from "fs";
|
|
18515
|
+
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute15, resolve as resolve15 } from "path";
|
|
18285
18516
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
18286
|
-
function
|
|
18517
|
+
function need6(value, reason) {
|
|
18287
18518
|
if (!value)
|
|
18288
18519
|
throw new Error(`Claude marketplace registry ${reason}`);
|
|
18289
18520
|
}
|
|
18290
18521
|
function absolutePath3(value) {
|
|
18291
|
-
|
|
18522
|
+
need6(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve15(value) === value, "requires a normalized absolute path");
|
|
18292
18523
|
}
|
|
18293
18524
|
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;
|
|
18294
18525
|
function parents2(path) {
|
|
18295
18526
|
const result = [];
|
|
18296
|
-
for (let at =
|
|
18297
|
-
const stat2 =
|
|
18298
|
-
|
|
18527
|
+
for (let at = dirname16(path);; at = dirname16(at)) {
|
|
18528
|
+
const stat2 = lstatSync21(at, { bigint: true });
|
|
18529
|
+
need6(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
18299
18530
|
result.push([at, stat2]);
|
|
18300
|
-
if (at ===
|
|
18531
|
+
if (at === dirname16(at))
|
|
18301
18532
|
return result;
|
|
18302
18533
|
}
|
|
18303
18534
|
}
|
|
18304
18535
|
function readRegistry2(path, budget) {
|
|
18305
18536
|
absolutePath3(path);
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
const ancestors2 = parents2(path), initial =
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
const fd =
|
|
18537
|
+
need6(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
18538
|
+
need6(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
18539
|
+
const ancestors2 = parents2(path), initial = lstatSync21(path, { bigint: true });
|
|
18540
|
+
need6(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
18541
|
+
need6(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
18542
|
+
const fd = openSync12(path, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
18312
18543
|
try {
|
|
18313
|
-
const opened =
|
|
18314
|
-
|
|
18544
|
+
const opened = fstatSync12(fd, { bigint: true });
|
|
18545
|
+
need6(opened.isFile() && sameFile2(initial, opened), "changed during open");
|
|
18315
18546
|
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
18316
18547
|
let length = 0;
|
|
18317
18548
|
while (length < bytes.length) {
|
|
18318
|
-
const count =
|
|
18549
|
+
const count = readSync10(fd, bytes, length, bytes.length - length, null);
|
|
18319
18550
|
if (!count)
|
|
18320
18551
|
break;
|
|
18321
18552
|
length += count;
|
|
18322
18553
|
}
|
|
18323
|
-
const after =
|
|
18324
|
-
|
|
18554
|
+
const after = fstatSync12(fd, { bigint: true }), current = lstatSync21(path, { bigint: true });
|
|
18555
|
+
need6(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
18325
18556
|
for (const [ancestor, before] of ancestors2) {
|
|
18326
|
-
const now =
|
|
18327
|
-
|
|
18557
|
+
const now = lstatSync21(ancestor, { bigint: true });
|
|
18558
|
+
need6(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
18328
18559
|
}
|
|
18329
18560
|
budget.remaining -= length;
|
|
18330
18561
|
try {
|
|
@@ -18333,7 +18564,7 @@ function readRegistry2(path, budget) {
|
|
|
18333
18564
|
throw new Error("Claude marketplace registry is not strict UTF-8");
|
|
18334
18565
|
}
|
|
18335
18566
|
} finally {
|
|
18336
|
-
|
|
18567
|
+
closeSync12(fd);
|
|
18337
18568
|
}
|
|
18338
18569
|
}
|
|
18339
18570
|
function parse6(text2) {
|
|
@@ -18344,7 +18575,7 @@ function parse6(text2) {
|
|
|
18344
18575
|
at++;
|
|
18345
18576
|
};
|
|
18346
18577
|
function string() {
|
|
18347
|
-
|
|
18578
|
+
need6(text2[at] === '"', "contains invalid JSON");
|
|
18348
18579
|
const start = at++;
|
|
18349
18580
|
while (at < text2.length) {
|
|
18350
18581
|
const character = text2[at++];
|
|
@@ -18355,7 +18586,7 @@ function parse6(text2) {
|
|
|
18355
18586
|
} catch {
|
|
18356
18587
|
throw new Error("Claude marketplace registry contains invalid JSON string");
|
|
18357
18588
|
}
|
|
18358
|
-
|
|
18589
|
+
need6(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
|
|
18359
18590
|
return value2;
|
|
18360
18591
|
}
|
|
18361
18592
|
if (character === "\\")
|
|
@@ -18364,7 +18595,7 @@ function parse6(text2) {
|
|
|
18364
18595
|
throw new Error("Claude marketplace registry contains an unterminated JSON string");
|
|
18365
18596
|
}
|
|
18366
18597
|
function value(depth) {
|
|
18367
|
-
|
|
18598
|
+
need6(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
|
|
18368
18599
|
space();
|
|
18369
18600
|
const character = text2[at];
|
|
18370
18601
|
if (character === '"')
|
|
@@ -18378,17 +18609,17 @@ function parse6(text2) {
|
|
|
18378
18609
|
if (object5) {
|
|
18379
18610
|
space();
|
|
18380
18611
|
const key = string();
|
|
18381
|
-
|
|
18612
|
+
need6(!keys.has(key), "contains a duplicate JSON key");
|
|
18382
18613
|
keys.add(key);
|
|
18383
18614
|
space();
|
|
18384
|
-
|
|
18615
|
+
need6(text2[at++] === ":", "contains invalid JSON");
|
|
18385
18616
|
entries.push([key, value(depth + 1)]);
|
|
18386
18617
|
} else
|
|
18387
18618
|
items.push(value(depth + 1));
|
|
18388
18619
|
space();
|
|
18389
18620
|
if (text2[at] === end)
|
|
18390
18621
|
break;
|
|
18391
|
-
|
|
18622
|
+
need6(text2[at++] === ",", "contains invalid JSON");
|
|
18392
18623
|
}
|
|
18393
18624
|
at++;
|
|
18394
18625
|
return object5 ? { kind: "object", entries } : { kind: "array", items };
|
|
@@ -18400,14 +18631,14 @@ function parse6(text2) {
|
|
|
18400
18631
|
}
|
|
18401
18632
|
numberPattern.lastIndex = at;
|
|
18402
18633
|
const number = numberPattern.exec(text2);
|
|
18403
|
-
|
|
18634
|
+
need6(number, "contains invalid JSON");
|
|
18404
18635
|
at += number[0].length;
|
|
18405
18636
|
return { kind: "number", value: number[0] };
|
|
18406
18637
|
}
|
|
18407
18638
|
const root2 = value(0);
|
|
18408
18639
|
space();
|
|
18409
|
-
|
|
18410
|
-
|
|
18640
|
+
need6(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
|
|
18641
|
+
need6(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
|
|
18411
18642
|
return root2;
|
|
18412
18643
|
}
|
|
18413
18644
|
function exactKeys(value, keys) {
|
|
@@ -18415,7 +18646,7 @@ function exactKeys(value, keys) {
|
|
|
18415
18646
|
}
|
|
18416
18647
|
var field = (object5, key) => object5.entries.find(([name]) => name === key)?.[1];
|
|
18417
18648
|
function textField(value) {
|
|
18418
|
-
|
|
18649
|
+
need6(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
18419
18650
|
return value.value;
|
|
18420
18651
|
}
|
|
18421
18652
|
function normalizeRow(row, version = 1) {
|
|
@@ -18423,7 +18654,7 @@ function normalizeRow(row, version = 1) {
|
|
|
18423
18654
|
if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
18424
18655
|
return;
|
|
18425
18656
|
const autoUpdate = field(row, "autoUpdate");
|
|
18426
|
-
|
|
18657
|
+
need6(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
18427
18658
|
}
|
|
18428
18659
|
const source = field(row, "source");
|
|
18429
18660
|
if (source?.kind !== "object")
|
|
@@ -18439,77 +18670,29 @@ function normalizeRow(row, version = 1) {
|
|
|
18439
18670
|
absolutePath3(sourceLocation);
|
|
18440
18671
|
absolutePath3(textField(field(row, "installLocation")));
|
|
18441
18672
|
const updated = textField(field(row, "lastUpdated"));
|
|
18442
|
-
|
|
18673
|
+
need6(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
|
|
18443
18674
|
const millis = Date.parse(updated);
|
|
18444
|
-
|
|
18675
|
+
need6(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
|
|
18445
18676
|
row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
|
|
18446
18677
|
}
|
|
18447
18678
|
function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
18448
18679
|
const value = parse6(readRegistry2(path, budget));
|
|
18449
18680
|
for (const [, row] of value.entries)
|
|
18450
18681
|
normalizeRow(row);
|
|
18451
|
-
const sha2562 =
|
|
18682
|
+
const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
18452
18683
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
18453
18684
|
}
|
|
18454
18685
|
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
18455
18686
|
const value = parse6(readRegistry2(path, budget));
|
|
18456
18687
|
for (const [, row] of value.entries)
|
|
18457
18688
|
normalizeRow(row, 2);
|
|
18458
|
-
const sha2562 =
|
|
18689
|
+
const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
18459
18690
|
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
18460
18691
|
}
|
|
18461
18692
|
|
|
18462
18693
|
// src/lib/codex-hook-discovery-recovery.ts
|
|
18463
18694
|
import { createHash as createHash17 } from "crypto";
|
|
18464
18695
|
|
|
18465
|
-
// src/lib/codex-hook-trust-files.ts
|
|
18466
|
-
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
18467
|
-
import { createHash as createHash16 } from "crypto";
|
|
18468
|
-
import { dirname as dirname16 } from "path";
|
|
18469
|
-
function need6(value, code) {
|
|
18470
|
-
if (!value)
|
|
18471
|
-
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
18472
|
-
}
|
|
18473
|
-
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
18474
|
-
function safeParents(file) {
|
|
18475
|
-
for (let p = dirname16(file);; p = dirname16(p)) {
|
|
18476
|
-
const s = lstatSync21(p);
|
|
18477
|
-
need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
18478
|
-
need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
18479
|
-
if (dirname16(p) === p)
|
|
18480
|
-
break;
|
|
18481
|
-
}
|
|
18482
|
-
}
|
|
18483
|
-
function snapshot(file, privateFile = false, options = {}) {
|
|
18484
|
-
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
18485
|
-
safeParents(file);
|
|
18486
|
-
const initial = lstatSync21(file, { bigint: true });
|
|
18487
|
-
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");
|
|
18488
|
-
need6(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
18489
|
-
const fd = openSync12(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
18490
|
-
try {
|
|
18491
|
-
const before = fstatSync12(fd, { bigint: true });
|
|
18492
|
-
need6(sameStat(initial, before), "INPUT_CHANGED");
|
|
18493
|
-
const bytes = Buffer.alloc(Number(before.size) + 1);
|
|
18494
|
-
let length = 0;
|
|
18495
|
-
while (length < bytes.length) {
|
|
18496
|
-
const n2 = readSync10(fd, bytes, length, bytes.length - length, null);
|
|
18497
|
-
if (!n2)
|
|
18498
|
-
break;
|
|
18499
|
-
length += n2;
|
|
18500
|
-
}
|
|
18501
|
-
need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync21(file, { bigint: true })), "INPUT_CHANGED");
|
|
18502
|
-
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
18503
|
-
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash16("sha256").update(content).digest("hex") };
|
|
18504
|
-
} finally {
|
|
18505
|
-
closeSync12(fd);
|
|
18506
|
-
}
|
|
18507
|
-
}
|
|
18508
|
-
function unchanged2(before) {
|
|
18509
|
-
const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
18510
|
-
need6(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
|
|
18511
|
-
}
|
|
18512
|
-
|
|
18513
18696
|
// src/lib/codex-hook-additions-review.ts
|
|
18514
18697
|
var events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
|
|
18515
18698
|
var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
|
|
@@ -18521,7 +18704,7 @@ var fields = [...CODEX_DISCOVERY_PROJECTION_FIELDS];
|
|
|
18521
18704
|
var proofs2 = new WeakMap;
|
|
18522
18705
|
function assertCodexHookDiscoveryRecovery(binding, witness) {
|
|
18523
18706
|
const proof = proofs2.get(witness);
|
|
18524
|
-
|
|
18707
|
+
need5(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
18525
18708
|
unchanged2(proof.before);
|
|
18526
18709
|
unchanged2(proof.current);
|
|
18527
18710
|
proof.recheckAdditions();
|
|
@@ -18530,7 +18713,7 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
|
|
|
18530
18713
|
if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
|
|
18531
18714
|
return false;
|
|
18532
18715
|
assertCodexHookDiscoveryRecovery(binding, witness);
|
|
18533
|
-
|
|
18716
|
+
need5(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
18534
18717
|
return true;
|
|
18535
18718
|
}
|
|
18536
18719
|
|
|
@@ -45935,6 +46118,7 @@ export {
|
|
|
45935
46118
|
planPluginAdmission,
|
|
45936
46119
|
planClaudeStopHookUpdate,
|
|
45937
46120
|
planClaudePreToolUseHookUpdate,
|
|
46121
|
+
planClaudeManagedHookProjection,
|
|
45938
46122
|
planClaudeHookEventsUpdate,
|
|
45939
46123
|
planAgentSettingsWitnessUpgrade,
|
|
45940
46124
|
planAgentIntegration,
|