@hasna/skills 0.10.34 → 0.10.36
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/README.md +6 -0
- package/bin/index.js +558 -338
- 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 +413 -219
- package/dist/lib/agent-integration.d.ts +27 -0
- package/dist/lib/codex-plugin-skill-controls.d.ts +1 -1
- package/dist/sdk/index.js +204 -11
- package/package.json +1 -1
|
@@ -133,6 +133,33 @@ export declare function planAgentIntegration(options: {
|
|
|
133
133
|
reviewedCacheAlias?: string;
|
|
134
134
|
codexNativeCatalog?: CodexNativeSkillCatalog;
|
|
135
135
|
}): AgentIntegrationPlan;
|
|
136
|
+
export interface ClaudeManagedHookProjection {
|
|
137
|
+
schema: "hasna.claude-managed-hook-projection.v1";
|
|
138
|
+
targetSha256: string;
|
|
139
|
+
sourceSha256: string;
|
|
140
|
+
replacements: Array<{
|
|
141
|
+
event: string;
|
|
142
|
+
groupIndex: number;
|
|
143
|
+
hookIndex: number;
|
|
144
|
+
beforeSha256: string;
|
|
145
|
+
command: string;
|
|
146
|
+
}>;
|
|
147
|
+
}
|
|
148
|
+
export interface ClaudeManagedHookProjectionOptions {
|
|
149
|
+
home?: string;
|
|
150
|
+
dataDir?: string;
|
|
151
|
+
projectDir?: string;
|
|
152
|
+
/** Exact caller-owned bytes, never a new native home or a path to be written. */
|
|
153
|
+
targetSettings: string;
|
|
154
|
+
expectedTargetSha256: string;
|
|
155
|
+
/** Supply the preceding plan's source witness when checking it before use. */
|
|
156
|
+
expectedSourceSha256?: string;
|
|
157
|
+
}
|
|
158
|
+
/** Read-only projection for a copied Claude configuration. This does not install
|
|
159
|
+
* hooks, adopt target discovery, change policy, or confer write/admission authority.
|
|
160
|
+
* The caller must preserve the full target, hold its owning writer lease, replan
|
|
161
|
+
* against both witnesses, CAS the full bytes and revalidate after application. */
|
|
162
|
+
export declare function planClaudeManagedHookProjection(options: ClaudeManagedHookProjectionOptions): ClaudeManagedHookProjection;
|
|
136
163
|
/** Plan an explicitly authorized Stop hook replacement without adopting drift.
|
|
137
164
|
* The caller owns authorization of its exact Stop hook delta; Skills refuses
|
|
138
165
|
* every other settings change and owns discovery witnesses and guarded writes.
|
|
@@ -28,7 +28,7 @@ export interface CodexPluginSourceInput {
|
|
|
28
28
|
type Read = (path: string) => string;
|
|
29
29
|
/** Capability controls remain bound even after every native Skill body disappears. */
|
|
30
30
|
export declare function reviewedCodexPluginCapabilitiesUnchanged(cache: string, controls: CodexPluginSkillControl[], read: Read): boolean;
|
|
31
|
-
export declare function reviewCodexPluginControls(catalog: CodexNativeSkillCatalog, documents: string[], cache: string, cwd: string, read: Read, rules: unknown, pluginSettings?: unknown): {
|
|
31
|
+
export declare function reviewCodexPluginControls(catalog: CodexNativeSkillCatalog, documents: string[], cache: string, cwd: string, read: Read, rules: unknown, pluginSettings?: unknown, plannedRules?: unknown): {
|
|
32
32
|
skills: CodexPluginSkillControl[];
|
|
33
33
|
inactivePlugins: CodexInactivePluginControl[];
|
|
34
34
|
sourceInputs: CodexPluginSourceInput[];
|
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.10.
|
|
32376
|
+
version: "0.10.36",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -38659,7 +38659,7 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read) {
|
|
|
38659
38659
|
return false;
|
|
38660
38660
|
}
|
|
38661
38661
|
}
|
|
38662
|
-
function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules, pluginSettings) {
|
|
38662
|
+
function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules, pluginSettings, plannedRules) {
|
|
38663
38663
|
if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
|
|
38664
38664
|
refuse3();
|
|
38665
38665
|
const skills = projectCodexNativeSkillCatalog({ data: [{ cwd, errors: [], skills: catalog.skills }] }, cwd);
|
|
@@ -38730,7 +38730,9 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
|
|
|
38730
38730
|
remotePluginId = plugin.remotePluginId;
|
|
38731
38731
|
}
|
|
38732
38732
|
const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document && rule?.enabled === false);
|
|
38733
|
-
|
|
38733
|
+
const nativeSkill = { name: parsed.name, path: document, pluginId: expectedPluginId, enabled: true };
|
|
38734
|
+
const plannedPathDeny = catalog.version === "codex-cli 0.160.0" && remotePluginId !== undefined && Array.isArray(rules) && rules.some((rule) => typeof rule?.name === "string" && rule.name.trim() === parsed.name && rule.enabled === false) && !rules.some((rule) => (rule?.path === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
|
|
38735
|
+
if (!exactPathDeny && !plannedPathDeny || !isCodexNativeSkillDisabled(nativeSkill, rules))
|
|
38734
38736
|
refuse3();
|
|
38735
38737
|
}
|
|
38736
38738
|
result.push({ ...parsed, pluginId: expectedPluginId, ...remotePluginId ? { remotePluginId } : {} });
|
|
@@ -42695,12 +42697,15 @@ function disableCodexBundledSkills(text2) {
|
|
|
42695
42697
|
// src/lib/agent-integration.ts
|
|
42696
42698
|
var sha4 = (data) => createHash18("sha256").update(data).digest("hex");
|
|
42697
42699
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
42698
|
-
function
|
|
42700
|
+
function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
|
|
42699
42701
|
try {
|
|
42700
|
-
const { dlopen, read: read3 } = __require("bun:ffi");
|
|
42702
|
+
const { dlopen, read: read3, ptr } = __require("bun:ffi");
|
|
42701
42703
|
const library = dlopen("/usr/lib/libSystem.B.dylib", {
|
|
42702
42704
|
acl_get_fd: { args: ["i32"], returns: "ptr" },
|
|
42703
42705
|
acl_free: { args: ["ptr"], returns: "i32" },
|
|
42706
|
+
acl_valid: { args: ["ptr"], returns: "i32" },
|
|
42707
|
+
acl_get_entry: { args: ["ptr", "i32", "ptr"], returns: "i32" },
|
|
42708
|
+
acl_get_tag_type: { args: ["ptr", "ptr"], returns: "i32" },
|
|
42704
42709
|
__error: { args: [], returns: "ptr" }
|
|
42705
42710
|
});
|
|
42706
42711
|
try {
|
|
@@ -42709,8 +42714,23 @@ function hasNoDarwinAcl(fd) {
|
|
|
42709
42714
|
return false;
|
|
42710
42715
|
const acl = library.symbols.acl_get_fd(fd);
|
|
42711
42716
|
if (acl) {
|
|
42712
|
-
|
|
42713
|
-
|
|
42717
|
+
try {
|
|
42718
|
+
if (!allowDenyOnly || library.symbols.acl_valid(acl) !== 0)
|
|
42719
|
+
return false;
|
|
42720
|
+
const entry = new BigUint64Array(1), tag = new Int32Array(1);
|
|
42721
|
+
for (let count = 0;count <= 128; count++) {
|
|
42722
|
+
const status = library.symbols.acl_get_entry(acl, count === 0 ? 0 : -1, ptr(entry));
|
|
42723
|
+
if (status !== 0) {
|
|
42724
|
+
return status === -1 && read3.i32(errno) === 22 && library.symbols.acl_valid(acl) === 0;
|
|
42725
|
+
}
|
|
42726
|
+
const value = read3.ptr(ptr(entry));
|
|
42727
|
+
if (count === 128 || !value || library.symbols.acl_get_tag_type(value, ptr(tag)) !== 0 || tag[0] !== 2)
|
|
42728
|
+
return false;
|
|
42729
|
+
}
|
|
42730
|
+
return false;
|
|
42731
|
+
} finally {
|
|
42732
|
+
library.symbols.acl_free(acl);
|
|
42733
|
+
}
|
|
42714
42734
|
}
|
|
42715
42735
|
return (acl === null || acl === 0) && read3.i32(errno) === 2;
|
|
42716
42736
|
} finally {
|
|
@@ -42740,7 +42760,7 @@ function isSystemRootAlias(path) {
|
|
|
42740
42760
|
throw new Error("Unprotected system temporary directory");
|
|
42741
42761
|
const fd = openSync16(directory, constants12.O_RDONLY | constants12.O_DIRECTORY | constants12.O_NOFOLLOW);
|
|
42742
42762
|
try {
|
|
42743
|
-
if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2) || !
|
|
42763
|
+
if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2) || !hasTrustedDarwinAcl(fd) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2))
|
|
42744
42764
|
throw new Error("Unverified system alias directory ACL");
|
|
42745
42765
|
} finally {
|
|
42746
42766
|
closeSync16(fd);
|
|
@@ -43269,15 +43289,21 @@ function planAgentIntegration(options) {
|
|
|
43269
43289
|
}
|
|
43270
43290
|
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
43271
43291
|
});
|
|
43272
|
-
const
|
|
43292
|
+
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join23(home, ".codex", "config.toml"), aliases)) : null;
|
|
43293
|
+
const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
|
|
43273
43294
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
|
|
43295
|
+
const codexPathConfig = options.agents.includes("codex") ? disableCodexSkills(codexConfigBefore ?? "", nativeSkills, aliases, canonicalAgentPath(join23(home, ".codex/skills", CLI_BRIDGE_NAME), aliases)) : undefined;
|
|
43296
|
+
const plannedCodexRules = codexPathConfig === undefined ? undefined : Bun.TOML.parse(codexPathConfig).skills?.config ?? [];
|
|
43274
43297
|
const observedNativeSources = [];
|
|
43275
43298
|
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join23(entry.path, "SKILL.md")), canonicalAgentPath(join23(home, ".codex/plugins/cache"), aliases), resolve16(options.projectDir ?? home), (path) => {
|
|
43276
43299
|
const bytes = readNativeBytes(path, 1024 * 1024);
|
|
43277
43300
|
observedNativeSources.push({ path, sha256: sha4(bytes) });
|
|
43278
43301
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
43279
|
-
}, codexConfig.skills?.config ?? [], codexConfig.plugins) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
|
|
43302
|
+
}, codexConfig.skills?.config ?? [], codexConfig.plugins, plannedCodexRules) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
|
|
43280
43303
|
const codexPluginSkills = codexPluginControls.skills;
|
|
43304
|
+
if (options.agents.includes("codex") && !options.codexNativeCatalog && codexPluginSkills.length && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join23(home, ".codex/plugins/cache"), aliases), codexPluginSkills, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)))) {
|
|
43305
|
+
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; provide a fresh native catalog and discovery review");
|
|
43306
|
+
}
|
|
43281
43307
|
const codexInactivePlugins = codexPluginControls.inactivePlugins;
|
|
43282
43308
|
const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
|
|
43283
43309
|
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root2 + sep5)) && codexDiscovery.sources.some((source) => source.path === join23(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
|
|
@@ -43352,7 +43378,9 @@ function planAgentIntegration(options) {
|
|
|
43352
43378
|
changes.push({ path, before, after });
|
|
43353
43379
|
if (agent === "codex") {
|
|
43354
43380
|
const configPath = canonicalAgentPath(join23(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
|
|
43355
|
-
|
|
43381
|
+
if (previous !== codexConfigBefore)
|
|
43382
|
+
throw new Error(`Configuration changed during planning: ${configPath}`);
|
|
43383
|
+
const next = disableReviewedCodexPluginNames(codexPathConfig, codexPluginSkills);
|
|
43356
43384
|
if (next !== (previous ?? ""))
|
|
43357
43385
|
changes.push({ path: configPath, before: previous, after: next });
|
|
43358
43386
|
}
|
|
@@ -43388,6 +43416,171 @@ function planAgentIntegration(options) {
|
|
|
43388
43416
|
recheckRootAliases(aliases);
|
|
43389
43417
|
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 } } : {} };
|
|
43390
43418
|
}
|
|
43419
|
+
function hookProjectionRefusal(reason) {
|
|
43420
|
+
throw new Error(`CLAUDE_MANAGED_HOOK_${reason}`);
|
|
43421
|
+
}
|
|
43422
|
+
function projectedHookAcl(path, observed) {
|
|
43423
|
+
if (process.platform !== "darwin")
|
|
43424
|
+
return;
|
|
43425
|
+
const identity4 = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}:${stat2.nlink}:${stat2.size}:${stat2.mtimeNs}`;
|
|
43426
|
+
const fd = openSync16(path, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK | (observed.isDirectory() ? constants12.O_DIRECTORY : 0));
|
|
43427
|
+
try {
|
|
43428
|
+
if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(observed) || !hasTrustedDarwinAcl(fd, true) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(observed) || identity4(lstatSync20(path, { bigint: true })) !== identity4(observed))
|
|
43429
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43430
|
+
} finally {
|
|
43431
|
+
closeSync16(fd);
|
|
43432
|
+
}
|
|
43433
|
+
}
|
|
43434
|
+
function projectedHookExecutable(command, expectedResolved) {
|
|
43435
|
+
try {
|
|
43436
|
+
if (!isAbsolute17(command) || /[\0\r\n]/.test(command) || resolve16(command) !== command)
|
|
43437
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43438
|
+
command = canonicalSystemPath(command);
|
|
43439
|
+
const parents3 = (file = command) => {
|
|
43440
|
+
const result = [];
|
|
43441
|
+
for (let path = dirname15(file);; path = dirname15(path)) {
|
|
43442
|
+
const stat2 = lstatSync20(path, { bigint: true });
|
|
43443
|
+
if (!stat2.isDirectory() || (stat2.mode & 0o022n) !== 0n && !(stat2.uid === 0n && (stat2.mode & 0o1000n) !== 0n))
|
|
43444
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43445
|
+
projectedHookAcl(path, stat2);
|
|
43446
|
+
result.push(`${path}:${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}`);
|
|
43447
|
+
if (dirname15(path) === path)
|
|
43448
|
+
break;
|
|
43449
|
+
}
|
|
43450
|
+
return result;
|
|
43451
|
+
};
|
|
43452
|
+
const parentIdentity = parents3(), link = lstatSync20(command, { bigint: true });
|
|
43453
|
+
if (link.uid !== BigInt(process.getuid()) || !link.isFile() && !link.isSymbolicLink())
|
|
43454
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43455
|
+
const linkIdentity = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.mode}:${stat2.uid}:${stat2.ctimeNs}`;
|
|
43456
|
+
const resolved = realpathSync8(command);
|
|
43457
|
+
if (expectedResolved !== undefined && resolved !== expectedResolved)
|
|
43458
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43459
|
+
const resolvedParentIdentity = parents3(resolved);
|
|
43460
|
+
const manifest = snapshot(join23(dirname15(dirname15(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
43461
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
43462
|
+
const pkg = JSON.parse(manifest.text);
|
|
43463
|
+
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join23(dirname15(manifest.file), "bin/index.js"))
|
|
43464
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43465
|
+
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
43466
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
43467
|
+
if ((cli.stat.mode & 0o100n) === 0n)
|
|
43468
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43469
|
+
const recheck = () => {
|
|
43470
|
+
try {
|
|
43471
|
+
if (realpathSync8(command) !== resolved || linkIdentity(lstatSync20(command, { bigint: true })) !== linkIdentity(link) || !isDeepStrictEqual6(parents3(), parentIdentity) || !isDeepStrictEqual6(parents3(resolved), resolvedParentIdentity))
|
|
43472
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43473
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
43474
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
43475
|
+
unchanged2(cli);
|
|
43476
|
+
unchanged2(manifest);
|
|
43477
|
+
} catch {
|
|
43478
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43479
|
+
}
|
|
43480
|
+
};
|
|
43481
|
+
recheck();
|
|
43482
|
+
return { resolved, sha256: cli.sha256, manifestSha256: manifest.sha256, recheck };
|
|
43483
|
+
} catch {
|
|
43484
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43485
|
+
}
|
|
43486
|
+
}
|
|
43487
|
+
function planClaudeManagedHookProjection(options) {
|
|
43488
|
+
if (typeof options.targetSettings !== "string" || Buffer.byteLength(options.targetSettings) > 1024 * 1024)
|
|
43489
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43490
|
+
const targetSha256 = sha4(options.targetSettings);
|
|
43491
|
+
if (!/^[a-f0-9]{64}$/.test(options.expectedTargetSha256) || options.expectedTargetSha256 !== targetSha256)
|
|
43492
|
+
hookProjectionRefusal("TARGET_CHANGED");
|
|
43493
|
+
let target;
|
|
43494
|
+
try {
|
|
43495
|
+
target = jsonObject(options.targetSettings, "target");
|
|
43496
|
+
} catch {
|
|
43497
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43498
|
+
}
|
|
43499
|
+
const home = resolve16(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
43500
|
+
const policyPath = join23(dataDir2, "agent-policy.json");
|
|
43501
|
+
let policyBytes, settingsBytes, settingsPath, command, profile;
|
|
43502
|
+
try {
|
|
43503
|
+
policyBytes = readNativeBytes(policyPath, 1024 * 1024);
|
|
43504
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
43505
|
+
if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
|
|
43506
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
43507
|
+
const binding = snapshot2.value.bridge;
|
|
43508
|
+
settingsPath = canonicalAgentPath(join23(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
|
|
43509
|
+
settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
|
|
43510
|
+
command = binding?.commands?.claude;
|
|
43511
|
+
profile = binding?.profiles?.claude;
|
|
43512
|
+
if (typeof command !== "string" || typeof profile !== "string")
|
|
43513
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
43514
|
+
assertManagedAgentBridge("claude", { home, dataDir: dataDir2, projectDir: options.projectDir, profileId: profile });
|
|
43515
|
+
} catch {
|
|
43516
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
43517
|
+
}
|
|
43518
|
+
const executable = projectedHookExecutable(command);
|
|
43519
|
+
const sourceSha256 = sha4(JSON.stringify({
|
|
43520
|
+
home,
|
|
43521
|
+
policyPath,
|
|
43522
|
+
policy: sha4(policyBytes),
|
|
43523
|
+
settingsPath,
|
|
43524
|
+
settings: sha4(settingsBytes),
|
|
43525
|
+
executable: { path: executable.resolved, sha256: executable.sha256, manifestSha256: executable.manifestSha256 }
|
|
43526
|
+
}));
|
|
43527
|
+
if (options.expectedSourceSha256 !== undefined && options.expectedSourceSha256 !== sourceSha256)
|
|
43528
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
43529
|
+
const replacements = [];
|
|
43530
|
+
const observed = [executable];
|
|
43531
|
+
if (target.hooks !== undefined) {
|
|
43532
|
+
if (!target.hooks || typeof target.hooks !== "object" || Array.isArray(target.hooks))
|
|
43533
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43534
|
+
for (const event of [...AGENT_ADAPTERS.claude.events, "PreToolUse"]) {
|
|
43535
|
+
const groups = target.hooks[event];
|
|
43536
|
+
if (groups === undefined)
|
|
43537
|
+
continue;
|
|
43538
|
+
if (!Array.isArray(groups))
|
|
43539
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43540
|
+
let recognized = 0;
|
|
43541
|
+
for (const [groupIndex, group] of groups.entries()) {
|
|
43542
|
+
if (!group || !Array.isArray(group.hooks))
|
|
43543
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43544
|
+
for (const [hookIndex, hook] of group.hooks.entries()) {
|
|
43545
|
+
if (hook?.type !== "command" || typeof hook.command !== "string")
|
|
43546
|
+
continue;
|
|
43547
|
+
if (!hook.command.includes("hook user-prompt --agent claude"))
|
|
43548
|
+
continue;
|
|
43549
|
+
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);
|
|
43550
|
+
if (!match || match[3] !== event)
|
|
43551
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
43552
|
+
const oldCommand = match[1].slice(1, -1).replaceAll("'\\''", "'");
|
|
43553
|
+
let rendered;
|
|
43554
|
+
try {
|
|
43555
|
+
rendered = renderAgentHookCommand(oldCommand, "claude", match[2], event);
|
|
43556
|
+
} catch {
|
|
43557
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
43558
|
+
}
|
|
43559
|
+
if (rendered !== hook.command)
|
|
43560
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
43561
|
+
const legacy = projectedHookExecutable(oldCommand, executable.resolved);
|
|
43562
|
+
if (legacy.resolved !== executable.resolved || legacy.sha256 !== executable.sha256 || legacy.manifestSha256 !== executable.manifestSha256)
|
|
43563
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43564
|
+
observed.push(legacy);
|
|
43565
|
+
if (++recognized > 1)
|
|
43566
|
+
hookProjectionRefusal("AMBIGUOUS");
|
|
43567
|
+
const replacement = renderAgentHookCommand(command, "claude", profile, event);
|
|
43568
|
+
if (replacement !== hook.command)
|
|
43569
|
+
replacements.push({ event, groupIndex, hookIndex, beforeSha256: sha4(hook.command), command: replacement });
|
|
43570
|
+
}
|
|
43571
|
+
}
|
|
43572
|
+
}
|
|
43573
|
+
}
|
|
43574
|
+
try {
|
|
43575
|
+
if (!readNativeBytes(policyPath, 1024 * 1024).equals(policyBytes) || !readNativeBytes(settingsPath, 1024 * 1024).equals(settingsBytes))
|
|
43576
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
43577
|
+
} catch {
|
|
43578
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
43579
|
+
}
|
|
43580
|
+
for (const item of observed)
|
|
43581
|
+
item.recheck();
|
|
43582
|
+
return { schema: "hasna.claude-managed-hook-projection.v1", targetSha256, sourceSha256, replacements };
|
|
43583
|
+
}
|
|
43391
43584
|
function planClaudeStopHookUpdate(options) {
|
|
43392
43585
|
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
43393
43586
|
}
|