@hasna/skills 0.9.13 → 0.9.14
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 +28 -6
- package/bin/mcp.js +2 -2
- 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 +80 -25
- package/dist/lib/agent-integration.d.ts +9 -1
- package/dist/lib/claude-settings-witness.d.ts +5 -1
- package/dist/sdk/index.js +77 -25
- package/docs/plugin-admission.md +27 -3
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36868,7 +36868,7 @@ var package_default;
|
|
|
36868
36868
|
var init_package = __esm(() => {
|
|
36869
36869
|
package_default = {
|
|
36870
36870
|
name: "@hasna/skills",
|
|
36871
|
-
version: "0.9.
|
|
36871
|
+
version: "0.9.14",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -50548,7 +50548,7 @@ function canonical2(value) {
|
|
|
50548
50548
|
function inferencePreference(key, child) {
|
|
50549
50549
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
50550
50550
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
50551
|
-
return
|
|
50551
|
+
return false;
|
|
50552
50552
|
}
|
|
50553
50553
|
if (key === "effortLevel") {
|
|
50554
50554
|
need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
|
|
@@ -51233,6 +51233,20 @@ function planAgentIntegration(options) {
|
|
|
51233
51233
|
recheckRootAliases(aliases);
|
|
51234
51234
|
return { dataDir: dataDir3, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
51235
51235
|
}
|
|
51236
|
+
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
51237
|
+
try {
|
|
51238
|
+
if (binding.method !== "automatic" && binding.method !== "reviewed")
|
|
51239
|
+
throw new Error("Invalid managed discovery method");
|
|
51240
|
+
verifyAgentDiscovery(binding);
|
|
51241
|
+
if (binding.method === "automatic") {
|
|
51242
|
+
const current = resolveAgentDiscovery({ home, agent: binding.agent, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
51243
|
+
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
51244
|
+
throw new Error("Configured native discovery roots changed during Claude update");
|
|
51245
|
+
}
|
|
51246
|
+
} catch (error) {
|
|
51247
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
51248
|
+
}
|
|
51249
|
+
}
|
|
51236
51250
|
function atomicWrite2(path, content) {
|
|
51237
51251
|
assertSafePath(path);
|
|
51238
51252
|
mkdirSync5(dirname10(path), { recursive: true, mode: 448 });
|
|
@@ -51257,8 +51271,12 @@ function applyAgentIntegration(plan) {
|
|
|
51257
51271
|
recheckRootAliases(aliases);
|
|
51258
51272
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
51259
51273
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
51260
|
-
for (const binding of plan.discoveryBefore ?? [])
|
|
51261
|
-
|
|
51274
|
+
for (const binding of plan.discoveryBefore ?? []) {
|
|
51275
|
+
if (plan.managedAgentChecks)
|
|
51276
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
51277
|
+
else
|
|
51278
|
+
verifyAgentDiscovery(binding);
|
|
51279
|
+
}
|
|
51262
51280
|
if (plan.observedPolicy && currentText(plan.observedPolicy.path) !== plan.observedPolicy.before)
|
|
51263
51281
|
throw new Error("Agent policy changed after planning");
|
|
51264
51282
|
for (const change of plan.changes)
|
|
@@ -51290,8 +51308,12 @@ function applyAgentIntegration(plan) {
|
|
|
51290
51308
|
written.push({ path: change.path, before: change.before, after: after2 });
|
|
51291
51309
|
}
|
|
51292
51310
|
recheckRootAliases(aliases);
|
|
51293
|
-
for (const binding of plan.discoveryAfter ?? [])
|
|
51294
|
-
|
|
51311
|
+
for (const binding of plan.discoveryAfter ?? []) {
|
|
51312
|
+
if (plan.managedAgentChecks)
|
|
51313
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
51314
|
+
else
|
|
51315
|
+
verifyAgentDiscovery(binding);
|
|
51316
|
+
}
|
|
51295
51317
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
51296
51318
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
51297
51319
|
atomicWrite2(join20(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha(change.before), afterHash: sha(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
package/bin/mcp.js
CHANGED
|
@@ -20019,7 +20019,7 @@ var package_default;
|
|
|
20019
20019
|
var init_package = __esm(() => {
|
|
20020
20020
|
package_default = {
|
|
20021
20021
|
name: "@hasna/skills",
|
|
20022
|
-
version: "0.9.
|
|
20022
|
+
version: "0.9.14",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
|
@@ -47083,7 +47083,7 @@ function canonical2(value) {
|
|
|
47083
47083
|
function inferencePreference(key, child) {
|
|
47084
47084
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
47085
47085
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
47086
|
-
return
|
|
47086
|
+
return false;
|
|
47087
47087
|
}
|
|
47088
47088
|
if (key === "effortLevel") {
|
|
47089
47089
|
need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
24122
24122
|
// package.json
|
|
24123
24123
|
var package_default = {
|
|
24124
24124
|
name: "@hasna/skills",
|
|
24125
|
-
version: "0.9.
|
|
24125
|
+
version: "0.9.14",
|
|
24126
24126
|
description: "Skills library for AI coding agents",
|
|
24127
24127
|
type: "module",
|
|
24128
24128
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
24125
24125
|
// package.json
|
|
24126
24126
|
var package_default = {
|
|
24127
24127
|
name: "@hasna/skills",
|
|
24128
|
-
version: "0.9.
|
|
24128
|
+
version: "0.9.14",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
package/dist/index.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export type { SkillSelection, SkillProfile, ResolvedSkillSelection, ResolvedSkil
|
|
|
60
60
|
export { buildSkillContext } from "./lib/skill-context.js";
|
|
61
61
|
export { syncSelectionProfile } from "./lib/selection-resolver.js";
|
|
62
62
|
export { inspectSkillSession, reconcileSkillSession, type SessionReconciliationInput, type SessionReconciliationOptions } from "./lib/session-reconciliation.js";
|
|
63
|
-
export { planAgentIntegration, planClaudeStopHookUpdate, applyAgentIntegration, inventoryNativeSkills, archiveNativeSkills } from "./lib/agent-integration.js";
|
|
63
|
+
export { planAgentIntegration, planClaudeStopHookUpdate, planClaudePreToolUseHookUpdate, planClaudeHookEventsUpdate, applyAgentIntegration, inventoryNativeSkills, archiveNativeSkills } from "./lib/agent-integration.js";
|
|
64
64
|
export { captureDiscoveryDirectories, captureDiscoveryByteSources, captureDiscoveryPathSources, type DiscoveryDirectory, type DiscoverySource } from "./lib/agent-discovery.js";
|
|
65
65
|
export { captureClaudeMarketplaceRegistry, captureClaudeMarketplaceRegistryV2 } from "./lib/claude-marketplace-registry.js";
|
|
66
66
|
export { captureClaudeSettings, captureClaudeSettingsV2 } from "./lib/claude-settings-witness.js";
|
|
@@ -69,3 +69,4 @@ export { planPluginAdmission, admitPlugin, resolveAdmittedPlugin, pluginResolver
|
|
|
69
69
|
export { captureManagedPluginRegistry, type ManagedPluginRegistrationWitness } from "./lib/plugin-discovery.js";
|
|
70
70
|
export type { RemoteCreditCheckoutOptions, RemoteCreditCheckout, RemoteCreditCheckoutErrorCode } from "./lib/remote-credit-checkout.js";
|
|
71
71
|
export { assertPortableAuthoringPath, type PortableAuthoringPathOptions } from "./lib/authoring-path.js";
|
|
72
|
+
export { CLAUDE_COORDINATED_HOOK_EVENTS, type ClaudeCoordinatedHookEvent } from "./lib/claude-settings-witness.js";
|
package/dist/index.js
CHANGED
|
@@ -9610,7 +9610,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
|
|
|
9610
9610
|
// package.json
|
|
9611
9611
|
var package_default = {
|
|
9612
9612
|
name: "@hasna/skills",
|
|
9613
|
-
version: "0.9.
|
|
9613
|
+
version: "0.9.14",
|
|
9614
9614
|
description: "Skills library for AI coding agents",
|
|
9615
9615
|
type: "module",
|
|
9616
9616
|
bin: {
|
|
@@ -12846,32 +12846,37 @@ function canonical2(value) {
|
|
|
12846
12846
|
return { kind: "array", items: value.items.map(canonical2) };
|
|
12847
12847
|
return value;
|
|
12848
12848
|
}
|
|
12849
|
-
|
|
12849
|
+
var CLAUDE_COORDINATED_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "Stop", "Notification", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart"];
|
|
12850
|
+
function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
12851
|
+
need2(Array.isArray(events) && events.length > 0 && events.length <= CLAUDE_COORDINATED_HOOK_EVENTS.length && new Set(events).size === events.length && events.every((event) => CLAUDE_COORDINATED_HOOK_EVENTS.includes(event)), "requires explicit supported hook events");
|
|
12852
|
+
const label = events.join(",");
|
|
12850
12853
|
function bounded(text2) {
|
|
12851
|
-
need2(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes,
|
|
12854
|
+
need2(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, `${label} update exceeds its byte limit`);
|
|
12852
12855
|
return parse3(text2);
|
|
12853
12856
|
}
|
|
12854
12857
|
const original = bounded(before ?? "{}"), next = bounded(replacement);
|
|
12855
12858
|
const hadHooks = original.entries.some(([key]) => key === "hooks");
|
|
12856
|
-
function
|
|
12859
|
+
function withoutOwnedEvents(value) {
|
|
12857
12860
|
const hooks = value.entries.find(([key]) => key === "hooks")?.[1];
|
|
12858
12861
|
if (hooks !== undefined) {
|
|
12859
|
-
need2(hooks.kind === "object",
|
|
12860
|
-
const
|
|
12861
|
-
|
|
12862
|
-
|
|
12862
|
+
need2(hooks.kind === "object", `${label} update requires an object of hooks`);
|
|
12863
|
+
for (const event of events) {
|
|
12864
|
+
const selected = hooks.entries.find(([key]) => key === event)?.[1];
|
|
12865
|
+
need2(selected === undefined || selected.kind === "array", `${event} update requires a ${event} array`);
|
|
12866
|
+
}
|
|
12867
|
+
hooks.entries = hooks.entries.filter(([key]) => !events.includes(key));
|
|
12863
12868
|
if (!hadHooks && hooks.entries.length === 0)
|
|
12864
12869
|
value.entries = value.entries.filter(([key]) => key !== "hooks");
|
|
12865
12870
|
}
|
|
12866
12871
|
return canonical2(value);
|
|
12867
12872
|
}
|
|
12868
|
-
need2(JSON.stringify(
|
|
12873
|
+
need2(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
12869
12874
|
}
|
|
12870
12875
|
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
12871
12876
|
function inferencePreference(key, child) {
|
|
12872
12877
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
12873
12878
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
12874
|
-
return
|
|
12879
|
+
return false;
|
|
12875
12880
|
}
|
|
12876
12881
|
if (key === "effortLevel") {
|
|
12877
12882
|
need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
|
|
@@ -13452,6 +13457,34 @@ function planAgentIntegration(options) {
|
|
|
13452
13457
|
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
13453
13458
|
}
|
|
13454
13459
|
function planClaudeStopHookUpdate(options) {
|
|
13460
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
13461
|
+
}
|
|
13462
|
+
function planClaudePreToolUseHookUpdate(options) {
|
|
13463
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["PreToolUse"] });
|
|
13464
|
+
}
|
|
13465
|
+
function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
13466
|
+
const canonical3 = (value) => canonicalAgentPath(value, aliases);
|
|
13467
|
+
const containsSettings = (root) => {
|
|
13468
|
+
const distance = relative4(canonical3(root), path);
|
|
13469
|
+
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep4}`) && !isAbsolute9(distance);
|
|
13470
|
+
};
|
|
13471
|
+
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical3(join16(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical3(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
|
|
13472
|
+
}
|
|
13473
|
+
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
13474
|
+
try {
|
|
13475
|
+
if (binding.method !== "automatic" && binding.method !== "reviewed")
|
|
13476
|
+
throw new Error("Invalid managed discovery method");
|
|
13477
|
+
verifyAgentDiscovery(binding);
|
|
13478
|
+
if (binding.method === "automatic") {
|
|
13479
|
+
const current = resolveAgentDiscovery({ home, agent: binding.agent, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
13480
|
+
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
13481
|
+
throw new Error("Configured native discovery roots changed during Claude update");
|
|
13482
|
+
}
|
|
13483
|
+
} catch (error) {
|
|
13484
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
function planClaudeHookEventsUpdate(options) {
|
|
13455
13488
|
const home = resolve9(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
13456
13489
|
const policyPath = join16(dataDir2, "agent-policy.json");
|
|
13457
13490
|
assertSafePath(policyPath);
|
|
@@ -13469,31 +13502,42 @@ function planClaudeStopHookUpdate(options) {
|
|
|
13469
13502
|
const before = lstatSync14(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
13470
13503
|
if ((before === null ? "absent" : sha(before)) !== options.expectedSettingsSha256)
|
|
13471
13504
|
throw new Error("Claude settings preimage changed");
|
|
13472
|
-
|
|
13505
|
+
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
13473
13506
|
const agents = bridge.agents;
|
|
13474
13507
|
const discoveries = Object.entries(bridge.discovery).map(([agent, binding]) => {
|
|
13475
|
-
if (!agents.includes(agent) || binding?.agent !== agent)
|
|
13476
|
-
throw new Error("Invalid managed discovery owner");
|
|
13508
|
+
if (!agents.includes(agent) || binding?.agent !== agent || typeof bridge.commands?.[agent] !== "string" || typeof bridge.profiles?.[agent] !== "string")
|
|
13509
|
+
throw new Error("Invalid managed discovery owner or command/profile binding");
|
|
13477
13510
|
return binding;
|
|
13478
13511
|
});
|
|
13479
|
-
if (discoveries.length !== agents.length)
|
|
13480
|
-
throw new Error("Missing managed discovery coverage");
|
|
13481
|
-
for (const
|
|
13512
|
+
if (discoveries.length !== agents.length || !agents.includes("claude"))
|
|
13513
|
+
throw new Error("Missing managed Claude discovery coverage");
|
|
13514
|
+
for (const binding of discoveries)
|
|
13515
|
+
verifyCoordinatedDiscovery(binding, home, aliases);
|
|
13516
|
+
const consumers = claudeSettingsConsumers(home, path, discoveries, aliases);
|
|
13517
|
+
for (const agent of consumers)
|
|
13482
13518
|
assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: home });
|
|
13483
13519
|
const replacements = new Map(before === options.replacement ? [] : [[path, options.replacement]]);
|
|
13484
|
-
const discoveryAfter = discoveries.map((binding) =>
|
|
13520
|
+
const discoveryAfter = discoveries.map((binding) => {
|
|
13521
|
+
const next = rebindAgentDiscovery(binding, replacements);
|
|
13522
|
+
for (const [index, source] of binding.sources.entries()) {
|
|
13523
|
+
if (source.path !== path && JSON.stringify(next.sources[index]) !== JSON.stringify(source)) {
|
|
13524
|
+
throw new Error(`Unrelated native discovery input changed during Claude update: ${source.path}`);
|
|
13525
|
+
}
|
|
13526
|
+
}
|
|
13527
|
+
return JSON.stringify(next) === JSON.stringify(binding) ? binding : next;
|
|
13528
|
+
});
|
|
13485
13529
|
const nextPolicy = { ...policy, bridge: { ...bridge, discovery: Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) } };
|
|
13486
13530
|
const changes = before === options.replacement ? [] : [{ path, before, after: options.replacement }];
|
|
13487
13531
|
if (JSON.stringify(nextPolicy) !== JSON.stringify(policy))
|
|
13488
13532
|
changes.push({ path: policyPath, before: snapshot.text, after: serializeManagedSkillPolicy(nextPolicy) });
|
|
13489
13533
|
recheckRootAliases(aliases);
|
|
13490
13534
|
const nativeSkills = inventoryNativeSkills(home, {
|
|
13491
|
-
agents,
|
|
13535
|
+
agents: consumers,
|
|
13492
13536
|
projectDir: home,
|
|
13493
13537
|
includeVendor: true,
|
|
13494
|
-
guardHermes:
|
|
13538
|
+
guardHermes: consumers.includes("hermes"),
|
|
13495
13539
|
allowRootAliases: aliases.length > 0,
|
|
13496
|
-
agentRoots: discoveries.flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
13540
|
+
agentRoots: discoveries.filter((binding) => consumers.includes(binding.agent)).flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
13497
13541
|
});
|
|
13498
13542
|
return {
|
|
13499
13543
|
dataDir: dataDir2,
|
|
@@ -13504,7 +13548,7 @@ function planClaudeStopHookUpdate(options) {
|
|
|
13504
13548
|
discoveryBefore: discoveries,
|
|
13505
13549
|
discoveryAfter,
|
|
13506
13550
|
rootAliases: aliases,
|
|
13507
|
-
managedAgentChecks: { home, agents:
|
|
13551
|
+
managedAgentChecks: { home, agents: consumers }
|
|
13508
13552
|
};
|
|
13509
13553
|
}
|
|
13510
13554
|
function atomicWrite2(path, content) {
|
|
@@ -13531,8 +13575,12 @@ function applyAgentIntegration(plan) {
|
|
|
13531
13575
|
recheckRootAliases(aliases);
|
|
13532
13576
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
13533
13577
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
13534
|
-
for (const binding of plan.discoveryBefore ?? [])
|
|
13535
|
-
|
|
13578
|
+
for (const binding of plan.discoveryBefore ?? []) {
|
|
13579
|
+
if (plan.managedAgentChecks)
|
|
13580
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
13581
|
+
else
|
|
13582
|
+
verifyAgentDiscovery(binding);
|
|
13583
|
+
}
|
|
13536
13584
|
if (plan.observedPolicy && currentText(plan.observedPolicy.path) !== plan.observedPolicy.before)
|
|
13537
13585
|
throw new Error("Agent policy changed after planning");
|
|
13538
13586
|
for (const change of plan.changes)
|
|
@@ -13564,8 +13612,12 @@ function applyAgentIntegration(plan) {
|
|
|
13564
13612
|
written.push({ path: change.path, before: change.before, after });
|
|
13565
13613
|
}
|
|
13566
13614
|
recheckRootAliases(aliases);
|
|
13567
|
-
for (const binding of plan.discoveryAfter ?? [])
|
|
13568
|
-
|
|
13615
|
+
for (const binding of plan.discoveryAfter ?? []) {
|
|
13616
|
+
if (plan.managedAgentChecks)
|
|
13617
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
13618
|
+
else
|
|
13619
|
+
verifyAgentDiscovery(binding);
|
|
13620
|
+
}
|
|
13569
13621
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
13570
13622
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
13571
13623
|
atomicWrite2(join16(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha(change.before), afterHash: sha(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
@@ -43658,6 +43710,8 @@ export {
|
|
|
43658
43710
|
planSkillsS3SnapshotUpload,
|
|
43659
43711
|
planPluginAdmission,
|
|
43660
43712
|
planClaudeStopHookUpdate,
|
|
43713
|
+
planClaudePreToolUseHookUpdate,
|
|
43714
|
+
planClaudeHookEventsUpdate,
|
|
43661
43715
|
planAgentIntegration,
|
|
43662
43716
|
pinSkill,
|
|
43663
43717
|
pinProjectSkill,
|
|
@@ -43861,6 +43915,7 @@ export {
|
|
|
43861
43915
|
DEFAULT_EXPORT_DIR,
|
|
43862
43916
|
ContentHashInputError,
|
|
43863
43917
|
CONTENT_HASH_LIMITS,
|
|
43918
|
+
CLAUDE_COORDINATED_HOOK_EVENTS,
|
|
43864
43919
|
CATEGORIES,
|
|
43865
43920
|
BASIC_SKILL_NAMES,
|
|
43866
43921
|
ARTICLE_GENERATION_SLUG,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AgentDiscoveryBinding, type ReviewedDiscoveryInputs } from "./agent-discovery.js";
|
|
2
2
|
import { type IntegrationAgent } from "./agent-adapters.js";
|
|
3
3
|
import type { CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
|
|
4
|
+
import { type ClaudeCoordinatedHookEvent } from "./claude-settings-witness.js";
|
|
4
5
|
export type { IntegrationAgent } from "./agent-adapters.js";
|
|
5
6
|
export type ContextHookEvent = "UserPromptSubmit" | "SessionStart" | "SubagentStart";
|
|
6
7
|
export interface AgentRootAlias {
|
|
@@ -96,11 +97,18 @@ export declare function planAgentIntegration(options: {
|
|
|
96
97
|
* every other settings change and owns discovery witnesses and guarded writes.
|
|
97
98
|
* No policy means this installer has no Skills integration to update.
|
|
98
99
|
*/
|
|
99
|
-
export
|
|
100
|
+
export interface ClaudeHookUpdateOptions {
|
|
100
101
|
home?: string;
|
|
101
102
|
dataDir?: string;
|
|
102
103
|
expectedSettingsSha256: string;
|
|
103
104
|
replacement: string;
|
|
105
|
+
}
|
|
106
|
+
export declare function planClaudeStopHookUpdate(options: ClaudeHookUpdateOptions): AgentIntegrationPlan | null;
|
|
107
|
+
/** Cooperating safety installers may replace only PreToolUse. The caller owns
|
|
108
|
+
* its exact hook authorization; Skills retains discovery and native guards. */
|
|
109
|
+
export declare function planClaudePreToolUseHookUpdate(options: ClaudeHookUpdateOptions): AgentIntegrationPlan | null;
|
|
110
|
+
export declare function planClaudeHookEventsUpdate(options: ClaudeHookUpdateOptions & {
|
|
111
|
+
events: readonly ClaudeCoordinatedHookEvent[];
|
|
104
112
|
}): AgentIntegrationPlan | null;
|
|
105
113
|
export declare function applyAgentIntegration(plan: AgentIntegrationPlan): {
|
|
106
114
|
changed: string[];
|
|
@@ -9,12 +9,16 @@ export declare const CLAUDE_SETTINGS_WITNESS_LIMITS: Readonly<{
|
|
|
9
9
|
export interface ClaudeSettingsWitnessBudget {
|
|
10
10
|
remaining: number;
|
|
11
11
|
}
|
|
12
|
-
/** Cooperating installers may replace
|
|
12
|
+
/** Cooperating installers may replace only their explicitly selected event. Compare the strict syntax
|
|
13
13
|
* trees, not JSON.parse values or the preference-tolerant witness digest: all
|
|
14
14
|
* other hooks, discovery settings, unknown values and number spellings remain
|
|
15
15
|
* identical. Whitespace and object-key order have no configuration meaning.
|
|
16
16
|
*/
|
|
17
|
+
export declare const CLAUDE_COORDINATED_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "Stop", "Notification", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart"];
|
|
18
|
+
export type ClaudeCoordinatedHookEvent = typeof CLAUDE_COORDINATED_HOOK_EVENTS[number];
|
|
19
|
+
export declare function assertClaudeHookEventsReplacement(before: string | null, replacement: string, events: readonly ClaudeCoordinatedHookEvent[]): void;
|
|
17
20
|
export declare function assertClaudeStopHookReplacement(before: string | null, replacement: string): void;
|
|
21
|
+
export declare function assertClaudePreToolUseHookReplacement(before: string | null, replacement: string): void;
|
|
18
22
|
/** Hash only reviewed installer-rendered settings after the disk preimage has been verified. */
|
|
19
23
|
export declare function hashClaudeSettingsReplacement(text: string, budget: ClaudeSettingsWitnessBudget): string;
|
|
20
24
|
/** Explicit capture never converts a stored legacy raw witness or changes settings. */
|
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.9.
|
|
32376
|
+
version: "0.9.14",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -40126,32 +40126,37 @@ function canonical3(value) {
|
|
|
40126
40126
|
return { kind: "array", items: value.items.map(canonical3) };
|
|
40127
40127
|
return value;
|
|
40128
40128
|
}
|
|
40129
|
-
|
|
40129
|
+
var CLAUDE_COORDINATED_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "Stop", "Notification", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart"];
|
|
40130
|
+
function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
40131
|
+
need3(Array.isArray(events) && events.length > 0 && events.length <= CLAUDE_COORDINATED_HOOK_EVENTS.length && new Set(events).size === events.length && events.every((event) => CLAUDE_COORDINATED_HOOK_EVENTS.includes(event)), "requires explicit supported hook events");
|
|
40132
|
+
const label = events.join(",");
|
|
40130
40133
|
function bounded(text2) {
|
|
40131
|
-
need3(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes,
|
|
40134
|
+
need3(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, `${label} update exceeds its byte limit`);
|
|
40132
40135
|
return parse6(text2);
|
|
40133
40136
|
}
|
|
40134
40137
|
const original = bounded(before ?? "{}"), next = bounded(replacement);
|
|
40135
40138
|
const hadHooks = original.entries.some(([key]) => key === "hooks");
|
|
40136
|
-
function
|
|
40139
|
+
function withoutOwnedEvents(value) {
|
|
40137
40140
|
const hooks = value.entries.find(([key]) => key === "hooks")?.[1];
|
|
40138
40141
|
if (hooks !== undefined) {
|
|
40139
|
-
need3(hooks.kind === "object",
|
|
40140
|
-
const
|
|
40141
|
-
|
|
40142
|
-
|
|
40142
|
+
need3(hooks.kind === "object", `${label} update requires an object of hooks`);
|
|
40143
|
+
for (const event of events) {
|
|
40144
|
+
const selected = hooks.entries.find(([key]) => key === event)?.[1];
|
|
40145
|
+
need3(selected === undefined || selected.kind === "array", `${event} update requires a ${event} array`);
|
|
40146
|
+
}
|
|
40147
|
+
hooks.entries = hooks.entries.filter(([key]) => !events.includes(key));
|
|
40143
40148
|
if (!hadHooks && hooks.entries.length === 0)
|
|
40144
40149
|
value.entries = value.entries.filter(([key]) => key !== "hooks");
|
|
40145
40150
|
}
|
|
40146
40151
|
return canonical3(value);
|
|
40147
40152
|
}
|
|
40148
|
-
need3(JSON.stringify(
|
|
40153
|
+
need3(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
40149
40154
|
}
|
|
40150
40155
|
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
40151
40156
|
function inferencePreference(key, child) {
|
|
40152
40157
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
40153
40158
|
need3(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
40154
|
-
return
|
|
40159
|
+
return false;
|
|
40155
40160
|
}
|
|
40156
40161
|
if (key === "effortLevel") {
|
|
40157
40162
|
need3(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
|
|
@@ -41301,6 +41306,34 @@ function planAgentIntegration(options) {
|
|
|
41301
41306
|
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41302
41307
|
}
|
|
41303
41308
|
function planClaudeStopHookUpdate(options) {
|
|
41309
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
41310
|
+
}
|
|
41311
|
+
function planClaudePreToolUseHookUpdate(options) {
|
|
41312
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["PreToolUse"] });
|
|
41313
|
+
}
|
|
41314
|
+
function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
41315
|
+
const canonical4 = (value) => canonicalAgentPath(value, aliases);
|
|
41316
|
+
const containsSettings = (root2) => {
|
|
41317
|
+
const distance = relative3(canonical4(root2), path);
|
|
41318
|
+
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep4}`) && !isAbsolute15(distance);
|
|
41319
|
+
};
|
|
41320
|
+
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical4(join21(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical4(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
|
|
41321
|
+
}
|
|
41322
|
+
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
41323
|
+
try {
|
|
41324
|
+
if (binding.method !== "automatic" && binding.method !== "reviewed")
|
|
41325
|
+
throw new Error("Invalid managed discovery method");
|
|
41326
|
+
verifyAgentDiscovery(binding);
|
|
41327
|
+
if (binding.method === "automatic") {
|
|
41328
|
+
const current = resolveAgentDiscovery({ home, agent: binding.agent, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
41329
|
+
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
41330
|
+
throw new Error("Configured native discovery roots changed during Claude update");
|
|
41331
|
+
}
|
|
41332
|
+
} catch (error) {
|
|
41333
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
41334
|
+
}
|
|
41335
|
+
}
|
|
41336
|
+
function planClaudeHookEventsUpdate(options) {
|
|
41304
41337
|
const home = resolve14(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
41305
41338
|
const policyPath = join21(dataDir2, "agent-policy.json");
|
|
41306
41339
|
assertSafePath(policyPath);
|
|
@@ -41318,31 +41351,42 @@ function planClaudeStopHookUpdate(options) {
|
|
|
41318
41351
|
const before = lstatSync18(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
41319
41352
|
if ((before === null ? "absent" : sha2(before)) !== options.expectedSettingsSha256)
|
|
41320
41353
|
throw new Error("Claude settings preimage changed");
|
|
41321
|
-
|
|
41354
|
+
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
41322
41355
|
const agents = bridge.agents;
|
|
41323
41356
|
const discoveries = Object.entries(bridge.discovery).map(([agent, binding]) => {
|
|
41324
|
-
if (!agents.includes(agent) || binding?.agent !== agent)
|
|
41325
|
-
throw new Error("Invalid managed discovery owner");
|
|
41357
|
+
if (!agents.includes(agent) || binding?.agent !== agent || typeof bridge.commands?.[agent] !== "string" || typeof bridge.profiles?.[agent] !== "string")
|
|
41358
|
+
throw new Error("Invalid managed discovery owner or command/profile binding");
|
|
41326
41359
|
return binding;
|
|
41327
41360
|
});
|
|
41328
|
-
if (discoveries.length !== agents.length)
|
|
41329
|
-
throw new Error("Missing managed discovery coverage");
|
|
41330
|
-
for (const
|
|
41361
|
+
if (discoveries.length !== agents.length || !agents.includes("claude"))
|
|
41362
|
+
throw new Error("Missing managed Claude discovery coverage");
|
|
41363
|
+
for (const binding of discoveries)
|
|
41364
|
+
verifyCoordinatedDiscovery(binding, home, aliases);
|
|
41365
|
+
const consumers = claudeSettingsConsumers(home, path, discoveries, aliases);
|
|
41366
|
+
for (const agent of consumers)
|
|
41331
41367
|
assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: home });
|
|
41332
41368
|
const replacements = new Map(before === options.replacement ? [] : [[path, options.replacement]]);
|
|
41333
|
-
const discoveryAfter = discoveries.map((binding) =>
|
|
41369
|
+
const discoveryAfter = discoveries.map((binding) => {
|
|
41370
|
+
const next = rebindAgentDiscovery(binding, replacements);
|
|
41371
|
+
for (const [index, source] of binding.sources.entries()) {
|
|
41372
|
+
if (source.path !== path && JSON.stringify(next.sources[index]) !== JSON.stringify(source)) {
|
|
41373
|
+
throw new Error(`Unrelated native discovery input changed during Claude update: ${source.path}`);
|
|
41374
|
+
}
|
|
41375
|
+
}
|
|
41376
|
+
return JSON.stringify(next) === JSON.stringify(binding) ? binding : next;
|
|
41377
|
+
});
|
|
41334
41378
|
const nextPolicy = { ...policy, bridge: { ...bridge, discovery: Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) } };
|
|
41335
41379
|
const changes = before === options.replacement ? [] : [{ path, before, after: options.replacement }];
|
|
41336
41380
|
if (JSON.stringify(nextPolicy) !== JSON.stringify(policy))
|
|
41337
41381
|
changes.push({ path: policyPath, before: snapshot2.text, after: serializeManagedSkillPolicy(nextPolicy) });
|
|
41338
41382
|
recheckRootAliases(aliases);
|
|
41339
41383
|
const nativeSkills = inventoryNativeSkills(home, {
|
|
41340
|
-
agents,
|
|
41384
|
+
agents: consumers,
|
|
41341
41385
|
projectDir: home,
|
|
41342
41386
|
includeVendor: true,
|
|
41343
|
-
guardHermes:
|
|
41387
|
+
guardHermes: consumers.includes("hermes"),
|
|
41344
41388
|
allowRootAliases: aliases.length > 0,
|
|
41345
|
-
agentRoots: discoveries.flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
41389
|
+
agentRoots: discoveries.filter((binding) => consumers.includes(binding.agent)).flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
41346
41390
|
});
|
|
41347
41391
|
return {
|
|
41348
41392
|
dataDir: dataDir2,
|
|
@@ -41353,7 +41397,7 @@ function planClaudeStopHookUpdate(options) {
|
|
|
41353
41397
|
discoveryBefore: discoveries,
|
|
41354
41398
|
discoveryAfter,
|
|
41355
41399
|
rootAliases: aliases,
|
|
41356
|
-
managedAgentChecks: { home, agents:
|
|
41400
|
+
managedAgentChecks: { home, agents: consumers }
|
|
41357
41401
|
};
|
|
41358
41402
|
}
|
|
41359
41403
|
function atomicWrite2(path, content) {
|
|
@@ -41380,8 +41424,12 @@ function applyAgentIntegration(plan) {
|
|
|
41380
41424
|
recheckRootAliases(aliases);
|
|
41381
41425
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
41382
41426
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
41383
|
-
for (const binding of plan.discoveryBefore ?? [])
|
|
41384
|
-
|
|
41427
|
+
for (const binding of plan.discoveryBefore ?? []) {
|
|
41428
|
+
if (plan.managedAgentChecks)
|
|
41429
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
41430
|
+
else
|
|
41431
|
+
verifyAgentDiscovery(binding);
|
|
41432
|
+
}
|
|
41385
41433
|
if (plan.observedPolicy && currentText(plan.observedPolicy.path) !== plan.observedPolicy.before)
|
|
41386
41434
|
throw new Error("Agent policy changed after planning");
|
|
41387
41435
|
for (const change of plan.changes)
|
|
@@ -41413,8 +41461,12 @@ function applyAgentIntegration(plan) {
|
|
|
41413
41461
|
written.push({ path: change.path, before: change.before, after });
|
|
41414
41462
|
}
|
|
41415
41463
|
recheckRootAliases(aliases);
|
|
41416
|
-
for (const binding of plan.discoveryAfter ?? [])
|
|
41417
|
-
|
|
41464
|
+
for (const binding of plan.discoveryAfter ?? []) {
|
|
41465
|
+
if (plan.managedAgentChecks)
|
|
41466
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
41467
|
+
else
|
|
41468
|
+
verifyAgentDiscovery(binding);
|
|
41469
|
+
}
|
|
41418
41470
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
41419
41471
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
41420
41472
|
atomicWrite2(join21(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha2(change.before), afterHash: sha2(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
package/docs/plugin-admission.md
CHANGED
|
@@ -294,9 +294,11 @@ addition to the v1 display/model selections, it tolerates the documented
|
|
|
294
294
|
persisted effort values `low`, `medium`, `high`, and `xhigh`, both at the top
|
|
295
295
|
level and in known built-in model entries whose only property is `effortLevel`.
|
|
296
296
|
Custom model entries and entries with any other property remain fully bound.
|
|
297
|
-
The boolean `skipDangerousModePermissionPrompt`
|
|
298
|
-
|
|
299
|
-
plugins, hooks, environment, ultracode and unknown
|
|
297
|
+
The boolean `skipDangerousModePermissionPrompt` remains bound: Claude also uses
|
|
298
|
+
this saved acceptance in bypass-mode and pending project MCP approval decisions.
|
|
299
|
+
Permission settings, plugins, hooks, environment, ultracode and unknown
|
|
300
|
+
configuration remain bound. Version 0.9.14 corrects the 0.9.13 normalization of
|
|
301
|
+
this flag; an existing v2 review containing it must be explicitly reviewed again.
|
|
300
302
|
Invalid recognized preference types are refused.
|
|
301
303
|
|
|
302
304
|
`captureClaudeMarketplaceRegistryV2(path)` creates a
|
|
@@ -322,3 +324,25 @@ preference: its owning installer must coordinate a guarded settings/policy
|
|
|
322
324
|
update, or it needs a fresh explicit review.
|
|
323
325
|
|
|
324
326
|
The effort schema follows [Claude's model configuration documentation](https://code.claude.com/docs/en/model-config#adjust-effort-level).
|
|
327
|
+
|
|
328
|
+
Cooperating installers can use `planClaudeHookEventsUpdate` with an explicit
|
|
329
|
+
selection of supported Claude hook events. `planClaudePreToolUseHookUpdate` and
|
|
330
|
+
`planClaudeStopHookUpdate` are single-event convenience wrappers. Supported events
|
|
331
|
+
are `PreToolUse`, `PostToolUse`, `Stop`, `Notification`, `SessionStart`, `SessionEnd`
|
|
332
|
+
`UserPromptSubmit` and `SubagentStart`. The transaction requires the exact settings SHA-256
|
|
333
|
+
preimage and preserves every field outside the selected events, including other
|
|
334
|
+
hooks. Apply the returned plan through `applyAgentIntegration`; this validates
|
|
335
|
+
existing managed discovery and native guards, writes settings and policy with
|
|
336
|
+
precondition checks, and retains recovery backups. An invalid policy or existing
|
|
337
|
+
drift is a refusal, never permission to use an uncoordinated writer. A null plan
|
|
338
|
+
means no managed Skills policy exists. These APIs do not authorize the hook's
|
|
339
|
+
behavior or turn a configuration-changing hook into a preference.
|
|
340
|
+
|
|
341
|
+
The transaction verifies every agent's discovery sources, directories and
|
|
342
|
+
automatic discovery closure. Native runtime and trust checks cover Claude and
|
|
343
|
+
the declared consumers of the settings path, including containing roots or
|
|
344
|
+
directories and OpenCode's Claude compatibility surface. Unrelated agents keep
|
|
345
|
+
their exact witnesses and trust state; they must still pass their own consumer
|
|
346
|
+
checks before use. The plan's `managedAgentChecks.agents` and `nativeSkills`
|
|
347
|
+
describe this scoped runtime check, not a fleet health result. A path-identity
|
|
348
|
+
witness intersecting the write still refuses instead of adopting a new inode.
|