@hasna/skills 0.10.72 → 0.10.74
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 +347 -2
- package/bin/index.js +2197 -1277
- package/bin/mcp.js +194 -46
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/provenance.d.ts +18 -0
- package/dist/index.js +1016 -713
- package/dist/lib/agent-discovery.d.ts +36 -0
- package/dist/lib/claude-installer-binding.d.ts +65 -0
- package/dist/lib/codex-node-modules.d.ts +11 -0
- package/dist/lib/codex-settings-witness-v1.d.ts +9 -0
- package/dist/lib/codex-settings-witness.d.ts +18 -0
- package/dist/lib/portable-skills-files.d.ts +10 -0
- package/dist/lib/selected-run.d.ts +45 -0
- package/dist/lib/selection-provenance.d.ts +108 -0
- package/dist/runtime-prerequisites.js +1 -1
- package/dist/sdk/index.js +1174 -842
- package/docs/plugin-admission.md +63 -12
- package/docs/skill-standard.md +18 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6988,8 +6988,8 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6988
6988
|
});
|
|
6989
6989
|
|
|
6990
6990
|
// src/lib/registry.ts
|
|
6991
|
-
import { existsSync as existsSync15, readFileSync as
|
|
6992
|
-
import { join as
|
|
6991
|
+
import { existsSync as existsSync15, readFileSync as readFileSync27, readdirSync as readdirSync14 } from "fs";
|
|
6992
|
+
import { join as join41 } from "path";
|
|
6993
6993
|
|
|
6994
6994
|
// src/lib/config.ts
|
|
6995
6995
|
import { closeSync, constants, existsSync as existsSync2, fstatSync, lstatSync, openSync, readFileSync, statSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -7266,7 +7266,7 @@ import {
|
|
|
7266
7266
|
statSync as statSync13,
|
|
7267
7267
|
writeFileSync as writeFileSync9
|
|
7268
7268
|
} from "fs";
|
|
7269
|
-
import { basename as basename17, dirname as
|
|
7269
|
+
import { basename as basename17, dirname as dirname28, isAbsolute as isAbsolute29, join as join40, normalize as normalize4, relative as relative11, resolve as resolve29, sep as sep17 } from "path";
|
|
7270
7270
|
|
|
7271
7271
|
// src/lib/registry-data/index.ts
|
|
7272
7272
|
var SKILLS = [];
|
|
@@ -8566,8 +8566,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
|
|
|
8566
8566
|
displayName: manifest.displayName ?? displayName(manifest.name),
|
|
8567
8567
|
category: manifest.category ?? "Development Tools",
|
|
8568
8568
|
tags: manifest.tags?.length ? manifest.tags : ["custom", manifest.name],
|
|
8569
|
-
inputs: [],
|
|
8570
|
-
commands: []
|
|
8569
|
+
inputs: Array.isArray(manifest.inputs) ? manifest.inputs : [],
|
|
8570
|
+
commands: Array.isArray(manifest.commands) ? manifest.commands : []
|
|
8571
8571
|
};
|
|
8572
8572
|
if (!existsSync6(join6(skillPath, "SKILL.md"))) {
|
|
8573
8573
|
writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
@@ -8924,9 +8924,9 @@ var NATIVE_SKILL_ROOTS = [
|
|
|
8924
8924
|
];
|
|
8925
8925
|
|
|
8926
8926
|
// src/lib/authoring-path.ts
|
|
8927
|
-
import { lstatSync as
|
|
8927
|
+
import { lstatSync as lstatSync33, realpathSync as realpathSync14 } from "fs";
|
|
8928
8928
|
import { homedir as homedir6 } from "os";
|
|
8929
|
-
import { basename as basename16, dirname as
|
|
8929
|
+
import { basename as basename16, dirname as dirname27, join as join39, resolve as resolve28, sep as sep16 } from "path";
|
|
8930
8930
|
|
|
8931
8931
|
// src/lib/agent-adapters.ts
|
|
8932
8932
|
var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes", "sumi"];
|
|
@@ -9973,8 +9973,8 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9973
9973
|
|
|
9974
9974
|
// src/lib/agent-discovery.ts
|
|
9975
9975
|
import { createHash as createHash30 } from "crypto";
|
|
9976
|
-
import { lstatSync as
|
|
9977
|
-
import { basename as basename15, dirname as
|
|
9976
|
+
import { lstatSync as lstatSync32, readFileSync as readFileSync26, readdirSync as readdirSync12, realpathSync as realpathSync13 } from "fs";
|
|
9977
|
+
import { basename as basename15, dirname as dirname26, isAbsolute as isAbsolute28, join as join38, relative as relative10, resolve as resolve27, sep as sep15 } from "path";
|
|
9978
9978
|
import { isDeepStrictEqual as isDeepStrictEqual9 } from "util";
|
|
9979
9979
|
|
|
9980
9980
|
// node_modules/yaml/dist/index.js
|
|
@@ -12430,6 +12430,10 @@ function canonical4(value, depth = 0, budget2 = { remaining: 65536 }) {
|
|
|
12430
12430
|
function hashCodexSettingsReplacement(text2, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
12431
12431
|
need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget2.remaining >= Buffer.byteLength(text2));
|
|
12432
12432
|
budget2.remaining -= Buffer.byteLength(text2);
|
|
12433
|
+
const prepared = prepareCodexSettingsV1(text2);
|
|
12434
|
+
return finishCodexSettingsV1(prepared.config, prepared.tokens);
|
|
12435
|
+
}
|
|
12436
|
+
function prepareCodexSettingsV1(text2) {
|
|
12433
12437
|
let config;
|
|
12434
12438
|
try {
|
|
12435
12439
|
config = Bun.TOML.parse(text2);
|
|
@@ -12437,6 +12441,9 @@ function hashCodexSettingsReplacement(text2, budget2 = { remaining: 256 * 1024 *
|
|
|
12437
12441
|
throw new Error("Invalid Codex settings witness");
|
|
12438
12442
|
}
|
|
12439
12443
|
need4(config && typeof config === "object" && !Array.isArray(config));
|
|
12444
|
+
return { config, tokens: numericTokens(text2) };
|
|
12445
|
+
}
|
|
12446
|
+
function finishCodexSettingsV1(config, tokens) {
|
|
12440
12447
|
if (Object.hasOwn(config, "model")) {
|
|
12441
12448
|
need4(typeof config.model === "string" && /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(config.model));
|
|
12442
12449
|
delete config.model;
|
|
@@ -12446,7 +12453,7 @@ function hashCodexSettingsReplacement(text2, budget2 = { remaining: 256 * 1024 *
|
|
|
12446
12453
|
need4(typeof config[key] === "string" && values.has(config[key]));
|
|
12447
12454
|
delete config[key];
|
|
12448
12455
|
}
|
|
12449
|
-
return sha(`hasna.skills.codex-settings.v1\x00${JSON.stringify([canonical4(config),
|
|
12456
|
+
return sha(`hasna.skills.codex-settings.v1\x00${JSON.stringify([canonical4(config), tokens])}`);
|
|
12450
12457
|
}
|
|
12451
12458
|
function captureCodexSettings(path, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
12452
12459
|
const text2 = readNativeSettingsWitnessFile(path, budget2, "config.toml");
|
|
@@ -13160,7 +13167,7 @@ function projectStatusLine(config) {
|
|
|
13160
13167
|
if (!Object.keys(tui).length)
|
|
13161
13168
|
delete config.tui;
|
|
13162
13169
|
}
|
|
13163
|
-
function
|
|
13170
|
+
function prepareSettings(text2, version, dependencies) {
|
|
13164
13171
|
let config;
|
|
13165
13172
|
try {
|
|
13166
13173
|
config = Bun.TOML.parse(text2);
|
|
@@ -13199,18 +13206,87 @@ function settingsDigest2(text2, version = 2, dependencies) {
|
|
|
13199
13206
|
delete config.tui.model_availability_nux;
|
|
13200
13207
|
if (isRecord3(config.tui) && typeof config.tui.screen_reader_detection_done === "boolean")
|
|
13201
13208
|
delete config.tui.screen_reader_detection_done;
|
|
13202
|
-
config
|
|
13209
|
+
return { config: embedCodexNumericTokens(config, tokens), numericSource: text2, dependencies };
|
|
13210
|
+
}
|
|
13211
|
+
const mcp = version >= 3 ? projectRegularCodexMcp(config, text2) : { config, numericSource: text2 };
|
|
13212
|
+
return version >= 4 ? projectModelAvailabilityNux(mcp.config, mcp.numericSource) : mcp;
|
|
13213
|
+
}
|
|
13214
|
+
function finishSettings(prepared, version) {
|
|
13215
|
+
const config = prepared.config;
|
|
13216
|
+
if (version === 6) {
|
|
13203
13217
|
normalize2(config, 6);
|
|
13204
13218
|
if (isRecord3(config.tui) && !Object.keys(config.tui).length)
|
|
13205
13219
|
delete config.tui;
|
|
13206
|
-
return sha2(`hasna.skills.codex-settings.v6\x00${JSON.stringify([canonical5(config), canonical5(dependencies)])}`);
|
|
13220
|
+
return sha2(`hasna.skills.codex-settings.v6\x00${JSON.stringify([canonical5(config), canonical5(prepared.dependencies)])}`);
|
|
13207
13221
|
}
|
|
13208
|
-
|
|
13209
|
-
const
|
|
13210
|
-
normalize2(projected.config, version);
|
|
13211
|
-
const values = [canonical5(projected.config), numericTokens2(projected.numericSource)];
|
|
13222
|
+
normalize2(config, version);
|
|
13223
|
+
const values = [canonical5(config), numericTokens2(prepared.numericSource)];
|
|
13212
13224
|
return sha2(`hasna.skills.codex-settings.v${version}\x00${JSON.stringify(values)}`);
|
|
13213
13225
|
}
|
|
13226
|
+
function settingsDigest2(text2, version = 2, dependencies) {
|
|
13227
|
+
return finishSettings(prepareSettings(text2, version, dependencies), version);
|
|
13228
|
+
}
|
|
13229
|
+
var SCREEN_READER_RECEIPT = "screen_reader_detection_done";
|
|
13230
|
+
function cloneTree(value) {
|
|
13231
|
+
if (Array.isArray(value))
|
|
13232
|
+
return value.map(cloneTree);
|
|
13233
|
+
if (isRecord3(value) && [Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
13234
|
+
return Object.fromEntries(Object.entries(value).map(([key2, child2]) => [key2, cloneTree(child2)]));
|
|
13235
|
+
return value;
|
|
13236
|
+
}
|
|
13237
|
+
function receiptEdits(config) {
|
|
13238
|
+
const tui = config.tui;
|
|
13239
|
+
const set = (value) => (target) => {
|
|
13240
|
+
if (!isRecord3(target.tui))
|
|
13241
|
+
target.tui = {};
|
|
13242
|
+
target.tui[SCREEN_READER_RECEIPT] = value;
|
|
13243
|
+
};
|
|
13244
|
+
if (tui === undefined || isRecord3(tui) && !Object.hasOwn(tui, SCREEN_READER_RECEIPT))
|
|
13245
|
+
return [set(true), set(false)];
|
|
13246
|
+
if (!isRecord3(tui) || typeof tui[SCREEN_READER_RECEIPT] !== "boolean")
|
|
13247
|
+
return [];
|
|
13248
|
+
const remove = (dropEmpty) => (target) => {
|
|
13249
|
+
delete target.tui[SCREEN_READER_RECEIPT];
|
|
13250
|
+
if (dropEmpty && !Object.keys(target.tui).length)
|
|
13251
|
+
delete target.tui;
|
|
13252
|
+
};
|
|
13253
|
+
return [remove(true), ...Object.keys(tui).length === 1 ? [remove(false)] : [], set(!tui[SCREEN_READER_RECEIPT])];
|
|
13254
|
+
}
|
|
13255
|
+
function codexSettingsToleratesScreenReaderReceipt(text2, hashMode, expected) {
|
|
13256
|
+
try {
|
|
13257
|
+
if (Buffer.byteLength(text2) > 1024 * 1024 || !/^[a-f0-9]{64}$/.test(expected))
|
|
13258
|
+
return false;
|
|
13259
|
+
if (hashMode === "codex-settings-v1") {
|
|
13260
|
+
const prepared2 = prepareCodexSettingsV1(text2);
|
|
13261
|
+
return receiptEdits(prepared2.config).some((edit) => {
|
|
13262
|
+
const candidate = cloneTree(prepared2.config);
|
|
13263
|
+
edit(candidate);
|
|
13264
|
+
return finishCodexSettingsV1(candidate, prepared2.tokens) === expected;
|
|
13265
|
+
});
|
|
13266
|
+
}
|
|
13267
|
+
const version = Number(hashMode.slice("codex-settings-v".length));
|
|
13268
|
+
if (![2, 3, 4, 5].includes(version))
|
|
13269
|
+
return false;
|
|
13270
|
+
const prepared = prepareSettings(text2, version);
|
|
13271
|
+
return receiptEdits(prepared.config).some((edit) => {
|
|
13272
|
+
const candidate = cloneTree(prepared.config);
|
|
13273
|
+
edit(candidate);
|
|
13274
|
+
if (version >= 4 && isRecord3(candidate.tui) && !Object.keys(candidate.tui).length)
|
|
13275
|
+
delete candidate.tui;
|
|
13276
|
+
return finishSettings({ ...prepared, config: candidate }, version) === expected;
|
|
13277
|
+
});
|
|
13278
|
+
} catch {
|
|
13279
|
+
return false;
|
|
13280
|
+
}
|
|
13281
|
+
}
|
|
13282
|
+
function codexSettingsWitnessMatch(path, hashMode, expected, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
13283
|
+
const text2 = readNativeSettingsWitnessFile(path, budget2, "config.toml");
|
|
13284
|
+
need7(Buffer.byteLength(text2) <= 1024 * 1024);
|
|
13285
|
+
const exact = hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(text2) : settingsDigest2(text2, Number(hashMode.slice("codex-settings-v".length)));
|
|
13286
|
+
if (exact === expected)
|
|
13287
|
+
return "exact";
|
|
13288
|
+
return codexSettingsToleratesScreenReaderReceipt(text2, hashMode, expected) ? "screen-reader-receipt" : null;
|
|
13289
|
+
}
|
|
13214
13290
|
function hashCodexSettingsReplacementV2(text2, budget2 = { remaining: 256 * 1024 * 1024 }) {
|
|
13215
13291
|
need7(Buffer.byteLength(text2) <= 1024 * 1024 && budget2.remaining >= Buffer.byteLength(text2));
|
|
13216
13292
|
budget2.remaining -= Buffer.byteLength(text2);
|
|
@@ -13925,6 +14001,8 @@ import { execFile, spawn as spawn2 } from "child_process";
|
|
|
13925
14001
|
import { realpathSync as realpathSync4, statSync as statSync8 } from "fs";
|
|
13926
14002
|
import { isAbsolute as isAbsolute14 } from "path";
|
|
13927
14003
|
import { promisify } from "util";
|
|
14004
|
+
var CODEX_CONFIG_FALLBACK_SUMMARY = "Invalid configuration; using defaults.";
|
|
14005
|
+
var CODEX_CONFIG_INVALID = "CODEX_HOOK_TRUST_NATIVE_CONFIG_INVALID: Codex could not load its configuration and fell back to defaults; repair config.toml (run codex once to see the error) and retry";
|
|
13928
14006
|
async function connectCodexHookRpc(options) {
|
|
13929
14007
|
const timeoutMs = options.timeoutMs ?? 20000;
|
|
13930
14008
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 20000)
|
|
@@ -13953,7 +14031,7 @@ async function connectCodexHookRpc(options) {
|
|
|
13953
14031
|
else
|
|
13954
14032
|
env.CODEX_HOME = options.codexHome;
|
|
13955
14033
|
const corpusLease = acquireCodexCorpusWrite([options.codexHome ?? join17(options.home, ".codex")], { codexCommand: binary });
|
|
13956
|
-
const child2 = spawn2(binary, ["app-server", "--
|
|
14034
|
+
const child2 = spawn2(binary, ["app-server", "--stdio"], {
|
|
13957
14035
|
cwd: options.home,
|
|
13958
14036
|
env,
|
|
13959
14037
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -13965,12 +14043,12 @@ async function connectCodexHookRpc(options) {
|
|
|
13965
14043
|
resolve15();
|
|
13966
14044
|
}));
|
|
13967
14045
|
const pending = new Map;
|
|
13968
|
-
let nextId = 1, buffer = Buffer.alloc(0), stopped = false, diagnosticBytes = 0;
|
|
14046
|
+
let nextId = 1, buffer = Buffer.alloc(0), stopped = false, diagnosticBytes = 0, configInvalid = false;
|
|
13969
14047
|
const fail2 = () => {
|
|
13970
14048
|
stopped = true;
|
|
13971
14049
|
for (const value of pending.values()) {
|
|
13972
14050
|
clearTimeout(value.timer);
|
|
13973
|
-
value.reject(new Error("CODEX_HOOK_TRUST_NATIVE_RPC_FAILED"));
|
|
14051
|
+
value.reject(new Error(configInvalid ? CODEX_CONFIG_INVALID : "CODEX_HOOK_TRUST_NATIVE_RPC_FAILED"));
|
|
13974
14052
|
}
|
|
13975
14053
|
pending.clear();
|
|
13976
14054
|
};
|
|
@@ -14010,6 +14088,12 @@ async function connectCodexHookRpc(options) {
|
|
|
14010
14088
|
child2.kill();
|
|
14011
14089
|
return;
|
|
14012
14090
|
}
|
|
14091
|
+
if (!("id" in value) && value.method === "configWarning" && value.params?.summary === CODEX_CONFIG_FALLBACK_SUMMARY) {
|
|
14092
|
+
configInvalid = true;
|
|
14093
|
+
fail2();
|
|
14094
|
+
child2.kill();
|
|
14095
|
+
return;
|
|
14096
|
+
}
|
|
14013
14097
|
const waiting = pending.get(value.id);
|
|
14014
14098
|
if (!waiting)
|
|
14015
14099
|
continue;
|
|
@@ -14026,7 +14110,7 @@ async function connectCodexHookRpc(options) {
|
|
|
14026
14110
|
processId: child2.pid,
|
|
14027
14111
|
request(method, params) {
|
|
14028
14112
|
if (stopped)
|
|
14029
|
-
return Promise.reject(new Error("CODEX_HOOK_TRUST_NATIVE_RPC_CLOSED"));
|
|
14113
|
+
return Promise.reject(new Error(configInvalid ? CODEX_CONFIG_INVALID : "CODEX_HOOK_TRUST_NATIVE_RPC_CLOSED"));
|
|
14030
14114
|
return new Promise((resolve15, reject) => {
|
|
14031
14115
|
const id = nextId++;
|
|
14032
14116
|
const timer = setTimeout(() => {
|
|
@@ -14069,7 +14153,7 @@ async function connectCodexHookRpc(options) {
|
|
|
14069
14153
|
return rpc;
|
|
14070
14154
|
} catch {
|
|
14071
14155
|
await rpc.close();
|
|
14072
|
-
throw new Error("CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED: native hooks/list and versioned config writes are required");
|
|
14156
|
+
throw new Error(configInvalid ? CODEX_CONFIG_INVALID : "CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED: native hooks/list and versioned config writes are required");
|
|
14073
14157
|
}
|
|
14074
14158
|
}
|
|
14075
14159
|
|
|
@@ -15053,14 +15137,14 @@ function projectReviewedClaudePluginManifest(text2, reviewedSha256) {
|
|
|
15053
15137
|
}
|
|
15054
15138
|
|
|
15055
15139
|
// src/lib/plugin-discovery.ts
|
|
15056
|
-
import { closeSync as closeSync18, constants as constants19, fstatSync as fstatSync18, lstatSync as
|
|
15057
|
-
import { basename as basename13, dirname as
|
|
15140
|
+
import { closeSync as closeSync18, constants as constants19, fstatSync as fstatSync18, lstatSync as lstatSync30, openSync as openSync18, opendirSync as opendirSync5, readSync as readSync13 } from "fs";
|
|
15141
|
+
import { basename as basename13, dirname as dirname24, isAbsolute as isAbsolute26, join as join37, resolve as resolve25 } from "path";
|
|
15058
15142
|
|
|
15059
15143
|
// src/lib/plugin-admission.ts
|
|
15060
15144
|
import { homedir as homedir5 } from "os";
|
|
15061
|
-
import { isAbsolute as isAbsolute25, join as
|
|
15145
|
+
import { isAbsolute as isAbsolute25, join as join36, resolve as resolve24 } from "path";
|
|
15062
15146
|
// package.json
|
|
15063
|
-
var version = "0.10.
|
|
15147
|
+
var version = "0.10.74";
|
|
15064
15148
|
var package_default = {
|
|
15065
15149
|
name: "@hasna/skills",
|
|
15066
15150
|
version,
|
|
@@ -16905,22 +16989,22 @@ async function prepareSkillDependencies(skillPath, env, timeoutMs = DEFAULT_PREP
|
|
|
16905
16989
|
}
|
|
16906
16990
|
|
|
16907
16991
|
// src/lib/skillinfo.ts
|
|
16908
|
-
import { existsSync as existsSync13, readFileSync as
|
|
16909
|
-
import { join as
|
|
16992
|
+
import { existsSync as existsSync13, readFileSync as readFileSync25 } from "fs";
|
|
16993
|
+
import { join as join35 } from "path";
|
|
16910
16994
|
|
|
16911
16995
|
// src/lib/installer.ts
|
|
16912
|
-
import { existsSync as existsSync12, readFileSync as
|
|
16913
|
-
import { join as
|
|
16996
|
+
import { existsSync as existsSync12, readFileSync as readFileSync23, rmSync as rmSync3 } from "fs";
|
|
16997
|
+
import { join as join34 } from "path";
|
|
16914
16998
|
import { homedir as homedir4 } from "os";
|
|
16915
16999
|
|
|
16916
17000
|
// src/lib/agent-sync.ts
|
|
16917
17001
|
import {
|
|
16918
17002
|
cpSync as cpSync2,
|
|
16919
17003
|
existsSync as existsSync10,
|
|
16920
|
-
lstatSync as
|
|
17004
|
+
lstatSync as lstatSync29,
|
|
16921
17005
|
mkdirSync as mkdirSync9,
|
|
16922
17006
|
mkdtempSync,
|
|
16923
|
-
readFileSync as
|
|
17007
|
+
readFileSync as readFileSync20,
|
|
16924
17008
|
readdirSync as readdirSync11,
|
|
16925
17009
|
renameSync as renameSync5,
|
|
16926
17010
|
rmSync as rmSync2,
|
|
@@ -16928,7 +17012,7 @@ import {
|
|
|
16928
17012
|
writeFileSync as writeFileSync7
|
|
16929
17013
|
} from "fs";
|
|
16930
17014
|
import { homedir as homedir3 } from "os";
|
|
16931
|
-
import { basename as basename12, dirname as
|
|
17015
|
+
import { basename as basename12, dirname as dirname23, join as join32 } from "path";
|
|
16932
17016
|
|
|
16933
17017
|
// src/lib/codex-native-skill-policy.ts
|
|
16934
17018
|
import { createHash as createHash21, randomUUID as randomUUID3 } from "crypto";
|
|
@@ -18270,16 +18354,59 @@ function assertCodexSemanticCacheWitnessUnchanged(previous, current) {
|
|
|
18270
18354
|
refuse4();
|
|
18271
18355
|
}
|
|
18272
18356
|
|
|
18357
|
+
// src/lib/codex-node-modules.ts
|
|
18358
|
+
import { lstatSync as lstatSync21, readFileSync as readFileSync16 } from "fs";
|
|
18359
|
+
import { dirname as dirname19, join as join26, resolve as resolve19, sep as sep12 } from "path";
|
|
18360
|
+
var PLUGIN_MANIFESTS = Object.freeze(["plugin.json", ".codex-plugin/plugin.json", ".claude-plugin/plugin.json", ".cursor-plugin/plugin.json"]);
|
|
18361
|
+
var PLUGIN_ROOT_DEPTH = 3;
|
|
18362
|
+
function codexPluginCacheSkillTree(path, depth) {
|
|
18363
|
+
if (depth <= PLUGIN_ROOT_DEPTH)
|
|
18364
|
+
return true;
|
|
18365
|
+
let root = path;
|
|
18366
|
+
for (let level = depth;level > PLUGIN_ROOT_DEPTH; level--)
|
|
18367
|
+
root = dirname19(root);
|
|
18368
|
+
try {
|
|
18369
|
+
const skillRoots = [join26(root, "skills")];
|
|
18370
|
+
for (const name of PLUGIN_MANIFESTS) {
|
|
18371
|
+
const manifest = join26(root, name), stat2 = lstatSync21(manifest, { throwIfNoEntry: false });
|
|
18372
|
+
if (!stat2)
|
|
18373
|
+
continue;
|
|
18374
|
+
if (!stat2.isFile() || stat2.size > 1024 * 1024)
|
|
18375
|
+
return true;
|
|
18376
|
+
const text2 = new TextDecoder("utf-8", { fatal: true }).decode(readFileSync16(manifest));
|
|
18377
|
+
hashNativeJsonControls(text2);
|
|
18378
|
+
const value = JSON.parse(text2);
|
|
18379
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
18380
|
+
return true;
|
|
18381
|
+
const declared = value.skills;
|
|
18382
|
+
if (declared === undefined)
|
|
18383
|
+
continue;
|
|
18384
|
+
const paths = typeof declared === "string" ? [declared] : declared;
|
|
18385
|
+
if (!Array.isArray(paths) || paths.some((item) => typeof item !== "string"))
|
|
18386
|
+
return true;
|
|
18387
|
+
for (const item of paths) {
|
|
18388
|
+
const target = resolve19(root, item);
|
|
18389
|
+
if (target !== root && !target.startsWith(root + sep12))
|
|
18390
|
+
return true;
|
|
18391
|
+
skillRoots.push(target.endsWith(`${sep12}SKILL.md`) ? dirname19(target) : target);
|
|
18392
|
+
}
|
|
18393
|
+
}
|
|
18394
|
+
return skillRoots.some((skillRoot) => path === skillRoot || path.startsWith(skillRoot + sep12) || skillRoot.startsWith(path + sep12));
|
|
18395
|
+
} catch {
|
|
18396
|
+
return true;
|
|
18397
|
+
}
|
|
18398
|
+
}
|
|
18399
|
+
|
|
18273
18400
|
// src/lib/agent-integration.ts
|
|
18274
18401
|
import { createHash as createHash27, randomUUID as randomUUID4 } from "crypto";
|
|
18275
18402
|
import { isDeepStrictEqual as isDeepStrictEqual8 } from "util";
|
|
18276
|
-
import { existsSync as existsSync9, lstatSync as
|
|
18277
|
-
import { basename as basename11, dirname as
|
|
18403
|
+
import { existsSync as existsSync9, lstatSync as lstatSync28, statSync as statSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync19, readdirSync as readdirSync10, opendirSync as opendirSync4, readlinkSync as readlinkSync7, realpathSync as realpathSync12, renameSync as renameSync4, rmdirSync as rmdirSync3, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5, chmodSync, openSync as openSync17, closeSync as closeSync17, fsyncSync as fsyncSync7, fstatSync as fstatSync17, readSync as readSync12, constants as constants18, linkSync as linkSync3 } from "fs";
|
|
18404
|
+
import { basename as basename11, dirname as dirname22, join as join31, resolve as resolve23, relative as relative9, isAbsolute as isAbsolute24, sep as sep14 } from "path";
|
|
18278
18405
|
import { homedir as homedir2 } from "os";
|
|
18279
18406
|
|
|
18280
18407
|
// src/lib/managed-policy.ts
|
|
18281
|
-
import { closeSync as closeSync13, constants as constants13, fstatSync as fstatSync13, lstatSync as
|
|
18282
|
-
import { join as
|
|
18408
|
+
import { closeSync as closeSync13, constants as constants13, fstatSync as fstatSync13, lstatSync as lstatSync22, openSync as openSync13, readSync as readSync9 } from "fs";
|
|
18409
|
+
import { join as join27 } from "path";
|
|
18283
18410
|
function refusal(reason2 = "is unreadable") {
|
|
18284
18411
|
return new SkillSelectionError("INVALID_AGENT_POLICY", `The Skills agent loading policy ${reason2}; refusing legacy fallback.`);
|
|
18285
18412
|
}
|
|
@@ -18310,10 +18437,10 @@ function serializeManagedSkillPolicy(value) {
|
|
|
18310
18437
|
return text2;
|
|
18311
18438
|
}
|
|
18312
18439
|
function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
18313
|
-
const path =
|
|
18440
|
+
const path = join27(dataDir2, "agent-policy.json");
|
|
18314
18441
|
let descriptor, observed = false;
|
|
18315
18442
|
try {
|
|
18316
|
-
const stat2 =
|
|
18443
|
+
const stat2 = lstatSync22(path);
|
|
18317
18444
|
observed = true;
|
|
18318
18445
|
if (!stat2.isFile() || stat2.isSymbolicLink())
|
|
18319
18446
|
throw refusal();
|
|
@@ -18339,7 +18466,7 @@ function readManagedSkillPolicySnapshot(dataDir2 = getDataDirReadOnly()) {
|
|
|
18339
18466
|
return { text: text2, value: parseManagedSkillPolicy(text2) };
|
|
18340
18467
|
} catch (error) {
|
|
18341
18468
|
if (error.code === "ENOENT" && !observed) {
|
|
18342
|
-
if (
|
|
18469
|
+
if (lstatSync22(path, { throwIfNoEntry: false }) === undefined)
|
|
18343
18470
|
return null;
|
|
18344
18471
|
}
|
|
18345
18472
|
if (error instanceof SkillSelectionError)
|
|
@@ -18359,8 +18486,8 @@ function requiresCliSkillLoading(dataDir2 = getDataDirReadOnly()) {
|
|
|
18359
18486
|
|
|
18360
18487
|
// src/lib/agent-bridge.ts
|
|
18361
18488
|
import { createHash as createHash23 } from "crypto";
|
|
18362
|
-
import { lstatSync as
|
|
18363
|
-
import { join as
|
|
18489
|
+
import { lstatSync as lstatSync23, readFileSync as readFileSync17, readdirSync as readdirSync9 } from "fs";
|
|
18490
|
+
import { join as join28 } from "path";
|
|
18364
18491
|
var CLI_BRIDGE_NAME = "skills-cli";
|
|
18365
18492
|
var CLI_BRIDGE_VERSION = 1;
|
|
18366
18493
|
var markdown = `---
|
|
@@ -18387,13 +18514,13 @@ function isOwnedCliBridge(path, expectedPaths) {
|
|
|
18387
18514
|
if (!expectedPaths.includes(path))
|
|
18388
18515
|
return false;
|
|
18389
18516
|
try {
|
|
18390
|
-
if (!
|
|
18517
|
+
if (!lstatSync23(path).isDirectory())
|
|
18391
18518
|
return false;
|
|
18392
18519
|
if (JSON.stringify(readdirSync9(path).sort()) !== JSON.stringify(Object.keys(CLI_BRIDGE_FILES).sort()))
|
|
18393
18520
|
return false;
|
|
18394
18521
|
return Object.entries(CLI_BRIDGE_FILES).every(([name, content]) => {
|
|
18395
|
-
const file =
|
|
18396
|
-
return stat2.isFile() && !stat2.isSymbolicLink() && stat2.size === Buffer.byteLength(content) &&
|
|
18522
|
+
const file = join28(path, name), stat2 = lstatSync23(file);
|
|
18523
|
+
return stat2.isFile() && !stat2.isSymbolicLink() && stat2.size === Buffer.byteLength(content) && readFileSync17(file, "utf8") === content;
|
|
18397
18524
|
});
|
|
18398
18525
|
} catch {
|
|
18399
18526
|
return false;
|
|
@@ -18670,8 +18797,8 @@ function disableCodexBundledSkills(text2) {
|
|
|
18670
18797
|
|
|
18671
18798
|
// src/lib/agent-sumi.ts
|
|
18672
18799
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
18673
|
-
import { accessSync as accessSync2, constants as constants14, existsSync as existsSync8, lstatSync as
|
|
18674
|
-
import { delimiter as delimiter2, isAbsolute as isAbsolute20, join as
|
|
18800
|
+
import { accessSync as accessSync2, constants as constants14, existsSync as existsSync8, lstatSync as lstatSync24, realpathSync as realpathSync10, statSync as statSync10 } from "fs";
|
|
18801
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute20, join as join29, resolve as resolve20 } from "path";
|
|
18675
18802
|
|
|
18676
18803
|
// src/lib/hook-diagnostics.ts
|
|
18677
18804
|
var CONTEXT_ERROR_CODES = new Set([
|
|
@@ -18760,7 +18887,7 @@ function recordWithKeys(value, keys) {
|
|
|
18760
18887
|
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key2) => Object.hasOwn(value, key2));
|
|
18761
18888
|
}
|
|
18762
18889
|
function absolutePath3(value) {
|
|
18763
|
-
return typeof value === "string" && value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute20(value) &&
|
|
18890
|
+
return typeof value === "string" && value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute20(value) && resolve20(value) === value;
|
|
18764
18891
|
}
|
|
18765
18892
|
function absoluteInput(value) {
|
|
18766
18893
|
return value.length > 0 && value.length <= 32768 && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute20(value);
|
|
@@ -18771,7 +18898,7 @@ function resolveSumiPathsCommand(pathValue) {
|
|
|
18771
18898
|
for (const directory2 of pathValue.split(delimiter2)) {
|
|
18772
18899
|
if (!directory2 || /[\x00-\x1f\x7f]/.test(directory2))
|
|
18773
18900
|
continue;
|
|
18774
|
-
const candidate =
|
|
18901
|
+
const candidate = resolve20(directory2, "sumi-paths");
|
|
18775
18902
|
try {
|
|
18776
18903
|
accessSync2(candidate, constants14.X_OK);
|
|
18777
18904
|
if (statSync10(candidate).isFile())
|
|
@@ -18783,7 +18910,7 @@ function resolveSumiPathsCommand(pathValue) {
|
|
|
18783
18910
|
function validPathPlan(value, home, cwd) {
|
|
18784
18911
|
if (!recordWithKeys(value, ["schemaVersion", "kind", "home", "cwd", "roots", "legacyRoots", "configFiles", "skillRoots"]))
|
|
18785
18912
|
return false;
|
|
18786
|
-
if (value.schemaVersion !== 1 || value.kind !== "sumi-paths" || value.home !==
|
|
18913
|
+
if (value.schemaVersion !== 1 || value.kind !== "sumi-paths" || value.home !== resolve20(home) || value.cwd !== resolve20(cwd))
|
|
18787
18914
|
return false;
|
|
18788
18915
|
if (!recordWithKeys(value.roots, SUMI_PATH_KEYS) || !recordWithKeys(value.legacyRoots, SUMI_PATH_KEYS))
|
|
18789
18916
|
return false;
|
|
@@ -18805,11 +18932,11 @@ function validPathPlan(value, home, cwd) {
|
|
|
18805
18932
|
const legacyConfigRoot = value.legacyRoots.config;
|
|
18806
18933
|
if (typeof configRoot !== "string" || legacyConfigRoot !== null && typeof legacyConfigRoot !== "string")
|
|
18807
18934
|
return false;
|
|
18808
|
-
if (value.configFiles.canonical !==
|
|
18935
|
+
if (value.configFiles.canonical !== join29(configRoot, "sumi.json") || value.skillRoots.canonical !== join29(configRoot, "skills"))
|
|
18809
18936
|
return false;
|
|
18810
18937
|
if (legacyConfigRoot === null)
|
|
18811
18938
|
return value.configFiles.legacy === null && value.skillRoots.legacy === null;
|
|
18812
|
-
return value.configFiles.legacy ===
|
|
18939
|
+
return value.configFiles.legacy === join29(legacyConfigRoot, "sumi.json") && value.skillRoots.legacy === join29(legacyConfigRoot, "skills");
|
|
18813
18940
|
}
|
|
18814
18941
|
function sumiPathPlan(home, cwd = process.cwd(), env = process.env) {
|
|
18815
18942
|
for (const key2 of ["SUMI_CONFIG", "SUMI_CONFIG_CONTENT"])
|
|
@@ -18827,8 +18954,8 @@ function sumiPathPlan(home, cwd = process.cwd(), env = process.env) {
|
|
|
18827
18954
|
childEnv[key2] = env[key2];
|
|
18828
18955
|
let child2;
|
|
18829
18956
|
try {
|
|
18830
|
-
child2 = spawnSync4(executable2, ["--json", "--home",
|
|
18831
|
-
cwd:
|
|
18957
|
+
child2 = spawnSync4(executable2, ["--json", "--home", resolve20(home), "--cwd", resolve20(cwd)], {
|
|
18958
|
+
cwd: resolve20(cwd),
|
|
18832
18959
|
env: childEnv,
|
|
18833
18960
|
encoding: "buffer",
|
|
18834
18961
|
timeout: 3000,
|
|
@@ -18860,7 +18987,7 @@ function selectedConfigDirectory(plan) {
|
|
|
18860
18987
|
const canonical9 = plan.roots.config, legacy = plan.legacyRoots.config;
|
|
18861
18988
|
if (legacy === null)
|
|
18862
18989
|
return existsSync8(canonical9) ? realpathSync10(canonical9) : canonical9;
|
|
18863
|
-
const canonicalStat =
|
|
18990
|
+
const canonicalStat = lstatSync24(canonical9, { throwIfNoEntry: false }), legacyStat = lstatSync24(legacy, { throwIfNoEntry: false });
|
|
18864
18991
|
if (canonicalStat && legacyStat && realpathSync10(canonical9) !== realpathSync10(legacy))
|
|
18865
18992
|
throw new Error("Sumi config roots conflict; complete native home adoption before Skills integration");
|
|
18866
18993
|
const selected = canonicalStat ? canonical9 : legacyStat ? legacy : canonical9;
|
|
@@ -18876,13 +19003,13 @@ function sumiConfigDirectory(home, env = process.env, cwd = process.cwd()) {
|
|
|
18876
19003
|
}
|
|
18877
19004
|
function sumiSkillRoots(home, cwd = process.cwd(), env = process.env) {
|
|
18878
19005
|
const plan = sumiPathPlan(home, cwd, env);
|
|
18879
|
-
return [
|
|
19006
|
+
return [join29(selectedConfigDirectory(plan), "skills")];
|
|
18880
19007
|
}
|
|
18881
19008
|
function sumiBridgeRoot(home, cwd = process.cwd(), env = process.env) {
|
|
18882
19009
|
return sumiSkillRoots(home, cwd, env)[0];
|
|
18883
19010
|
}
|
|
18884
19011
|
function sumiConfigPath(home, cwd = process.cwd(), env = process.env) {
|
|
18885
|
-
return
|
|
19012
|
+
return join29(sumiConfigDirectory(home, env, cwd), "sumi.json");
|
|
18886
19013
|
}
|
|
18887
19014
|
function renderSumiPlugin(command, profile) {
|
|
18888
19015
|
return renderSumiPluginVersion(command, profile, 3);
|
|
@@ -18987,9 +19114,9 @@ var SUMI_SKILL_PERMISSIONS = [
|
|
|
18987
19114
|
];
|
|
18988
19115
|
|
|
18989
19116
|
// src/lib/codex-hook-trust-files.ts
|
|
18990
|
-
import { closeSync as closeSync14, constants as constants15, fstatSync as fstatSync14, fsyncSync as fsyncSync4, lstatSync as
|
|
19117
|
+
import { closeSync as closeSync14, constants as constants15, fstatSync as fstatSync14, fsyncSync as fsyncSync4, lstatSync as lstatSync25, openSync as openSync14, readlinkSync as readlinkSync5, readSync as readSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
18991
19118
|
import { createHash as createHash24 } from "crypto";
|
|
18992
|
-
import { dirname as
|
|
19119
|
+
import { dirname as dirname20, isAbsolute as isAbsolute21, resolve as resolve21 } from "path";
|
|
18993
19120
|
function need8(value, code) {
|
|
18994
19121
|
if (!value)
|
|
18995
19122
|
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
@@ -18999,11 +19126,11 @@ function trustedAncestorOwner(uid2) {
|
|
|
18999
19126
|
return BigInt(uid2) === 0n || BigInt(uid2) === BigInt(process.getuid());
|
|
19000
19127
|
}
|
|
19001
19128
|
function safeParents(file) {
|
|
19002
|
-
for (let p =
|
|
19003
|
-
const s =
|
|
19129
|
+
for (let p = dirname20(file);; p = dirname20(p)) {
|
|
19130
|
+
const s = lstatSync25(p);
|
|
19004
19131
|
need8(s.isDirectory() && !s.isSymbolicLink() && trustedAncestorOwner(s.uid), "UNSAFE_PARENT");
|
|
19005
19132
|
need8((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
19006
|
-
if (
|
|
19133
|
+
if (dirname20(p) === p)
|
|
19007
19134
|
break;
|
|
19008
19135
|
}
|
|
19009
19136
|
}
|
|
@@ -19018,12 +19145,12 @@ function directAliasTarget(link) {
|
|
|
19018
19145
|
const names = parts.slice(first);
|
|
19019
19146
|
if (!names.length || names.some((part) => part === "" || part === "." || part === ".."))
|
|
19020
19147
|
return;
|
|
19021
|
-
return
|
|
19148
|
+
return resolve21(dirname20(link), text2);
|
|
19022
19149
|
}
|
|
19023
19150
|
function snapshot(file, privateFile = false, options = {}) {
|
|
19024
19151
|
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
19025
19152
|
safeParents(file);
|
|
19026
|
-
const initial =
|
|
19153
|
+
const initial = lstatSync25(file, { bigint: true });
|
|
19027
19154
|
need8(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");
|
|
19028
19155
|
need8(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
19029
19156
|
const fd = openSync14(file, constants15.O_RDONLY | constants15.O_NOFOLLOW | constants15.O_NONBLOCK);
|
|
@@ -19038,7 +19165,7 @@ function snapshot(file, privateFile = false, options = {}) {
|
|
|
19038
19165
|
break;
|
|
19039
19166
|
length += n2;
|
|
19040
19167
|
}
|
|
19041
|
-
need8(length === Number(before.size) && sameStat(before, fstatSync14(fd, { bigint: true })) && sameStat(before,
|
|
19168
|
+
need8(length === Number(before.size) && sameStat(before, fstatSync14(fd, { bigint: true })) && sameStat(before, lstatSync25(file, { bigint: true })), "INPUT_CHANGED");
|
|
19042
19169
|
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
19043
19170
|
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash24("sha256").update(content).digest("hex") };
|
|
19044
19171
|
} finally {
|
|
@@ -19052,7 +19179,7 @@ function unchanged2(before) {
|
|
|
19052
19179
|
|
|
19053
19180
|
// src/cli/commands/runtime-launcher.ts
|
|
19054
19181
|
import { createHash as createHash25 } from "crypto";
|
|
19055
|
-
import { closeSync as closeSync15, constants as constants16, fchmodSync as fchmodSync2, fstatSync as fstatSync15, fsyncSync as fsyncSync5, lstatSync as
|
|
19182
|
+
import { closeSync as closeSync15, constants as constants16, fchmodSync as fchmodSync2, fstatSync as fstatSync15, fsyncSync as fsyncSync5, lstatSync as lstatSync26, openSync as openSync15, readFileSync as readFileSync18, readlinkSync as readlinkSync6, realpathSync as realpathSync11, symlinkSync, writeSync as writeSync3 } from "fs";
|
|
19056
19183
|
import { isAbsolute as isAbsolute22 } from "path";
|
|
19057
19184
|
|
|
19058
19185
|
// src/server/launcher-environment-policy.ts
|
|
@@ -19259,9 +19386,9 @@ function parsePinnedLauncher(text2) {
|
|
|
19259
19386
|
var PINNED_LAUNCHER_MAX_BYTES = 16 * 1024;
|
|
19260
19387
|
function resolveLauncherCommand(path) {
|
|
19261
19388
|
const physical = realpathSync11(path);
|
|
19262
|
-
const stat2 =
|
|
19389
|
+
const stat2 = lstatSync26(physical);
|
|
19263
19390
|
if (stat2.isFile() && stat2.size <= PINNED_LAUNCHER_MAX_BYTES) {
|
|
19264
|
-
const text2 =
|
|
19391
|
+
const text2 = readFileSync18(physical, "utf8");
|
|
19265
19392
|
const binding = parsePinnedLauncher(text2);
|
|
19266
19393
|
if (binding) {
|
|
19267
19394
|
let entry;
|
|
@@ -19281,7 +19408,7 @@ function assertPinnedLauncherRuntimeSafe(runtime) {
|
|
|
19281
19408
|
assertLauncherPath(runtime, "LAUNCHER_RUNTIME_PATH_INVALID");
|
|
19282
19409
|
let stat2;
|
|
19283
19410
|
try {
|
|
19284
|
-
stat2 =
|
|
19411
|
+
stat2 = lstatSync26(runtime);
|
|
19285
19412
|
} catch {
|
|
19286
19413
|
throw new Error("LAUNCHER_RUNTIME_UNSAFE");
|
|
19287
19414
|
}
|
|
@@ -19293,8 +19420,8 @@ function assertPinnedLauncherRuntimeSafe(runtime) {
|
|
|
19293
19420
|
|
|
19294
19421
|
// src/lib/retained-review-artifacts.ts
|
|
19295
19422
|
import { createHash as createHash26 } from "crypto";
|
|
19296
|
-
import { constants as constants17, closeSync as closeSync16, fstatSync as fstatSync16, fsyncSync as fsyncSync6, lstatSync as
|
|
19297
|
-
import { dirname as
|
|
19423
|
+
import { constants as constants17, closeSync as closeSync16, fstatSync as fstatSync16, fsyncSync as fsyncSync6, lstatSync as lstatSync27, mkdirSync as mkdirSync7, openSync as openSync16, readSync as readSync11, writeSync as writeSync4 } from "fs";
|
|
19424
|
+
import { dirname as dirname21, isAbsolute as isAbsolute23, join as join30, resolve as resolve22, sep as sep13 } from "path";
|
|
19298
19425
|
var sha3 = (bytes) => createHash26("sha256").update(bytes).digest("hex");
|
|
19299
19426
|
var hex = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
19300
19427
|
var refuse5 = () => {
|
|
@@ -19308,19 +19435,19 @@ function keys(value, names) {
|
|
|
19308
19435
|
return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === names.length && names.every((name) => Object.hasOwn(value, name)));
|
|
19309
19436
|
}
|
|
19310
19437
|
function safe4(path) {
|
|
19311
|
-
need9(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute23(path) &&
|
|
19312
|
-
for (let cursor = path;; cursor =
|
|
19313
|
-
const stat2 =
|
|
19438
|
+
need9(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute23(path) && resolve22(path) === path && !/[\x00-\x1f\x7f]/.test(path));
|
|
19439
|
+
for (let cursor = path;; cursor = dirname21(cursor)) {
|
|
19440
|
+
const stat2 = lstatSync27(cursor, { throwIfNoEntry: false });
|
|
19314
19441
|
need9(!stat2?.isSymbolicLink());
|
|
19315
19442
|
if (cursor !== path && stat2)
|
|
19316
19443
|
need9(stat2.isDirectory());
|
|
19317
|
-
if (
|
|
19444
|
+
if (dirname21(cursor) === cursor)
|
|
19318
19445
|
break;
|
|
19319
19446
|
}
|
|
19320
19447
|
}
|
|
19321
19448
|
function readExact(path) {
|
|
19322
19449
|
safe4(path);
|
|
19323
|
-
const before =
|
|
19450
|
+
const before = lstatSync27(path, { throwIfNoEntry: false });
|
|
19324
19451
|
need9(before?.isFile() && before.nlink === 1 && before.size <= AGENT_POLICY_LIMITS.discoveryRawSourceBytes && (before.mode & 63) === 0 && (process.getuid === undefined || before.uid === process.getuid()));
|
|
19325
19452
|
const fd = openSync16(path, constants17.O_RDONLY | constants17.O_NOFOLLOW | constants17.O_NONBLOCK);
|
|
19326
19453
|
try {
|
|
@@ -19334,7 +19461,7 @@ function readExact(path) {
|
|
|
19334
19461
|
offset += count;
|
|
19335
19462
|
}
|
|
19336
19463
|
need9(readSync11(fd, Buffer.alloc(1), 0, 1, null) === 0);
|
|
19337
|
-
const after = fstatSync16(fd), current =
|
|
19464
|
+
const after = fstatSync16(fd), current = lstatSync27(path, { throwIfNoEntry: false });
|
|
19338
19465
|
safe4(path);
|
|
19339
19466
|
need9(current && opened.dev === current.dev && opened.ino === current.ino && opened.mode === current.mode && opened.uid === current.uid && opened.size === current.size && opened.mtimeMs === current.mtimeMs && opened.ctimeMs === current.ctimeMs && current.nlink === 1 && opened.mode === after.mode && opened.uid === after.uid && after.nlink === 1 && opened.size === after.size && opened.mtimeMs === after.mtimeMs && opened.ctimeMs === after.ctimeMs);
|
|
19340
19467
|
const text2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
@@ -19360,24 +19487,24 @@ function verifyReviewArtifactOriginal(path, expectedSha256) {
|
|
|
19360
19487
|
need9(hex(expectedSha256) && sha3(readExact(path)) === expectedSha256);
|
|
19361
19488
|
}
|
|
19362
19489
|
function retainedArtifactPath(metadata) {
|
|
19363
|
-
return
|
|
19490
|
+
return join30(metadata.storeRoot, "agent-discovery", "artifacts", metadata.sha256, "catalog.json");
|
|
19364
19491
|
}
|
|
19365
19492
|
function validRetainedArtifact(value) {
|
|
19366
|
-
return keys(value, ["version", "kind", "storeRoot", "originalPath", "sha256", "catalogSha256"]) && value.version === 1 && value.kind === "codex-native-catalog" && hex(value.sha256) && hex(value.catalogSha256) && [value.storeRoot, value.originalPath].every((path) => typeof path === "string" && isAbsolute23(path) &&
|
|
19493
|
+
return keys(value, ["version", "kind", "storeRoot", "originalPath", "sha256", "catalogSha256"]) && value.version === 1 && value.kind === "codex-native-catalog" && hex(value.sha256) && hex(value.catalogSha256) && [value.storeRoot, value.originalPath].every((path) => typeof path === "string" && isAbsolute23(path) && resolve22(path) === path && path.length <= AGENT_POLICY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(path));
|
|
19367
19494
|
}
|
|
19368
19495
|
function privateStore(storeRoot) {
|
|
19369
19496
|
safe4(storeRoot);
|
|
19370
|
-
const stat2 =
|
|
19497
|
+
const stat2 = lstatSync27(storeRoot, { throwIfNoEntry: false });
|
|
19371
19498
|
need9(!stat2 || stat2.isDirectory() && (stat2.mode & 18) === 0 && (process.getuid === undefined || stat2.uid === process.getuid()));
|
|
19372
19499
|
}
|
|
19373
19500
|
function verifyRetainedReviewArtifact(source, storeRoot) {
|
|
19374
19501
|
const artifact = source.reviewArtifact;
|
|
19375
|
-
need9(validRetainedArtifact(artifact) && source.hashMode === "bytes" && source.sha256 === artifact.sha256 && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.path === retainedArtifactPath(artifact) && (storeRoot === undefined || artifact.storeRoot ===
|
|
19502
|
+
need9(validRetainedArtifact(artifact) && source.hashMode === "bytes" && source.sha256 === artifact.sha256 && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.path === retainedArtifactPath(artifact) && (storeRoot === undefined || artifact.storeRoot === resolve22(storeRoot)));
|
|
19376
19503
|
privateStore(artifact.storeRoot);
|
|
19377
19504
|
const text2 = readExact(source.path);
|
|
19378
19505
|
need9(sha3(text2) === artifact.sha256 && catalogDigest(text2) === artifact.catalogSha256);
|
|
19379
|
-
for (let parent =
|
|
19380
|
-
const stat2 =
|
|
19506
|
+
for (let parent = dirname21(source.path);parent !== artifact.storeRoot; parent = dirname21(parent)) {
|
|
19507
|
+
const stat2 = lstatSync27(parent);
|
|
19381
19508
|
need9(stat2.isDirectory() && (stat2.mode & 63) === 0 && (process.getuid === undefined || stat2.uid === process.getuid()));
|
|
19382
19509
|
}
|
|
19383
19510
|
}
|
|
@@ -19385,7 +19512,7 @@ function planReviewArtifactRetention(binding, artifacts, dataDir2, home) {
|
|
|
19385
19512
|
if (artifacts === undefined)
|
|
19386
19513
|
return { binding, changes: [], observed: [] };
|
|
19387
19514
|
need9(binding.agent === "codex" && binding.method === "reviewed" && Array.isArray(artifacts) && artifacts.length > 0 && artifacts.length <= 32);
|
|
19388
|
-
privateStore(
|
|
19515
|
+
privateStore(resolve22(dataDir2));
|
|
19389
19516
|
const paths = new Set;
|
|
19390
19517
|
const changes = [], observed = [];
|
|
19391
19518
|
let totalBytes = 0;
|
|
@@ -19394,19 +19521,19 @@ function planReviewArtifactRetention(binding, artifacts, dataDir2, home) {
|
|
|
19394
19521
|
need9(keys(declaration, ["kind", "path", "sha256"]) && declaration.kind === "codex-native-catalog" && hex(declaration.sha256) && !paths.has(declaration.path));
|
|
19395
19522
|
paths.add(declaration.path);
|
|
19396
19523
|
safe4(declaration.path);
|
|
19397
|
-
const liveRoots = [
|
|
19398
|
-
need9(!liveRoots.some((root) => declaration.path === root || declaration.path.startsWith(root +
|
|
19524
|
+
const liveRoots = [join30(home, ".codex"), ...binding.roots, ...(binding.directories ?? []).map((item) => item.path)];
|
|
19525
|
+
need9(!liveRoots.some((root) => declaration.path === root || declaration.path.startsWith(root + sep13)));
|
|
19399
19526
|
const matches = sources.filter((source2) => source2.path === declaration.path);
|
|
19400
19527
|
need9(matches.length === 1 && matches[0].sha256 === declaration.sha256 && matches[0].reviewArtifact === undefined && [undefined, "bytes"].includes(matches[0].hashMode) && matches[0].format === undefined && matches[0].fields === undefined && matches[0].managedPlugins === undefined);
|
|
19401
19528
|
const text2 = readExact(declaration.path);
|
|
19402
19529
|
need9(sha3(text2) === declaration.sha256);
|
|
19403
19530
|
totalBytes += Buffer.byteLength(text2);
|
|
19404
19531
|
need9(totalBytes <= AGENT_POLICY_LIMITS.discoveryRawTotalBytes);
|
|
19405
|
-
const metadata = { version: 1, kind: declaration.kind, storeRoot:
|
|
19532
|
+
const metadata = { version: 1, kind: declaration.kind, storeRoot: resolve22(dataDir2), originalPath: declaration.path, sha256: declaration.sha256, catalogSha256: catalogDigest(text2) };
|
|
19406
19533
|
const path = retainedArtifactPath(metadata);
|
|
19407
19534
|
safe4(path);
|
|
19408
19535
|
const source = { path, sha256: metadata.sha256, hashMode: "bytes", reviewArtifact: metadata };
|
|
19409
|
-
if (
|
|
19536
|
+
if (lstatSync27(path, { throwIfNoEntry: false }))
|
|
19410
19537
|
verifyRetainedReviewArtifact(source, dataDir2);
|
|
19411
19538
|
else if (!changes.some((change) => change.path === path))
|
|
19412
19539
|
changes.push({ path, before: null, after: text2, artifact: metadata });
|
|
@@ -19420,14 +19547,14 @@ function writeReviewArtifactExclusive(change) {
|
|
|
19420
19547
|
need9(change.before === null && validRetainedArtifact(change.artifact) && change.path === retainedArtifactPath(change.artifact) && sha3(change.after) === change.artifact.sha256 && catalogDigest(change.after) === change.artifact.catalogSha256);
|
|
19421
19548
|
privateStore(change.artifact.storeRoot);
|
|
19422
19549
|
const missing2 = [];
|
|
19423
|
-
for (let parent =
|
|
19550
|
+
for (let parent = dirname21(change.path);!lstatSync27(parent, { throwIfNoEntry: false }); parent = dirname21(parent))
|
|
19424
19551
|
missing2.push(parent);
|
|
19425
19552
|
for (const parent of missing2.reverse())
|
|
19426
19553
|
mkdirSync7(parent, { mode: 448 });
|
|
19427
19554
|
safe4(change.path);
|
|
19428
19555
|
privateStore(change.artifact.storeRoot);
|
|
19429
|
-
for (let parent =
|
|
19430
|
-
const stat2 =
|
|
19556
|
+
for (let parent = dirname21(change.path);parent !== change.artifact.storeRoot; parent = dirname21(parent)) {
|
|
19557
|
+
const stat2 = lstatSync27(parent);
|
|
19431
19558
|
need9(stat2.isDirectory() && (stat2.mode & 63) === 0 && (process.getuid === undefined || stat2.uid === process.getuid()));
|
|
19432
19559
|
}
|
|
19433
19560
|
const fd = openSync16(change.path, constants17.O_WRONLY | constants17.O_CREAT | constants17.O_EXCL | constants17.O_NOFOLLOW, 384);
|
|
@@ -19444,7 +19571,7 @@ function writeReviewArtifactExclusive(change) {
|
|
|
19444
19571
|
closeSync16(fd);
|
|
19445
19572
|
}
|
|
19446
19573
|
need9(readExact(change.path) === change.after);
|
|
19447
|
-
const directory2 = openSync16(
|
|
19574
|
+
const directory2 = openSync16(dirname21(change.path), constants17.O_RDONLY | constants17.O_DIRECTORY | constants17.O_NOFOLLOW);
|
|
19448
19575
|
try {
|
|
19449
19576
|
fsyncSync6(directory2);
|
|
19450
19577
|
} finally {
|
|
@@ -19461,7 +19588,7 @@ function nativeAgentRoot(home, agent, cwd = process.cwd()) {
|
|
|
19461
19588
|
const root = AGENT_ADAPTERS[agent].root;
|
|
19462
19589
|
if (typeof root !== "string")
|
|
19463
19590
|
throw new Error(`Native skill root is unavailable for ${agent}`);
|
|
19464
|
-
return
|
|
19591
|
+
return join31(home, root);
|
|
19465
19592
|
}
|
|
19466
19593
|
function nativeAgentConfig(home, agent, cwd = process.cwd()) {
|
|
19467
19594
|
if (agent === "sumi")
|
|
@@ -19469,7 +19596,7 @@ function nativeAgentConfig(home, agent, cwd = process.cwd()) {
|
|
|
19469
19596
|
const config = AGENT_ADAPTERS[agent].config;
|
|
19470
19597
|
if (typeof config !== "string")
|
|
19471
19598
|
throw new Error(`Native configuration path is unavailable for ${agent}`);
|
|
19472
|
-
return
|
|
19599
|
+
return join31(home, config);
|
|
19473
19600
|
}
|
|
19474
19601
|
function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
|
|
19475
19602
|
try {
|
|
@@ -19521,11 +19648,11 @@ function isSystemRootAlias(path) {
|
|
|
19521
19648
|
const identity5 = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}`;
|
|
19522
19649
|
const directoryIdentity = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}`;
|
|
19523
19650
|
try {
|
|
19524
|
-
const before =
|
|
19651
|
+
const before = lstatSync28(path, { bigint: true });
|
|
19525
19652
|
if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync7(path) !== link)
|
|
19526
19653
|
return false;
|
|
19527
19654
|
const directories = ["/", "/private", target].map((directory2) => {
|
|
19528
|
-
const stat2 =
|
|
19655
|
+
const stat2 = lstatSync28(directory2, { bigint: true });
|
|
19529
19656
|
if (!stat2.isDirectory() || stat2.uid !== 0n)
|
|
19530
19657
|
throw new Error("Untrusted system alias directory");
|
|
19531
19658
|
if (directory2 !== "/private/tmp") {
|
|
@@ -19542,25 +19669,25 @@ function isSystemRootAlias(path) {
|
|
|
19542
19669
|
}
|
|
19543
19670
|
return { directory: directory2, identity: directoryIdentity(stat2) };
|
|
19544
19671
|
});
|
|
19545
|
-
return realpathSync12(path) === target && readlinkSync7(path) === link && identity5(
|
|
19672
|
+
return realpathSync12(path) === target && readlinkSync7(path) === link && identity5(lstatSync28(path, { bigint: true })) === identity5(before) && directories.every((item) => directoryIdentity(lstatSync28(item.directory, { bigint: true })) === item.identity);
|
|
19546
19673
|
} catch {
|
|
19547
19674
|
return false;
|
|
19548
19675
|
}
|
|
19549
19676
|
}
|
|
19550
19677
|
function canonicalSystemPath(path) {
|
|
19551
|
-
const absolute =
|
|
19552
|
-
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !
|
|
19678
|
+
const absolute = resolve23(path), root = `/${absolute.split(sep14)[1]}`;
|
|
19679
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync28(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
19553
19680
|
return absolute;
|
|
19554
19681
|
if (!isSystemRootAlias(root))
|
|
19555
19682
|
throw new Error(`Refusing symlink path: ${root}`);
|
|
19556
19683
|
return `/private${absolute}`;
|
|
19557
19684
|
}
|
|
19558
19685
|
function assertSafePath(path) {
|
|
19559
|
-
let cursor =
|
|
19686
|
+
let cursor = resolve23(path);
|
|
19560
19687
|
while (true) {
|
|
19561
|
-
if (
|
|
19688
|
+
if (lstatSync28(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
19562
19689
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
19563
|
-
const parent =
|
|
19690
|
+
const parent = dirname22(cursor);
|
|
19564
19691
|
if (parent === cursor)
|
|
19565
19692
|
return;
|
|
19566
19693
|
cursor = parent;
|
|
@@ -19568,17 +19695,17 @@ function assertSafePath(path) {
|
|
|
19568
19695
|
}
|
|
19569
19696
|
function readRootAlias(home, agent) {
|
|
19570
19697
|
assertSafePath(home);
|
|
19571
|
-
const realHome = realpathSync12(home), alias =
|
|
19572
|
-
const stat2 =
|
|
19698
|
+
const realHome = realpathSync12(home), alias = join31(realHome, `.${agent}`);
|
|
19699
|
+
const stat2 = lstatSync28(alias, { throwIfNoEntry: false, bigint: true });
|
|
19573
19700
|
if (!stat2?.isSymbolicLink())
|
|
19574
19701
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
19575
|
-
const link = readlinkSync7(alias), targetPath =
|
|
19702
|
+
const link = readlinkSync7(alias), targetPath = resolve23(dirname22(alias), link);
|
|
19576
19703
|
assertSafePath(targetPath);
|
|
19577
|
-
const targetStat =
|
|
19704
|
+
const targetStat = lstatSync28(targetPath, { throwIfNoEntry: false });
|
|
19578
19705
|
if (!targetStat?.isDirectory())
|
|
19579
19706
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
19580
19707
|
const target = realpathSync12(targetPath), inside = relative9(realHome, target);
|
|
19581
|
-
if (!inside || inside === ".." || inside.startsWith(`..${
|
|
19708
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep14}`) || isAbsolute24(inside))
|
|
19582
19709
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
19583
19710
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
19584
19711
|
}
|
|
@@ -19587,15 +19714,15 @@ function rootAliases(home, allowed = false) {
|
|
|
19587
19714
|
return [];
|
|
19588
19715
|
const result = [];
|
|
19589
19716
|
for (const agent of ["claude", "codex"])
|
|
19590
|
-
if (
|
|
19717
|
+
if (lstatSync28(join31(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
19591
19718
|
result.push(readRootAlias(home, agent));
|
|
19592
|
-
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target +
|
|
19719
|
+
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep14) || other.target.startsWith(item.target + sep14))))
|
|
19593
19720
|
throw new Error("Agent root alias targets must not overlap");
|
|
19594
19721
|
return result;
|
|
19595
19722
|
}
|
|
19596
19723
|
function canonicalAgentPath(path, aliases) {
|
|
19597
|
-
const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias +
|
|
19598
|
-
return binding ?
|
|
19724
|
+
const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep14));
|
|
19725
|
+
return binding ? join31(binding.target, relative9(binding.alias, absolute)) : absolute;
|
|
19599
19726
|
}
|
|
19600
19727
|
function disabledCodexSkillPaths(config, aliases) {
|
|
19601
19728
|
const skills = config.skills;
|
|
@@ -19618,15 +19745,15 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
19618
19745
|
} catch {
|
|
19619
19746
|
continue;
|
|
19620
19747
|
}
|
|
19621
|
-
if (!document2.endsWith(`${
|
|
19748
|
+
if (!document2.endsWith(`${sep14}SKILL.md`))
|
|
19622
19749
|
continue;
|
|
19623
19750
|
settings.set(document2, [...settings.get(document2) ?? [], entry.enabled === false]);
|
|
19624
19751
|
}
|
|
19625
|
-
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) =>
|
|
19752
|
+
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) => dirname22(document2));
|
|
19626
19753
|
}
|
|
19627
19754
|
function recheckRootAliases(aliases) {
|
|
19628
19755
|
for (const binding of aliases) {
|
|
19629
|
-
if (!["claude", "codex"].includes(binding.agent) || binding.alias !==
|
|
19756
|
+
if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join31(binding.home, `.${binding.agent}`))
|
|
19630
19757
|
throw new Error("Invalid agent root alias binding");
|
|
19631
19758
|
const current = readRootAlias(binding.home, binding.agent);
|
|
19632
19759
|
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
@@ -19635,11 +19762,11 @@ function recheckRootAliases(aliases) {
|
|
|
19635
19762
|
}
|
|
19636
19763
|
function nativeSourceDigest(path) {
|
|
19637
19764
|
assertSafePath(path);
|
|
19638
|
-
return
|
|
19765
|
+
return lstatSync28(path, { throwIfNoEntry: false }) ? sha4(readNativeBytes(path, 1024 * 1024)) : null;
|
|
19639
19766
|
}
|
|
19640
19767
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
19641
19768
|
assertSafePath(path);
|
|
19642
|
-
const before =
|
|
19769
|
+
const before = lstatSync28(path);
|
|
19643
19770
|
if (!before.isFile() || before.size > maximum)
|
|
19644
19771
|
throw new Error("Unsupported or oversized native skill file");
|
|
19645
19772
|
const descriptor = openSync17(path, constants18.O_RDONLY | constants18.O_NOFOLLOW | constants18.O_NONBLOCK);
|
|
@@ -19655,7 +19782,7 @@ function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
|
19655
19782
|
break;
|
|
19656
19783
|
length += count;
|
|
19657
19784
|
}
|
|
19658
|
-
const after = fstatSync17(descriptor), current =
|
|
19785
|
+
const after = fstatSync17(descriptor), current = lstatSync28(path);
|
|
19659
19786
|
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)
|
|
19660
19787
|
throw new Error("Native skill file changed while reading");
|
|
19661
19788
|
return bytes.subarray(0, length);
|
|
@@ -19667,14 +19794,14 @@ function treeHash(root) {
|
|
|
19667
19794
|
const hash3 = createHash27("sha256");
|
|
19668
19795
|
let bytes = 0, files = 0;
|
|
19669
19796
|
function visit2(path) {
|
|
19670
|
-
const stat2 =
|
|
19797
|
+
const stat2 = lstatSync28(path);
|
|
19671
19798
|
if (stat2.isSymbolicLink())
|
|
19672
19799
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
19673
19800
|
const rel = relative9(root, path);
|
|
19674
19801
|
if (stat2.isDirectory()) {
|
|
19675
19802
|
hash3.update(`d\x00${rel}\x00`);
|
|
19676
19803
|
for (const name of readdirSync10(path).sort())
|
|
19677
|
-
visit2(
|
|
19804
|
+
visit2(join31(path, name));
|
|
19678
19805
|
return;
|
|
19679
19806
|
}
|
|
19680
19807
|
if (!stat2.isFile())
|
|
@@ -19696,20 +19823,20 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
19696
19823
|
const identities = new Set;
|
|
19697
19824
|
const selected = [];
|
|
19698
19825
|
for (const target of manifest.targets) {
|
|
19699
|
-
if (!isAbsolute24(target.projectRoot) ||
|
|
19826
|
+
if (!isAbsolute24(target.projectRoot) || resolve23(target.projectRoot) !== target.projectRoot)
|
|
19700
19827
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
19701
|
-
const projectRoot = canonicalSystemPath(target.projectRoot), normalized2 = relative9(projectRoot,
|
|
19702
|
-
if (!existsSync9(projectRoot) || !
|
|
19828
|
+
const projectRoot = canonicalSystemPath(target.projectRoot), normalized2 = relative9(projectRoot, resolve23(projectRoot, target.path));
|
|
19829
|
+
if (!existsSync9(projectRoot) || !lstatSync28(projectRoot).isDirectory())
|
|
19703
19830
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
19704
19831
|
assertSafePath(projectRoot);
|
|
19705
|
-
if (isAbsolute24(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized2 || normalized2 === ".." || normalized2.startsWith(`..${
|
|
19832
|
+
if (isAbsolute24(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized2 || normalized2 === ".." || normalized2.startsWith(`..${sep14}`) || normalized2 !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
19706
19833
|
throw new Error(`Invalid native migration target: ${target.path}`);
|
|
19707
19834
|
const identity5 = `${target.agent}\x00${canonicalSystemPath(projectRoot)}\x00${normalized2}`;
|
|
19708
19835
|
if (identities.has(identity5))
|
|
19709
19836
|
throw new Error(`Duplicate native migration target: ${target.path}`);
|
|
19710
19837
|
identities.add(identity5);
|
|
19711
|
-
const expected = canonicalSystemPath(
|
|
19712
|
-
const matches = inventory.filter((entry2) => entry2.agent === target.agent &&
|
|
19838
|
+
const expected = canonicalSystemPath(resolve23(target.projectRoot, target.path));
|
|
19839
|
+
const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve23(entry2.path) === expected);
|
|
19713
19840
|
if (matches.length !== 1)
|
|
19714
19841
|
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
19715
19842
|
const entry = matches[0];
|
|
@@ -19728,7 +19855,7 @@ function projectAncestorDirectories(projects) {
|
|
|
19728
19855
|
if (depth >= 100)
|
|
19729
19856
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
19730
19857
|
directories.add(path);
|
|
19731
|
-
const parent =
|
|
19858
|
+
const parent = dirname22(path);
|
|
19732
19859
|
if (parent === path)
|
|
19733
19860
|
break;
|
|
19734
19861
|
path = parent;
|
|
@@ -19737,17 +19864,17 @@ function projectAncestorDirectories(projects) {
|
|
|
19737
19864
|
return [...directories];
|
|
19738
19865
|
}
|
|
19739
19866
|
function isCodexSkillDiscoveryInput(path) {
|
|
19740
|
-
for (let cursor = canonicalSystemPath(path);cursor !==
|
|
19741
|
-
if (ROOTS.some(([agent, root]) => agent === "codex" && cursor.endsWith(
|
|
19867
|
+
for (let cursor = canonicalSystemPath(path);cursor !== dirname22(cursor); cursor = dirname22(cursor)) {
|
|
19868
|
+
if (ROOTS.some(([agent, root]) => agent === "codex" && cursor.endsWith(join31(sep14, root))))
|
|
19742
19869
|
return true;
|
|
19743
19870
|
}
|
|
19744
19871
|
return false;
|
|
19745
19872
|
}
|
|
19746
19873
|
function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
19747
19874
|
home = canonicalSystemPath(home);
|
|
19748
|
-
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute24(options.reviewedCacheAlias) ||
|
|
19875
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute24(options.reviewedCacheAlias) || resolve23(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
19749
19876
|
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
19750
|
-
if (options.disabledVendorPaths?.some((path) => !isAbsolute24(path) ||
|
|
19877
|
+
if (options.disabledVendorPaths?.some((path) => !isAbsolute24(path) || resolve23(path) !== path))
|
|
19751
19878
|
throw new Error("Disabled vendor paths must be exact absolute paths");
|
|
19752
19879
|
const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
|
|
19753
19880
|
const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
|
|
@@ -19755,25 +19882,25 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19755
19882
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
19756
19883
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
19757
19884
|
const rootDefinitions = selectedAgents ? ROOTS.filter(([agent, path]) => agent !== "sumi" && selectedAgents.has(agent) || selectedAgents.has("hermes") && agent === "codex" && path === ".agents/skills") : ROOTS.filter(([agent]) => agent !== "sumi");
|
|
19758
|
-
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(
|
|
19885
|
+
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join31(home, path), aliases)]);
|
|
19759
19886
|
const includesSumi = !selectedAgents || selectedAgents.has("sumi");
|
|
19760
19887
|
if (includesSumi)
|
|
19761
19888
|
for (const path of sumiSkillRoots(home))
|
|
19762
19889
|
roots.push(["sumi", canonicalAgentPath(path, aliases)]);
|
|
19763
|
-
const bridgePaths = Object.entries(AGENT_ADAPTERS).filter(([agent]) => agent !== "sumi" || includesSumi).map(([agent]) => canonicalAgentPath(
|
|
19890
|
+
const bridgePaths = Object.entries(AGENT_ADAPTERS).filter(([agent]) => agent !== "sumi" || includesSumi).map(([agent]) => canonicalAgentPath(join31(nativeAgentRoot(home, agent), CLI_BRIDGE_NAME), aliases));
|
|
19764
19891
|
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
19765
19892
|
for (const [agent, path] of rootDefinitions)
|
|
19766
|
-
roots.push([agent, canonicalAgentPath(
|
|
19893
|
+
roots.push([agent, canonicalAgentPath(join31(project, path), aliases)]);
|
|
19767
19894
|
if (selectedAgents?.has("sumi")) {
|
|
19768
19895
|
for (const path of sumiSkillRoots(home, project))
|
|
19769
19896
|
roots.push(["sumi", canonicalAgentPath(path, aliases)]);
|
|
19770
19897
|
for (const directory2 of [".claude", ".agents"])
|
|
19771
19898
|
for (const name of ["skill", "skills"])
|
|
19772
|
-
roots.push(["sumi", canonicalAgentPath(
|
|
19899
|
+
roots.push(["sumi", canonicalAgentPath(join31(project, directory2, name), aliases)]);
|
|
19773
19900
|
}
|
|
19774
19901
|
}
|
|
19775
19902
|
const installationInputs = options.codexInstallationInputRoots ?? [];
|
|
19776
|
-
if (installationInputs.some((input) =>
|
|
19903
|
+
if (installationInputs.some((input) => resolve23(input) !== input || realpathSync12(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep14) || input.startsWith(path + sep14)))))
|
|
19777
19904
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
19778
19905
|
const entries = [], seen = new Set;
|
|
19779
19906
|
const cacheScans = new Map, maxAliasProofEntries = 1e4;
|
|
@@ -19787,19 +19914,19 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19787
19914
|
}
|
|
19788
19915
|
let reviewedCacheAliasFound = false;
|
|
19789
19916
|
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
19790
|
-
const link = readlinkSync7(path), lexicalTarget =
|
|
19791
|
-
if (isAbsolute24(link) ? link !== lexicalTarget :
|
|
19917
|
+
const link = readlinkSync7(path), lexicalTarget = resolve23(parent, link);
|
|
19918
|
+
if (isAbsolute24(link) ? link !== lexicalTarget : dirname22(link) !== ".")
|
|
19792
19919
|
return false;
|
|
19793
19920
|
const target = canonicalSystemPath(lexicalTarget);
|
|
19794
|
-
if (
|
|
19921
|
+
if (dirname22(target) !== parent)
|
|
19795
19922
|
return false;
|
|
19796
|
-
if (!
|
|
19923
|
+
if (!lstatSync28(target, { throwIfNoEntry: false })?.isDirectory())
|
|
19797
19924
|
return false;
|
|
19798
19925
|
const scan = cacheScans.get(target);
|
|
19799
19926
|
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
19800
19927
|
return false;
|
|
19801
19928
|
if (scan.hasSkills) {
|
|
19802
|
-
const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target +
|
|
19929
|
+
const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep14)));
|
|
19803
19930
|
if (path === reviewedCacheAlias && targetSkills.length > 0)
|
|
19804
19931
|
reviewedCacheAliasFound = true;
|
|
19805
19932
|
if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
|
|
@@ -19814,11 +19941,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19814
19941
|
admit(path);
|
|
19815
19942
|
const scan = { complete: true, hasSkills: false, entries: 1 };
|
|
19816
19943
|
assertSafePath(path);
|
|
19817
|
-
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${
|
|
19944
|
+
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${sep14}`)))
|
|
19818
19945
|
return scan;
|
|
19819
19946
|
if (!existsSync9(path))
|
|
19820
19947
|
return scan;
|
|
19821
|
-
const stat2 =
|
|
19948
|
+
const stat2 = lstatSync28(path);
|
|
19822
19949
|
if (stat2.isFile()) {
|
|
19823
19950
|
if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
|
|
19824
19951
|
throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
|
|
@@ -19826,21 +19953,21 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19826
19953
|
}
|
|
19827
19954
|
if (!stat2.isDirectory())
|
|
19828
19955
|
throw new Error(`Unsupported native discovery entry: ${path}`);
|
|
19829
|
-
if (existsSync9(
|
|
19956
|
+
if (existsSync9(join31(path, "SKILL.md"))) {
|
|
19830
19957
|
scan.hasSkills = true;
|
|
19831
19958
|
if (seen.has(path))
|
|
19832
19959
|
return scan;
|
|
19833
19960
|
seen.add(path);
|
|
19834
19961
|
let managed = false;
|
|
19835
|
-
const marker =
|
|
19962
|
+
const marker = join31(path, ".hasna-skills.json");
|
|
19836
19963
|
if (existsSync9(marker)) {
|
|
19837
19964
|
try {
|
|
19838
|
-
managed = JSON.parse(
|
|
19965
|
+
managed = JSON.parse(readFileSync19(marker, "utf8")).managedBy === "@hasna/skills";
|
|
19839
19966
|
} catch {}
|
|
19840
19967
|
}
|
|
19841
19968
|
const bridge = (!vendor || agent === "sumi") && isOwnedCliBridge(path, bridgePaths);
|
|
19842
|
-
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target +
|
|
19843
|
-
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" || isCodexSkillDiscoveryInput(path) ? { codexHome: process.env.CODEX_HOME ??
|
|
19969
|
+
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep14));
|
|
19970
|
+
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" || isCodexSkillDiscoveryInput(path) ? { codexHome: process.env.CODEX_HOME ?? join31(home, ".codex") } : {}, ...agent === "codex" && path.startsWith(canonicalAgentPath(join31(home, ".codex", "skills", ".system"), aliases) + sep14) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve23(home) } : {}, ...rootAlias ? { rootAlias } : {} });
|
|
19844
19971
|
return scan;
|
|
19845
19972
|
}
|
|
19846
19973
|
if (depth > (agent === "sumi" ? 32 : vendor ? 32 : 3))
|
|
@@ -19849,7 +19976,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19849
19976
|
try {
|
|
19850
19977
|
let child2;
|
|
19851
19978
|
while ((child2 = directory2.readSync()) !== null) {
|
|
19852
|
-
admit(
|
|
19979
|
+
admit(join31(path, child2.name));
|
|
19853
19980
|
children.push(child2);
|
|
19854
19981
|
}
|
|
19855
19982
|
} finally {
|
|
@@ -19859,15 +19986,15 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19859
19986
|
if (pluginCache)
|
|
19860
19987
|
children.sort((a, b) => Number(a.isSymbolicLink()) - Number(b.isSymbolicLink()));
|
|
19861
19988
|
for (const { name } of children) {
|
|
19862
|
-
if (name === "node_modules")
|
|
19989
|
+
if (name === "node_modules" && !(agent === "codex" && (!pluginCache || codexPluginCacheSkillTree(join31(path, name), depth + 1))))
|
|
19863
19990
|
continue;
|
|
19864
|
-
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && agent !== "sumi" && !(options.guardHermes && `${path}${
|
|
19991
|
+
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && agent !== "sumi" && !(options.guardHermes && `${path}${sep14}`.includes(`${sep14}.agents${sep14}skills${sep14}`)))
|
|
19865
19992
|
continue;
|
|
19866
19993
|
const isVendor = vendor || name === ".system";
|
|
19867
19994
|
if (isVendor && !options.includeVendor)
|
|
19868
19995
|
continue;
|
|
19869
|
-
const child2 =
|
|
19870
|
-
if (isVendor &&
|
|
19996
|
+
const child2 = join31(path, name);
|
|
19997
|
+
if (isVendor && lstatSync28(child2, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
19871
19998
|
const target = statSync11(child2, { throwIfNoEntry: false });
|
|
19872
19999
|
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child2, path)) {
|
|
19873
20000
|
scan.entries++;
|
|
@@ -19884,7 +20011,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19884
20011
|
return scan;
|
|
19885
20012
|
}
|
|
19886
20013
|
function scanRoot(agent, path, vendor = false, pluginCache = false, configuredRoot = false) {
|
|
19887
|
-
if (agent === "hermes" || options.guardHermes && `${path}${
|
|
20014
|
+
if (agent === "hermes" || options.guardHermes && `${path}${sep14}`.includes(`${sep14}.agents${sep14}skills${sep14}`))
|
|
19888
20015
|
assertNoHermesLegacyShadow(path);
|
|
19889
20016
|
if (!visit2(agent, path, vendor, 0, pluginCache, false, configuredRoot).complete)
|
|
19890
20017
|
throw new Error(`Native skill discovery limit exceeded; review this root before continuing: ${path}`);
|
|
@@ -19893,11 +20020,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19893
20020
|
scanRoot(agent, path);
|
|
19894
20021
|
if (options.includeVendor) {
|
|
19895
20022
|
for (const agent of ["codex", "claude"].filter(includesAgent))
|
|
19896
|
-
scanRoot(agent, canonicalAgentPath(
|
|
20023
|
+
scanRoot(agent, canonicalAgentPath(join31(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
19897
20024
|
if (includesAgent("claude"))
|
|
19898
|
-
scanRoot("claude", canonicalAgentPath(
|
|
20025
|
+
scanRoot("claude", canonicalAgentPath(join31(home, ".claude", "plugins", "synced"), aliases), true);
|
|
19899
20026
|
if (includesAgent("gemini"))
|
|
19900
|
-
scanRoot("gemini",
|
|
20027
|
+
scanRoot("gemini", join31(home, ".gemini", "extensions"), true);
|
|
19901
20028
|
}
|
|
19902
20029
|
const configured = options.configured ? INTEGRATION_AGENTS.filter(includesAgent).flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
19903
20030
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
@@ -19910,7 +20037,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
19910
20037
|
}
|
|
19911
20038
|
function readOptional(path) {
|
|
19912
20039
|
assertSafePath(path);
|
|
19913
|
-
return existsSync9(path) ?
|
|
20040
|
+
return existsSync9(path) ? readFileSync19(path, "utf8") : null;
|
|
19914
20041
|
}
|
|
19915
20042
|
function jsonObject(text2, path) {
|
|
19916
20043
|
if (text2 === null)
|
|
@@ -20010,19 +20137,19 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
20010
20137
|
function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
20011
20138
|
const bundled = disableCodexBundledSkills(text2);
|
|
20012
20139
|
let result = normalizeCodexInlinePathConfig(bundled);
|
|
20013
|
-
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [
|
|
20140
|
+
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve23(bridgePath), resolve23(join31(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
|
|
20014
20141
|
const previousConfig = Bun.TOML.parse(bundled).skills?.config;
|
|
20015
20142
|
if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
|
|
20016
20143
|
throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
|
|
20017
20144
|
const bridgeNeedsRepair = previousConfig?.some((entry) => entry.enabled === false && bridgeSelector(entry));
|
|
20018
20145
|
const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
|
|
20019
20146
|
for (const skill of selections) {
|
|
20020
|
-
const path =
|
|
20147
|
+
const path = join31(skill.path, "SKILL.md");
|
|
20021
20148
|
let found = false;
|
|
20022
20149
|
result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
|
|
20023
20150
|
const parsed = Bun.TOML.parse(section);
|
|
20024
20151
|
const declared = parsed.skills?.config?.[0];
|
|
20025
|
-
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [
|
|
20152
|
+
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve23(path), resolve23(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
|
|
20026
20153
|
return section;
|
|
20027
20154
|
found = true;
|
|
20028
20155
|
return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
|
|
@@ -20043,8 +20170,8 @@ enabled = false
|
|
|
20043
20170
|
return result;
|
|
20044
20171
|
}
|
|
20045
20172
|
function assertCodexInstallationInputs(home, projectDirs, inputs) {
|
|
20046
|
-
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory2) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
20047
|
-
if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input +
|
|
20173
|
+
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory2) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => resolve23(directory2, path)));
|
|
20174
|
+
if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input + sep14) || input.startsWith(root + sep14))))
|
|
20048
20175
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
20049
20176
|
}
|
|
20050
20177
|
function planAgentIntegration(options) {
|
|
@@ -20054,7 +20181,7 @@ function planAgentIntegration(options) {
|
|
|
20054
20181
|
throw new Error("Explicit Codex skill denials require a fresh native catalog and discovery review");
|
|
20055
20182
|
const home = options.home ?? homedir2(), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20056
20183
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
20057
|
-
const policyPath =
|
|
20184
|
+
const policyPath = join31(dataDir2, "agent-policy.json");
|
|
20058
20185
|
assertSafePath(policyPath);
|
|
20059
20186
|
const priorSnapshot = readManagedSkillPolicySnapshot(dataDir2), previousPolicy = priorSnapshot?.text ?? null, policy = priorSnapshot?.value ?? {};
|
|
20060
20187
|
if (policy.bridge !== undefined && (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge)))
|
|
@@ -20085,34 +20212,42 @@ function planAgentIntegration(options) {
|
|
|
20085
20212
|
const executable2 = discoveryExecutable(agent) ?? (prior ? recordedDiscoveryExecutable(agent, prior) : null);
|
|
20086
20213
|
executables.set(agent, executable2);
|
|
20087
20214
|
const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? { ...policy.bridge.discovery[agent], ...agent === "codex" ? { codexDisabledPluginSkills: policy.bridge.codexPluginSkills ?? [] } : {} } : undefined;
|
|
20088
|
-
|
|
20089
|
-
|
|
20090
|
-
|
|
20215
|
+
try {
|
|
20216
|
+
if (retainedReview) {
|
|
20217
|
+
assertRetainedReviewState(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
|
|
20218
|
+
retainedAgents.push(agent);
|
|
20219
|
+
}
|
|
20220
|
+
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
|
|
20221
|
+
} catch (error) {
|
|
20222
|
+
if (isDiscoveryRootUnresolved(error) || error instanceof SumiPathResolverError || error instanceof NativeSkillCopiesDriftError)
|
|
20223
|
+
throw error;
|
|
20224
|
+
if (!retainedReview)
|
|
20225
|
+
throw error;
|
|
20226
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message.replace(/^NATIVE_SKILL_DRIFT: /, "")}. This agent reuses a saved discovery review and that review no longer verifies, so a fresh explicit review is required: skills hook install --agent ${agent} --discovery-inputs <review> --apply.`);
|
|
20091
20227
|
}
|
|
20092
|
-
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
|
|
20093
20228
|
});
|
|
20094
|
-
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(
|
|
20229
|
+
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases)) : null;
|
|
20095
20230
|
const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
|
|
20096
20231
|
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) });
|
|
20097
|
-
const codexPathConfig = options.agents.includes("codex") ? disableCodexSkills(codexConfigBefore ?? "", nativeSkills, aliases, canonicalAgentPath(
|
|
20232
|
+
const codexPathConfig = options.agents.includes("codex") ? disableCodexSkills(codexConfigBefore ?? "", nativeSkills, aliases, canonicalAgentPath(join31(home, ".codex/skills", CLI_BRIDGE_NAME), aliases)) : undefined;
|
|
20098
20233
|
const plannedCodexRules = codexPathConfig === undefined ? undefined : Bun.TOML.parse(codexPathConfig).skills?.config ?? [];
|
|
20099
20234
|
const observedNativeSources = [];
|
|
20100
|
-
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) =>
|
|
20235
|
+
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join31(entry.path, "SKILL.md")), canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), resolve23(options.projectDir ?? home), (path) => {
|
|
20101
20236
|
const bytes = readNativeBytes(path, 1024 * 1024);
|
|
20102
20237
|
observedNativeSources.push({ path, sha256: sha4(bytes) });
|
|
20103
20238
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
20104
20239
|
}, codexConfig.skills?.config ?? [], codexConfig.plugins, plannedCodexRules, options.codexSkillDenials) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
|
|
20105
|
-
const cache = canonicalAgentPath(
|
|
20240
|
+
const cache = canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases);
|
|
20106
20241
|
const retired = (policy.bridge?.codexPluginSkills ?? []).filter((control) => absentDisabledCodexPluginParent(cache, control.pluginParent, policy.bridge.codexPluginSkills, codexConfig.skills?.config ?? []));
|
|
20107
20242
|
for (const denial of options.codexSkillDenials ?? []) {
|
|
20108
20243
|
const control = codexPluginControls.skills.find((item) => item.name === denial.name);
|
|
20109
20244
|
if (!control)
|
|
20110
20245
|
throw new Error("Explicit Codex denial did not resolve a reviewed identity");
|
|
20111
|
-
const root =
|
|
20246
|
+
const root = join31(control.pluginParent, relative9(control.pluginParent, denial.path).split(sep14)[0]);
|
|
20112
20247
|
const review = options.discoveryInputs.agents.find((item) => item.agent === "codex");
|
|
20113
|
-
for (const path of [
|
|
20248
|
+
for (const path of [join31(root, ".app.json"), join31(root, ".mcp.json"), join31(root, "hooks")])
|
|
20114
20249
|
observedNativeSources.push({ path, sha256: nativeSourceDigest(path) });
|
|
20115
|
-
const files = [
|
|
20250
|
+
const files = [join31(root, ".codex-plugin/plugin.json"), ...control.appSha256 ? [join31(root, ".app.json")] : [], ...control.mcpSha256 ? [join31(root, ".mcp.json")] : []];
|
|
20116
20251
|
for (const path of files)
|
|
20117
20252
|
if (!review?.sources.some((source) => source.path === path && source.format === undefined && source.fields === undefined && [undefined, "bytes"].includes(source.hashMode) && source.sha256 === sha4(readNativeBytes(path, 1024 * 1024))))
|
|
20118
20253
|
throw new Error("Explicit Codex denial requires current full manifest and capability discovery witnesses");
|
|
@@ -20121,12 +20256,12 @@ function planAgentIntegration(options) {
|
|
|
20121
20256
|
for (const discovery of discoveries)
|
|
20122
20257
|
if (discovery.agent === "codex" && discovery.method === "reviewed")
|
|
20123
20258
|
discovery.codexDisabledPluginSkills = codexPluginSkills;
|
|
20124
|
-
if (options.agents.includes("codex") && !options.codexNativeCatalog && codexPluginSkills.length && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(
|
|
20259
|
+
if (options.agents.includes("codex") && !options.codexNativeCatalog && codexPluginSkills.length && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), codexPluginSkills, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? [])) {
|
|
20125
20260
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; provide a fresh native catalog and discovery review");
|
|
20126
20261
|
}
|
|
20127
20262
|
const codexInactivePlugins = codexPluginControls.inactivePlugins;
|
|
20128
20263
|
const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
|
|
20129
|
-
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot +
|
|
20264
|
+
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep14) || input.sourceRoot.startsWith(root + sep14)) && codexDiscovery.sources.some((source) => source.path === join31(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
|
|
20130
20265
|
assertCodexInstallationInputs(home, [options.projectDir ?? home], codexPluginSourceInputs.map((input) => input.sourceRoot));
|
|
20131
20266
|
const changes = [];
|
|
20132
20267
|
let observedSettings;
|
|
@@ -20134,19 +20269,19 @@ function planAgentIntegration(options) {
|
|
|
20134
20269
|
if (!INTEGRATION_AGENTS.includes(agent))
|
|
20135
20270
|
throw new Error(`Unsupported agent: ${agent}`);
|
|
20136
20271
|
const { command, profileId: profileId2 } = bindings.get(agent);
|
|
20137
|
-
const bridgePath = canonicalAgentPath(
|
|
20272
|
+
const bridgePath = canonicalAgentPath(join31(nativeAgentRoot(home, agent, options.projectDir ?? process.cwd()), CLI_BRIDGE_NAME), aliases);
|
|
20138
20273
|
assertSafePath(bridgePath);
|
|
20139
20274
|
if (existsSync9(bridgePath) && !isOwnedCliBridge(bridgePath, [bridgePath]))
|
|
20140
20275
|
throw new Error(`Refusing to overwrite an unrecognized or modified Skills bridge: ${bridgePath}`);
|
|
20141
20276
|
for (const [name, after2] of Object.entries(CLI_BRIDGE_FILES)) {
|
|
20142
|
-
const path2 =
|
|
20277
|
+
const path2 = join31(bridgePath, name), before2 = readOptional(path2);
|
|
20143
20278
|
if (before2 !== after2)
|
|
20144
20279
|
changes.push({ path: path2, before: before2, after: after2 });
|
|
20145
20280
|
}
|
|
20146
20281
|
const path = canonicalAgentPath(nativeAgentConfig(home, agent, options.projectDir ?? process.cwd()), aliases);
|
|
20147
20282
|
const before = readOptional(path);
|
|
20148
20283
|
if (agent === "hermes") {
|
|
20149
|
-
const supervisorPath =
|
|
20284
|
+
const supervisorPath = join31(dataDir2, "agent-hooks", "hermes.js"), supervisorBefore = readOptional(supervisorPath), supervisorAfter = renderHermesSupervisor(command, profileId2);
|
|
20150
20285
|
const priorCommand = policy.bridge?.commands?.hermes, priorProfile = policy.bridge?.profiles?.hermes ?? policy.profileId;
|
|
20151
20286
|
const previous = policy.bridge?.supervisors?.hermes;
|
|
20152
20287
|
if (supervisorBefore !== null && supervisorBefore !== supervisorAfter && !(previous?.path === supervisorPath && typeof priorCommand === "string" && typeof priorProfile === "string" && supervisorBefore === renderHermesSupervisor(priorCommand, priorProfile)))
|
|
@@ -20157,7 +20292,7 @@ function planAgentIntegration(options) {
|
|
|
20157
20292
|
const after2 = configureHermesHooks(before, supervisor, previous);
|
|
20158
20293
|
if (before !== after2)
|
|
20159
20294
|
changes.push({ path, before, after: after2 });
|
|
20160
|
-
const optOut =
|
|
20295
|
+
const optOut = join31(home, ".hermes", HERMES_OPT_OUT), optOutBefore = readOptional(optOut);
|
|
20161
20296
|
if (optOutBefore === null)
|
|
20162
20297
|
changes.push({ path: optOut, before: null, after: `Managed by @hasna/skills: bundled native skill reseeding is disabled.
|
|
20163
20298
|
` });
|
|
@@ -20168,7 +20303,7 @@ function planAgentIntegration(options) {
|
|
|
20168
20303
|
if (config.permissions !== undefined && (!Array.isArray(config.permissions) || config.permissions.some((rule) => !rule || typeof rule.action !== "string" || typeof rule.resource !== "string" || !["allow", "deny", "ask"].includes(rule.effect))))
|
|
20169
20304
|
throw new Error("Expected Sumi native permission rules");
|
|
20170
20305
|
config.permissions = [...(config.permissions ?? []).filter((rule) => rule.action !== "skill"), ...SUMI_SKILL_PERMISSIONS];
|
|
20171
|
-
const pluginPath =
|
|
20306
|
+
const pluginPath = join31(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
20172
20307
|
const pluginAfter = renderSumiPlugin(command, profileId2);
|
|
20173
20308
|
const priorCommand = policy.bridge?.commands?.sumi, priorProfile = policy.bridge?.profiles?.sumi ?? policy.profileId;
|
|
20174
20309
|
if (pluginBefore !== null && pluginBefore !== pluginAfter && !(typeof priorCommand === "string" && typeof priorProfile === "string" && isManagedSumiPlugin(pluginBefore, priorCommand, priorProfile)))
|
|
@@ -20180,7 +20315,7 @@ function planAgentIntegration(options) {
|
|
|
20180
20315
|
if (!permission || typeof permission !== "object" || Array.isArray(permission))
|
|
20181
20316
|
throw new Error("Expected OpenCode permission object");
|
|
20182
20317
|
config.permission = { ...permission, skill: { "*": "deny", [CLI_BRIDGE_NAME]: "allow" } };
|
|
20183
|
-
const pluginPath =
|
|
20318
|
+
const pluginPath = join31(home, ".config", "opencode", "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
20184
20319
|
const pluginAfter = renderOpenCodePlugin(command, profileId2);
|
|
20185
20320
|
const priorCommand = policy.bridge?.commands?.opencode;
|
|
20186
20321
|
const ownedPrevious = typeof priorCommand === "string" && typeof policy.profileId === "string" && pluginBefore === renderOpenCodePlugin(priorCommand, policy.bridge?.profiles?.opencode ?? policy.profileId);
|
|
@@ -20197,7 +20332,7 @@ function planAgentIntegration(options) {
|
|
|
20197
20332
|
if (before !== after)
|
|
20198
20333
|
changes.push({ path, before, after });
|
|
20199
20334
|
if (agent === "codex") {
|
|
20200
|
-
const configPath = canonicalAgentPath(
|
|
20335
|
+
const configPath = canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
|
|
20201
20336
|
if (previous !== codexConfigBefore)
|
|
20202
20337
|
throw new Error(`Configuration changed during planning: ${configPath}`);
|
|
20203
20338
|
const next = disableReviewedCodexPluginNames(codexPathConfig, codexPluginSkills);
|
|
@@ -20209,28 +20344,28 @@ function planAgentIntegration(options) {
|
|
|
20209
20344
|
}
|
|
20210
20345
|
}
|
|
20211
20346
|
}
|
|
20212
|
-
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha4(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(
|
|
20347
|
+
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha4(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join31(home, ".codex/config.toml"), aliases))?.before === null ? null : sha4(readOptional(canonicalAgentPath(join31(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview;
|
|
20213
20348
|
const discoveryAfter = discoveries.map((binding) => {
|
|
20214
20349
|
const rebound = rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after])));
|
|
20215
20350
|
if (binding.agent !== "codex")
|
|
20216
20351
|
return rebound;
|
|
20217
20352
|
const { codexInstallationInputs: previousInputs, ...current } = rebound;
|
|
20218
20353
|
const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
|
|
20219
|
-
const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory2) => inputs.some((root) => root.startsWith(directory2.path +
|
|
20354
|
+
const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory2) => inputs.some((root) => root.startsWith(directory2.path + sep14))).map((directory2) => directory2.path), inputs) : previousInputs.directories;
|
|
20220
20355
|
const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: codexPluginSkillReview.version, catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
|
|
20221
|
-
return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(
|
|
20356
|
+
return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(join31(home, ".codex/config.toml"), aliases))?.after ?? codexConfigBefore ?? "").skills?.config ?? []);
|
|
20222
20357
|
});
|
|
20223
20358
|
const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
|
|
20224
20359
|
...policy.bridge,
|
|
20225
20360
|
version: CLI_BRIDGE_VERSION,
|
|
20226
20361
|
digest: CLI_BRIDGE_DIGEST,
|
|
20227
20362
|
agents: [...new Set([...priorAgents, ...options.agents])].sort(),
|
|
20228
|
-
home:
|
|
20363
|
+
home: resolve23(home),
|
|
20229
20364
|
includeVendor: true,
|
|
20230
|
-
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path:
|
|
20365
|
+
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join31(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha4(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
|
|
20231
20366
|
commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
|
|
20232
20367
|
profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
|
|
20233
|
-
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(
|
|
20368
|
+
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join31(home, ".codex", "skills", ".system"), aliases) + sep14)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
|
|
20234
20369
|
rootAliases: aliases,
|
|
20235
20370
|
...options.codexNativeCatalog || codexPluginSkills.length || codexInactivePlugins.length || codexPluginSourceInputs.length ? { codexPluginSkills, codexInactivePlugins, codexPluginSkillReview } : {},
|
|
20236
20371
|
discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
|
|
@@ -20273,7 +20408,7 @@ function projectedHookAcl(path, observed) {
|
|
|
20273
20408
|
const identity5 = observed.isDirectory() ? (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}` : (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}:${stat2.nlink}:${stat2.size}:${stat2.mtimeNs}`;
|
|
20274
20409
|
const fd = openSync17(path, constants18.O_RDONLY | constants18.O_NOFOLLOW | constants18.O_NONBLOCK | (observed.isDirectory() ? constants18.O_DIRECTORY : 0));
|
|
20275
20410
|
try {
|
|
20276
|
-
if (identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || !hasTrustedDarwinAcl(fd, true) || identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || identity5(
|
|
20411
|
+
if (identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || !hasTrustedDarwinAcl(fd, true) || identity5(fstatSync17(fd, { bigint: true })) !== identity5(observed) || identity5(lstatSync28(path, { bigint: true })) !== identity5(observed))
|
|
20277
20412
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20278
20413
|
} finally {
|
|
20279
20414
|
closeSync17(fd);
|
|
@@ -20281,23 +20416,23 @@ function projectedHookAcl(path, observed) {
|
|
|
20281
20416
|
}
|
|
20282
20417
|
function projectedHookExecutable(command, expectedResolved) {
|
|
20283
20418
|
try {
|
|
20284
|
-
if (!isAbsolute24(command) || /[\0\r\n]/.test(command) ||
|
|
20419
|
+
if (!isAbsolute24(command) || /[\0\r\n]/.test(command) || resolve23(command) !== command)
|
|
20285
20420
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20286
20421
|
command = canonicalSystemPath(command);
|
|
20287
20422
|
const parents2 = (file = command) => {
|
|
20288
20423
|
const result = [];
|
|
20289
|
-
for (let path =
|
|
20290
|
-
const stat2 =
|
|
20424
|
+
for (let path = dirname22(file);; path = dirname22(path)) {
|
|
20425
|
+
const stat2 = lstatSync28(path, { bigint: true });
|
|
20291
20426
|
if (!stat2.isDirectory() || !trustedAncestorOwner(stat2.uid) || (stat2.mode & 0o022n) !== 0n && !(stat2.uid === 0n && (stat2.mode & 0o1000n) !== 0n))
|
|
20292
20427
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20293
20428
|
projectedHookAcl(path, stat2);
|
|
20294
20429
|
result.push(`${path}:${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}`);
|
|
20295
|
-
if (
|
|
20430
|
+
if (dirname22(path) === path)
|
|
20296
20431
|
break;
|
|
20297
20432
|
}
|
|
20298
20433
|
return result;
|
|
20299
20434
|
};
|
|
20300
|
-
const parentIdentity = parents2(), link =
|
|
20435
|
+
const parentIdentity = parents2(), link = lstatSync28(command, { bigint: true });
|
|
20301
20436
|
if (link.uid !== BigInt(process.getuid()) || !link.isFile() && !link.isSymbolicLink())
|
|
20302
20437
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20303
20438
|
const linkIdentity = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.mode}:${stat2.uid}:${stat2.ctimeNs}`;
|
|
@@ -20317,18 +20452,18 @@ function projectedHookExecutable(command, expectedResolved) {
|
|
|
20317
20452
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20318
20453
|
const path = target.pinned.runtime;
|
|
20319
20454
|
assertPinnedLauncherRuntimeSafe(path);
|
|
20320
|
-
if (
|
|
20455
|
+
if (resolve23(path) !== path)
|
|
20321
20456
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20322
|
-
runtime = { path, parents: parents2(path), stat:
|
|
20457
|
+
runtime = { path, parents: parents2(path), stat: lstatSync28(path, { bigint: true }) };
|
|
20323
20458
|
if (!runtime.stat.isFile())
|
|
20324
20459
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20325
20460
|
projectedHookAcl(path, runtime.stat);
|
|
20326
20461
|
}
|
|
20327
20462
|
const resolvedParentIdentity = parents2(resolved);
|
|
20328
|
-
const manifest = snapshot(
|
|
20463
|
+
const manifest = snapshot(join31(dirname22(dirname22(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
20329
20464
|
projectedHookAcl(manifest.file, manifest.stat);
|
|
20330
20465
|
const pkg = JSON.parse(manifest.text);
|
|
20331
|
-
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !==
|
|
20466
|
+
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join31(dirname22(manifest.file), "bin/index.js"))
|
|
20332
20467
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20333
20468
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
20334
20469
|
projectedHookAcl(cli.file, cli.stat);
|
|
@@ -20337,7 +20472,7 @@ function projectedHookExecutable(command, expectedResolved) {
|
|
|
20337
20472
|
const recheck = () => {
|
|
20338
20473
|
try {
|
|
20339
20474
|
const again = resolveLauncherCommand(command);
|
|
20340
|
-
if (again.entry !== resolved || again.physical !== target.physical || linkIdentity(
|
|
20475
|
+
if (again.entry !== resolved || again.physical !== target.physical || linkIdentity(lstatSync28(command, { bigint: true })) !== linkIdentity(link) || !direct() || !isDeepStrictEqual8(parents2(), parentIdentity) || !isDeepStrictEqual8(parents2(resolved), resolvedParentIdentity))
|
|
20341
20476
|
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
20342
20477
|
if (launcher) {
|
|
20343
20478
|
if (!isDeepStrictEqual8(parents2(target.physical), launcherParentIdentity))
|
|
@@ -20377,8 +20512,8 @@ function planClaudeManagedHookProjection(options) {
|
|
|
20377
20512
|
} catch {
|
|
20378
20513
|
hookProjectionRefusal("TARGET_INVALID");
|
|
20379
20514
|
}
|
|
20380
|
-
const home =
|
|
20381
|
-
const policyPath =
|
|
20515
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20516
|
+
const policyPath = join31(dataDir2, "agent-policy.json");
|
|
20382
20517
|
let policyBytes, settingsBytes, settingsPath, command, profile;
|
|
20383
20518
|
try {
|
|
20384
20519
|
policyBytes = readNativeBytes(policyPath, 1024 * 1024);
|
|
@@ -20386,7 +20521,7 @@ function planClaudeManagedHookProjection(options) {
|
|
|
20386
20521
|
if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
|
|
20387
20522
|
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
20388
20523
|
const binding = snapshot2.value.bridge;
|
|
20389
|
-
settingsPath = canonicalAgentPath(
|
|
20524
|
+
settingsPath = canonicalAgentPath(join31(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
|
|
20390
20525
|
settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
|
|
20391
20526
|
command = binding?.commands?.claude;
|
|
20392
20527
|
profile = binding?.profiles?.claude;
|
|
@@ -20472,7 +20607,7 @@ function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
|
20472
20607
|
const canonical9 = (value) => canonicalAgentPath(value, aliases);
|
|
20473
20608
|
const containsSettings = (root) => {
|
|
20474
20609
|
const distance = relative9(canonical9(root), path);
|
|
20475
|
-
return distance === "" || distance !== ".." && !distance.startsWith(`..${
|
|
20610
|
+
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep14}`) && !isAbsolute24(distance);
|
|
20476
20611
|
};
|
|
20477
20612
|
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || binding.agent !== "sumi" && canonical9(nativeAgentConfig(home, binding.agent)) === path || binding.sources.some((source) => canonical9(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory2) => containsSettings(directory2.path))).map((binding) => binding.agent);
|
|
20478
20613
|
}
|
|
@@ -20490,8 +20625,8 @@ function verifyCoordinatedDiscovery(binding, home, aliases, executable2) {
|
|
|
20490
20625
|
}
|
|
20491
20626
|
}
|
|
20492
20627
|
function planClaudeHookEventsUpdate(options) {
|
|
20493
|
-
const home =
|
|
20494
|
-
const policyPath =
|
|
20628
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20629
|
+
const policyPath = join31(dataDir2, "agent-policy.json");
|
|
20495
20630
|
assertSafePath(policyPath);
|
|
20496
20631
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20497
20632
|
if (!snapshot2)
|
|
@@ -20502,9 +20637,9 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
20502
20637
|
}
|
|
20503
20638
|
const aliases = bridge.rootAliases ?? [];
|
|
20504
20639
|
recheckRootAliases(aliases);
|
|
20505
|
-
const path = canonicalAgentPath(
|
|
20640
|
+
const path = canonicalAgentPath(join31(home, ".claude", "settings.json"), aliases);
|
|
20506
20641
|
assertSafePath(path);
|
|
20507
|
-
const before =
|
|
20642
|
+
const before = lstatSync28(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
20508
20643
|
if ((before === null ? "absent" : sha4(before)) !== options.expectedSettingsSha256)
|
|
20509
20644
|
throw new Error("Claude settings preimage changed");
|
|
20510
20645
|
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
@@ -20560,7 +20695,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
20560
20695
|
}
|
|
20561
20696
|
function atomicWrite2(path, content) {
|
|
20562
20697
|
assertSafePath(path);
|
|
20563
|
-
mkdirSync8(
|
|
20698
|
+
mkdirSync8(dirname22(path), { recursive: true, mode: 448 });
|
|
20564
20699
|
const temporary = `${path}.skills-${randomUUID4()}`;
|
|
20565
20700
|
try {
|
|
20566
20701
|
writeFileSync6(temporary, content, { mode: 384, flag: "wx" });
|
|
@@ -20575,7 +20710,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
20575
20710
|
throw new Error("Invalid Codex witness target version");
|
|
20576
20711
|
if (options.targetClaudeVersion !== undefined && (options.agent !== "claude" || ![3, 4].includes(options.targetClaudeVersion)))
|
|
20577
20712
|
throw new Error("Invalid Claude witness target version");
|
|
20578
|
-
const home =
|
|
20713
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20579
20714
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20580
20715
|
if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
|
|
20581
20716
|
throw new Error("Managed policy preimage changed");
|
|
@@ -20584,7 +20719,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
20584
20719
|
const binding = policy.bridge?.discovery?.[options.agent];
|
|
20585
20720
|
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
20586
20721
|
throw new Error("Settings witness upgrade requires an existing reviewed native binding");
|
|
20587
|
-
const configPath = canonicalAgentPath(options.agent === "sumi" ? nativeAgentConfig(home, "sumi", options.projectDir ?? process.cwd()) :
|
|
20722
|
+
const configPath = canonicalAgentPath(options.agent === "sumi" ? nativeAgentConfig(home, "sumi", options.projectDir ?? process.cwd()) : join31(home, options.agent === "claude" ? ".claude/settings.json" : ".codex/config.toml"), aliases);
|
|
20588
20723
|
const settings = readOptional(configPath);
|
|
20589
20724
|
if (settings === null || sha4(settings) !== options.expectedSettingsSha256)
|
|
20590
20725
|
throw new Error("Native settings preimage changed");
|
|
@@ -20635,12 +20770,12 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
20635
20770
|
const replacement = { ...binding, sources: rebound };
|
|
20636
20771
|
verifyAgentDiscovery(replacement, undefined, aliases);
|
|
20637
20772
|
const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
20638
|
-
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path:
|
|
20773
|
+
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text, after }], nativeSkills: [], observedPolicy: { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], ...policy.bridge.discoveryExecutables ? { discoveryExecutables: policy.bridge.discoveryExecutables } : {}, 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 })) } };
|
|
20639
20774
|
}
|
|
20640
20775
|
function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
20641
20776
|
if (options.agent !== "codex" || !/^[a-f0-9]{64}$/.test(options.expectedPolicySha256) || !/^[a-f0-9]{64}$/.test(options.expectedSettingsSha256) || options.expectedCacheWitnessSha256 !== undefined && !/^[a-f0-9]{64}$/.test(options.expectedCacheWitnessSha256) || !Array.isArray(options.appOnlyParents) || options.appOnlyParents.length > 4096 || new Set(options.appOnlyParents).size !== options.appOnlyParents.length || options.reviewedHookParents !== undefined && (!Array.isArray(options.reviewedHookParents) || options.reviewedHookParents.length > 4096 || new Set(options.reviewedHookParents).size !== options.reviewedHookParents.length))
|
|
20642
20777
|
throw new Error("Invalid Codex semantic cache upgrade request");
|
|
20643
|
-
const home =
|
|
20778
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
20644
20779
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20645
20780
|
if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
|
|
20646
20781
|
throw new Error("Managed policy preimage changed");
|
|
@@ -20654,10 +20789,10 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20654
20789
|
throw new Error("Cache upgrade requires prior installed-plugin review");
|
|
20655
20790
|
if (!controls.length || !isDeepStrictEqual8(controls, binding.codexDisabledPluginSkills ?? controls))
|
|
20656
20791
|
throw new Error("Cache upgrade requires unchanged enrolled plugin controls");
|
|
20657
|
-
const cacheRoot = canonicalAgentPath(
|
|
20658
|
-
if (controls.some((control) =>
|
|
20792
|
+
const cacheRoot = canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases);
|
|
20793
|
+
if (controls.some((control) => dirname22(dirname22(control.pluginParent)) !== cacheRoot))
|
|
20659
20794
|
throw new Error("Cache upgrade names another enrolled cache");
|
|
20660
|
-
const configPath = canonicalAgentPath(
|
|
20795
|
+
const configPath = canonicalAgentPath(join31(home, ".codex/config.toml"), aliases), settings = readOptional(configPath);
|
|
20661
20796
|
if (settings === null || sha4(settings) !== options.expectedSettingsSha256)
|
|
20662
20797
|
throw new Error("Native settings preimage changed");
|
|
20663
20798
|
const boundedRead = (path) => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
@@ -20680,13 +20815,13 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20680
20815
|
return unchangedFullSource(path);
|
|
20681
20816
|
};
|
|
20682
20817
|
const appOnlyParents = options.appOnlyParents.map((parent) => {
|
|
20683
|
-
const parts = relative9(cacheRoot, parent).split(
|
|
20684
|
-
if (!isAbsolute24(parent) ||
|
|
20818
|
+
const parts = relative9(cacheRoot, parent).split(sep14);
|
|
20819
|
+
if (!isAbsolute24(parent) || resolve23(parent) !== parent || parts.length !== 2 || parts.some((part) => !/^[A-Za-z0-9_-]{1,64}$/.test(part)) || controls.some((control) => control.pluginParent === parent))
|
|
20685
20820
|
throw new Error("Unsupported app-only parent role");
|
|
20686
|
-
const receipt = JSON.parse(unchangedFullSource(
|
|
20821
|
+
const receipt = JSON.parse(unchangedFullSource(join31(parent, ".codex-remote-plugin-install.json")));
|
|
20687
20822
|
if (!receipt || Object.keys(receipt).length !== 2 || receipt.schema_version !== 1 || typeof receipt.remote_plugin_id !== "string")
|
|
20688
20823
|
throw new Error("Invalid reviewed app-only parent identity");
|
|
20689
|
-
const manifests = binding.sources.filter((source) => source.path.startsWith(parent +
|
|
20824
|
+
const manifests = binding.sources.filter((source) => source.path.startsWith(parent + sep14) && source.path.endsWith(join31(sep14, ".codex-plugin/plugin.json")));
|
|
20690
20825
|
if (!manifests.length)
|
|
20691
20826
|
throw new Error("App-only bootstrap lacks prior full manifests");
|
|
20692
20827
|
for (const source of manifests)
|
|
@@ -20694,10 +20829,10 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20694
20829
|
return { role: "app-only", pluginId: `${parts[1]}@${parts[0]}`, namespace: parts[1], pluginParent: parent, remotePluginId: receipt.remote_plugin_id };
|
|
20695
20830
|
}).sort((a, b) => a.pluginParent.localeCompare(b.pluginParent));
|
|
20696
20831
|
const reviewedHookParents = (options.reviewedHookParents ?? []).map((parent) => {
|
|
20697
|
-
const parts = relative9(cacheRoot, parent).split(
|
|
20698
|
-
if (!isAbsolute24(parent) ||
|
|
20832
|
+
const parts = relative9(cacheRoot, parent).split(sep14);
|
|
20833
|
+
if (!isAbsolute24(parent) || resolve23(parent) !== parent || parts.length !== 2 || parts.some((part) => !/^[A-Za-z0-9_-]{1,64}$/.test(part)) || controls.some((control) => control.pluginParent === parent) || appOnlyParents.some((role) => role.pluginParent === parent))
|
|
20699
20834
|
throw new Error("Unsupported reviewed native-hook parent role");
|
|
20700
|
-
const receiptPath =
|
|
20835
|
+
const receiptPath = join31(parent, ".codex-remote-plugin-install.json"), receiptStat = lstatSync28(receiptPath, { throwIfNoEntry: false });
|
|
20701
20836
|
let identity5;
|
|
20702
20837
|
if (receiptStat) {
|
|
20703
20838
|
if (!receiptStat.isFile() || receiptStat.isSymbolicLink() || receiptStat.size > 16 * 1024)
|
|
@@ -20734,11 +20869,11 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20734
20869
|
for (const version2 of versions) {
|
|
20735
20870
|
if (!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/.test(version2))
|
|
20736
20871
|
throw new Error("Invalid reviewed native-hook version identity");
|
|
20737
|
-
const root =
|
|
20872
|
+
const root = join31(parent, version2), stat2 = lstatSync28(root, { throwIfNoEntry: false });
|
|
20738
20873
|
if (!stat2?.isDirectory() || stat2.isSymbolicLink() || realpathSync12(root) !== root)
|
|
20739
20874
|
throw new Error("Invalid reviewed native-hook materialization");
|
|
20740
|
-
unchangedReviewedHookSource(
|
|
20741
|
-
unchangedReviewedHookSource(
|
|
20875
|
+
unchangedReviewedHookSource(join31(root, ".codex-plugin/plugin.json"));
|
|
20876
|
+
unchangedReviewedHookSource(join31(root, "hooks/hooks.json"));
|
|
20742
20877
|
}
|
|
20743
20878
|
return identity5;
|
|
20744
20879
|
}).sort((a, b) => a.pluginParent.localeCompare(b.pluginParent));
|
|
@@ -20748,10 +20883,10 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20748
20883
|
assertCodexSemanticCacheHookSafety(cacheRoot, parents2, configPath, hookRootAlias);
|
|
20749
20884
|
const supersededSources = binding.sources.filter((source) => codexSemanticCacheSourceRole(source, cacheRoot, parents2, reviewedHookParents.map((parent) => parent.pluginParent)));
|
|
20750
20885
|
for (const source of supersededSources)
|
|
20751
|
-
if ([".app.json", ".mcp.json"].includes(source.path.split(
|
|
20886
|
+
if ([".app.json", ".mcp.json"].includes(source.path.split(sep14).at(-1)))
|
|
20752
20887
|
unchangedFullSource(source.path);
|
|
20753
20888
|
const supersededDirectories = (binding.directories ?? []).filter((directory2) => codexSemanticCacheDirectoryRole(directory2.path, cacheRoot));
|
|
20754
|
-
if (!supersededDirectories.some((directory2) => directory2.path === cacheRoot) || (binding.directories ?? []).some((directory2) => cacheRoot.startsWith(directory2.path +
|
|
20889
|
+
if (!supersededDirectories.some((directory2) => directory2.path === cacheRoot) || (binding.directories ?? []).some((directory2) => cacheRoot.startsWith(directory2.path + sep14)))
|
|
20755
20890
|
throw new Error("Cache upgrade requires exact cache directory coverage without an unproved ancestor projection");
|
|
20756
20891
|
const rules = Bun.TOML.parse(settings).skills?.config ?? [];
|
|
20757
20892
|
const witness = captureCodexSemanticCacheWitness({ cacheRoot, controls, appOnlyParents, reviewedHookParents, rules, read: boundedRead });
|
|
@@ -20774,8 +20909,8 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20774
20909
|
dataDir: dataDir2,
|
|
20775
20910
|
profileId: policy.profileId,
|
|
20776
20911
|
nativeSkills: [],
|
|
20777
|
-
changes: [{ path:
|
|
20778
|
-
observedPolicy: { path:
|
|
20912
|
+
changes: [{ path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text, after }],
|
|
20913
|
+
observedPolicy: { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text },
|
|
20779
20914
|
observedSettings: { path: configPath, before: settings },
|
|
20780
20915
|
discoveryBefore: [replacement],
|
|
20781
20916
|
discoveryAfter: [replacement],
|
|
@@ -20788,7 +20923,7 @@ function planCodexSemanticCacheWitnessUpgrade(options) {
|
|
|
20788
20923
|
function planReviewedArtifactMigration(options) {
|
|
20789
20924
|
if (options.kind !== "codex-native-catalog" || !/^[a-f0-9]{64}$/.test(options.expectedPolicySha256) || !/^[a-f0-9]{64}$/.test(options.expectedSourceSha256))
|
|
20790
20925
|
throw new Error("REVIEW_ARTIFACT_INVALID");
|
|
20791
|
-
const home =
|
|
20926
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = resolve23(options.dataDir ?? getDataDirReadOnly());
|
|
20792
20927
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
20793
20928
|
if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
|
|
20794
20929
|
throw new Error("REVIEW_ARTIFACT_POLICY_CHANGED");
|
|
@@ -20805,8 +20940,8 @@ function planReviewedArtifactMigration(options) {
|
|
|
20805
20940
|
dataDir: dataDir2,
|
|
20806
20941
|
profileId: snapshot2.value.profileId,
|
|
20807
20942
|
nativeSkills: [],
|
|
20808
|
-
changes: [...retained.changes, { path:
|
|
20809
|
-
observedPolicy: { path:
|
|
20943
|
+
changes: [...retained.changes, { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text, after: serializeManagedSkillPolicy(next) }],
|
|
20944
|
+
observedPolicy: { path: join31(dataDir2, "agent-policy.json"), before: snapshot2.text },
|
|
20810
20945
|
reviewArtifactSources: retained.observed,
|
|
20811
20946
|
retainedReviewArtifacts: retained.changes,
|
|
20812
20947
|
reviewArtifactMigration: { ...options, home, dataDir: dataDir2 },
|
|
@@ -20832,18 +20967,18 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20832
20967
|
throw new Error("Native identity source changed after planning");
|
|
20833
20968
|
if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
|
|
20834
20969
|
throw new Error("Native settings changed after witness planning");
|
|
20835
|
-
const policyPath =
|
|
20970
|
+
const policyPath = join31(plan.dataDir, "agent-policy.json");
|
|
20836
20971
|
assertSafePath(policyPath);
|
|
20837
|
-
const currentText = (path) =>
|
|
20972
|
+
const currentText = (path) => resolve23(path) === resolve23(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
|
|
20838
20973
|
readManagedSkillPolicySnapshot(plan.dataDir);
|
|
20839
20974
|
for (const change of plan.changes)
|
|
20840
|
-
if (
|
|
20975
|
+
if (resolve23(change.path) === resolve23(policyPath))
|
|
20841
20976
|
parseManagedSkillPolicy(change.after);
|
|
20842
20977
|
const aliases = plan.rootAliases ?? [];
|
|
20843
20978
|
assertCurrent();
|
|
20844
20979
|
recheckRootAliases(aliases);
|
|
20845
20980
|
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
20846
|
-
|
|
20981
|
+
assertRetainedReviewState(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
20847
20982
|
if (plan.reviewArtifactMigration) {
|
|
20848
20983
|
const verified = planReviewedArtifactMigration(plan.reviewArtifactMigration);
|
|
20849
20984
|
if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.discoveryAfter) !== JSON.stringify(plan.discoveryAfter))
|
|
@@ -20882,10 +21017,12 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20882
21017
|
for (const change of plan.changes)
|
|
20883
21018
|
if (currentText(change.path) !== change.before)
|
|
20884
21019
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
20885
|
-
const backupRoot =
|
|
21020
|
+
const backupRoot = join31(plan.dataDir, "migration", randomUUID4()), backups = [], written = [], createdDirectories = new Set;
|
|
20886
21021
|
if (!plan.changes.length) {
|
|
20887
21022
|
assertCurrent();
|
|
20888
21023
|
recheckRootAliases(aliases);
|
|
21024
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
21025
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
20889
21026
|
return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
20890
21027
|
}
|
|
20891
21028
|
assertSafePath(backupRoot);
|
|
@@ -20893,9 +21030,9 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20893
21030
|
chmodSync(backupRoot, 448);
|
|
20894
21031
|
for (const [index, change] of plan.changes.entries())
|
|
20895
21032
|
if (change.before !== null) {
|
|
20896
|
-
const backup =
|
|
21033
|
+
const backup = join31(backupRoot, `${index}-${sha4(change.path).slice(0, 12)}.backup`);
|
|
20897
21034
|
writeFileSync6(backup, change.before, { mode: 384, flag: "wx" });
|
|
20898
|
-
if (
|
|
21035
|
+
if (readFileSync19(backup, "utf8") !== change.before)
|
|
20899
21036
|
throw new Error("Native configuration preservation readback failed");
|
|
20900
21037
|
backups.push(backup);
|
|
20901
21038
|
}
|
|
@@ -20905,7 +21042,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20905
21042
|
recheckRootAliases(aliases);
|
|
20906
21043
|
if (currentText(change.path) !== change.before)
|
|
20907
21044
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
20908
|
-
for (let directory2 =
|
|
21045
|
+
for (let directory2 = dirname22(change.path);!existsSync9(directory2); directory2 = dirname22(directory2))
|
|
20909
21046
|
createdDirectories.add(directory2);
|
|
20910
21047
|
const after = change.after;
|
|
20911
21048
|
const immutable = plan.retainedReviewArtifacts?.find((item) => item.path === change.path);
|
|
@@ -20935,7 +21072,9 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20935
21072
|
}
|
|
20936
21073
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
20937
21074
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.projectDir ?? plan.managedAgentChecks.home });
|
|
20938
|
-
|
|
21075
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
21076
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
21077
|
+
atomicWrite2(join31(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha4(change.before), afterHash: sha4(change.after) })), backups, ...plan.cacheWitnessUpgrade ? { cacheWitnessUpgrade: plan.cacheWitnessUpgrade } : {}, ...plan.retainedReviewArtifacts?.length ? { retainedReviewArtifacts: plan.retainedReviewArtifacts.map((item) => ({ path: item.path, ...item.artifact })) } : {}, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
20939
21078
|
`);
|
|
20940
21079
|
} catch (error) {
|
|
20941
21080
|
assertCurrent();
|
|
@@ -20986,11 +21125,11 @@ function writeArchiveJournal(path, value) {
|
|
|
20986
21125
|
closeSync17(descriptor);
|
|
20987
21126
|
descriptor = undefined;
|
|
20988
21127
|
renameSync4(temporary, path);
|
|
20989
|
-
syncArchiveDirectory(
|
|
21128
|
+
syncArchiveDirectory(dirname22(path));
|
|
20990
21129
|
} finally {
|
|
20991
21130
|
if (descriptor !== undefined)
|
|
20992
21131
|
closeSync17(descriptor);
|
|
20993
|
-
if (
|
|
21132
|
+
if (lstatSync28(temporary, { throwIfNoEntry: false }))
|
|
20994
21133
|
unlinkSync5(temporary);
|
|
20995
21134
|
}
|
|
20996
21135
|
}
|
|
@@ -20998,7 +21137,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
20998
21137
|
const selected = options.targetManifest ? selectNativeMigrationTargets(inventory, options.targetManifest) : inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
20999
21138
|
const roots = selected.flatMap((entry) => {
|
|
21000
21139
|
const root = (entry.rootAlias?.agent === "codex" ? entry.rootAlias.alias : undefined) ?? codexCorpusRootForPath(entry.path);
|
|
21001
|
-
const owner2 = entry.codexHome ?? (entry.agent === "codex" && entry.bridgeHome ?
|
|
21140
|
+
const owner2 = entry.codexHome ?? (entry.agent === "codex" && entry.bridgeHome ? join31(entry.bridgeHome, ".codex") : undefined);
|
|
21002
21141
|
if (!root && !owner2 && (entry.agent === "codex" || isCodexSkillDiscoveryInput(entry.path)))
|
|
21003
21142
|
throw new Error("CODEX_CORPUS_ROOT_UNVERIFIED");
|
|
21004
21143
|
return [...new Set([root, owner2].filter((value) => value !== undefined))];
|
|
@@ -21013,7 +21152,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21013
21152
|
recheckRootAliases(aliases);
|
|
21014
21153
|
for (const entry of inventory.filter((entry2) => entry2.bridge)) {
|
|
21015
21154
|
const adapter = AGENT_ADAPTERS[entry.agent];
|
|
21016
|
-
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(
|
|
21155
|
+
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(join31(nativeAgentRoot(entry.bridgeHome, entry.agent), CLI_BRIDGE_NAME), aliases) : undefined;
|
|
21017
21156
|
if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
|
|
21018
21157
|
throw new Error(`Skills bridge changed after planning: ${entry.path}`);
|
|
21019
21158
|
}
|
|
@@ -21024,11 +21163,11 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21024
21163
|
const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
|
|
21025
21164
|
if (!selected.length)
|
|
21026
21165
|
return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
21027
|
-
const operationId = randomUUID4(), archiveRoot =
|
|
21166
|
+
const operationId = randomUUID4(), archiveRoot = join31(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join31(archiveRoot, "receipt.json");
|
|
21028
21167
|
const entries = selected.map((entry, index) => ({
|
|
21029
|
-
source: entry.vendor ?
|
|
21030
|
-
archive:
|
|
21031
|
-
hash: entry.vendor ? sha4(readNativeBytes(
|
|
21168
|
+
source: entry.vendor ? join31(entry.path, "SKILL.md") : entry.path,
|
|
21169
|
+
archive: join31(archiveRoot, `${index}-${sha4(entry.path).slice(0, 12)}`),
|
|
21170
|
+
hash: entry.vendor ? sha4(readNativeBytes(join31(entry.path, "SKILL.md"))) : entry.hash,
|
|
21032
21171
|
...entry.vendor ? { discoveryOnly: true } : {},
|
|
21033
21172
|
status: "planned"
|
|
21034
21173
|
}));
|
|
@@ -21036,7 +21175,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21036
21175
|
const moved = [];
|
|
21037
21176
|
const verifyArchive = (entry) => {
|
|
21038
21177
|
assertSafePath(entry.archive);
|
|
21039
|
-
const stat2 =
|
|
21178
|
+
const stat2 = lstatSync28(entry.archive);
|
|
21040
21179
|
if (entry.discoveryOnly ? !stat2.isFile() : !stat2.isDirectory())
|
|
21041
21180
|
throw new Error("Archive type changed");
|
|
21042
21181
|
if ((entry.discoveryOnly ? sha4(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -21044,14 +21183,14 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21044
21183
|
};
|
|
21045
21184
|
assertSafePath(archiveRoot);
|
|
21046
21185
|
const created = [];
|
|
21047
|
-
for (let path = archiveRoot;!existsSync9(path); path =
|
|
21186
|
+
for (let path = archiveRoot;!existsSync9(path); path = dirname22(path))
|
|
21048
21187
|
created.push(path);
|
|
21049
21188
|
mkdirSync8(archiveRoot, { recursive: true, mode: 448 });
|
|
21050
21189
|
try {
|
|
21051
21190
|
for (const directory2 of created)
|
|
21052
21191
|
syncArchiveDirectory(directory2);
|
|
21053
21192
|
if (created.length)
|
|
21054
|
-
syncArchiveDirectory(
|
|
21193
|
+
syncArchiveDirectory(dirname22(created.at(-1)));
|
|
21055
21194
|
writeArchiveJournal(receiptPath, journal);
|
|
21056
21195
|
for (const [index, entry] of selected.entries()) {
|
|
21057
21196
|
assertCurrent();
|
|
@@ -21064,7 +21203,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21064
21203
|
writeArchiveJournal(receiptPath, journal);
|
|
21065
21204
|
renameSync4(move.source, move.archive);
|
|
21066
21205
|
moved.push(move);
|
|
21067
|
-
syncArchiveDirectory(
|
|
21206
|
+
syncArchiveDirectory(dirname22(move.source));
|
|
21068
21207
|
syncArchiveDirectory(archiveRoot);
|
|
21069
21208
|
verifyArchive(move);
|
|
21070
21209
|
move.status = "archived";
|
|
@@ -21087,7 +21226,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21087
21226
|
recheckRootAliases(aliases);
|
|
21088
21227
|
assertSafePath(entry.source);
|
|
21089
21228
|
assertSafePath(entry.archive);
|
|
21090
|
-
if (
|
|
21229
|
+
if (lstatSync28(entry.source, { throwIfNoEntry: false })) {
|
|
21091
21230
|
entry.status = "recovery-required";
|
|
21092
21231
|
entry.conflict = "source-occupied";
|
|
21093
21232
|
continue;
|
|
@@ -21101,10 +21240,10 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21101
21240
|
}
|
|
21102
21241
|
if (entry.discoveryOnly) {
|
|
21103
21242
|
linkSync3(entry.archive, entry.source);
|
|
21104
|
-
syncArchiveDirectory(
|
|
21243
|
+
syncArchiveDirectory(dirname22(entry.source));
|
|
21105
21244
|
unlinkSync5(entry.archive);
|
|
21106
21245
|
} else {
|
|
21107
|
-
if (
|
|
21246
|
+
if (lstatSync28(entry.source, { throwIfNoEntry: false })) {
|
|
21108
21247
|
entry.status = "recovery-required";
|
|
21109
21248
|
entry.conflict = "source-occupied";
|
|
21110
21249
|
continue;
|
|
@@ -21112,7 +21251,7 @@ function archiveNativeSkillsUnlocked(inventory, options, assertCurrent) {
|
|
|
21112
21251
|
renameSync4(entry.archive, entry.source);
|
|
21113
21252
|
}
|
|
21114
21253
|
entry.status = "restored";
|
|
21115
|
-
syncArchiveDirectory(
|
|
21254
|
+
syncArchiveDirectory(dirname22(entry.source));
|
|
21116
21255
|
syncArchiveDirectory(archiveRoot);
|
|
21117
21256
|
} catch {
|
|
21118
21257
|
entry.status = "recovery-required";
|
|
@@ -21135,12 +21274,12 @@ function classifiedCodexPluginCacheCopy(entry, cache, read2, admit) {
|
|
|
21135
21274
|
try {
|
|
21136
21275
|
if (entry.agent !== "codex" || !entry.vendor || entry.bridge || entry.system)
|
|
21137
21276
|
return false;
|
|
21138
|
-
const document2 =
|
|
21277
|
+
const document2 = join31(entry.path, "SKILL.md");
|
|
21139
21278
|
const inside = relative9(cache, entry.path);
|
|
21140
|
-
if (!inside || isAbsolute24(inside) || inside.split(
|
|
21279
|
+
if (!inside || isAbsolute24(inside) || inside.split(sep14).some((segment) => segment === ".."))
|
|
21141
21280
|
return false;
|
|
21142
21281
|
assertSafePath(entry.path);
|
|
21143
|
-
if (!
|
|
21282
|
+
if (!lstatSync28(cache).isDirectory() || !lstatSync28(entry.path).isDirectory() || !lstatSync28(document2).isFile())
|
|
21144
21283
|
return false;
|
|
21145
21284
|
if (realpathSync12.native(cache) !== cache || realpathSync12.native(entry.path) !== entry.path || realpathSync12.native(document2) !== document2)
|
|
21146
21285
|
return false;
|
|
@@ -21159,7 +21298,7 @@ function isInertCodexPluginCacheCopy(entry, cache, allowedHostPaths, read2) {
|
|
|
21159
21298
|
}
|
|
21160
21299
|
function isInertCodexVendorPluginCopy(entry, cache, rules, read2) {
|
|
21161
21300
|
try {
|
|
21162
|
-
if (entry.managed ||
|
|
21301
|
+
if (entry.managed || lstatSync28(join31(entry.path, ".hasna-skills.json"), { throwIfNoEntry: false }))
|
|
21163
21302
|
return false;
|
|
21164
21303
|
const marketplace = codexVendorMarketplace(cache, entry.path);
|
|
21165
21304
|
if (!marketplace)
|
|
@@ -21177,7 +21316,7 @@ function isInertCodexVendorPluginCopy(entry, cache, rules, read2) {
|
|
|
21177
21316
|
try {
|
|
21178
21317
|
return realpathSync12.native(path);
|
|
21179
21318
|
} catch {
|
|
21180
|
-
return
|
|
21319
|
+
return resolve23(path);
|
|
21181
21320
|
}
|
|
21182
21321
|
};
|
|
21183
21322
|
const matches = (rule) => typeof rule?.name === "string" && rule.name.trim() === identity5.name || typeof rule?.path === "string" && [document2, entry.path].includes(canonical9(rule.path));
|
|
@@ -21197,12 +21336,78 @@ function nativeSkillPathList(paths, invisible = false) {
|
|
|
21197
21336
|
const remaining = paths.length - shown.length;
|
|
21198
21337
|
return `${shown.join(", ")}${remaining ? `; ${remaining} more` : ""}`;
|
|
21199
21338
|
}
|
|
21339
|
+
function nativeSkillRemediation(agent, blocking, context) {
|
|
21340
|
+
const quote2 = (value) => nativeSkillPathList([value], true);
|
|
21341
|
+
const codex = agent === "codex";
|
|
21342
|
+
const system = blocking.filter((entry) => codex && entry.system === true);
|
|
21343
|
+
const cacheCopies = blocking.filter((entry) => codex && !system.includes(entry) && entry.path.startsWith(context.cache + sep14));
|
|
21344
|
+
const nameRule = (entry) => {
|
|
21345
|
+
if (context.strict || codexVendorMarketplace(context.cache, entry.path) === null)
|
|
21346
|
+
return null;
|
|
21347
|
+
if (context.rules !== undefined && !Array.isArray(context.rules))
|
|
21348
|
+
return null;
|
|
21349
|
+
try {
|
|
21350
|
+
const identity5 = classifyCodexPluginCacheDocument(join31(entry.path, "SKILL.md"), context.cache, context.read);
|
|
21351
|
+
if (!identity5)
|
|
21352
|
+
return null;
|
|
21353
|
+
const rules = [...context.rules ?? [], { name: identity5.name, enabled: false }];
|
|
21354
|
+
return isInertCodexVendorPluginCopy(entry, context.cache, rules, context.read) ? identity5.name : null;
|
|
21355
|
+
} catch {
|
|
21356
|
+
return null;
|
|
21357
|
+
}
|
|
21358
|
+
};
|
|
21359
|
+
const named = cacheCopies.flatMap((entry) => {
|
|
21360
|
+
const name = nameRule(entry);
|
|
21361
|
+
return name ? [{ entry, name }] : [];
|
|
21362
|
+
});
|
|
21363
|
+
const unnamed = cacheCopies.filter((entry) => !named.some((item) => item.entry === entry));
|
|
21364
|
+
const refresh = unnamed.filter((entry) => context.strict || context.toleratedChange || context.enrolled(entry));
|
|
21365
|
+
const byPath = unnamed.filter((entry) => !refresh.includes(entry));
|
|
21366
|
+
const otherVendor = blocking.filter((entry) => entry.vendor && !system.includes(entry) && !cacheCopies.includes(entry));
|
|
21367
|
+
const user = blocking.filter((entry) => !entry.vendor && !system.includes(entry) && !cacheCopies.includes(entry));
|
|
21368
|
+
const install = (catalog) => {
|
|
21369
|
+
const fresh = [...context.automatic ? [] : ["discovery review"], ...catalog ? ["native catalog"] : []];
|
|
21370
|
+
return `skills hook install --agent codex${catalog ? " --codex-native-catalog <reviewed-catalog>" : ""}${context.automatic ? "" : " --discovery-inputs <reviewed-file>"} --apply${fresh.length ? `, with a fresh ${fresh.join(" and ")}` : ""}`;
|
|
21371
|
+
};
|
|
21372
|
+
const followUp = (rows, entries, removesRules = false) => context.automatic || removesRules || !context.keepsConfig(rows) ? ` ${context.automatic ? "This automatic binding" : "The reviewed binding"} records that file, so follow the edit with ${install(entries.some(context.enrolled))}.` : "";
|
|
21373
|
+
const row = (selector) => `[[skills.config]]
|
|
21374
|
+
${selector}
|
|
21375
|
+
enabled = false
|
|
21376
|
+
`;
|
|
21377
|
+
const steps = [];
|
|
21378
|
+
if (user.length)
|
|
21379
|
+
steps.push(`For user and project copies, review skills migrate native --agent ${agent} --project <working-directory> --include-unmanaged${context.aliases ? " --allow-root-aliases" : ""} --json before continuing; it inventories that directory and its ancestors. Use --apply after reviewing the archive plan.`);
|
|
21380
|
+
if (named.length) {
|
|
21381
|
+
const names = [...new Set(named.map((item) => item.name))];
|
|
21382
|
+
const rows = names.map((name) => row(`name = ${JSON.stringify(name)}`));
|
|
21383
|
+
steps.push(`Codex refreshes OpenAI marketplace copies on its own, so disable each in Codex instead: add [[skills.config]] with name = ${nativeSkillPathList(names, true)}${names.length > 1 ? " (one rule per name)" : ""} and enabled = false to ${quote2(context.config)}. Codex then never loads it and the Codex prompt hook accepts it.${followUp(rows, named.map((item) => item.entry))}`);
|
|
21384
|
+
}
|
|
21385
|
+
if (byPath.length) {
|
|
21386
|
+
const rules = Array.isArray(context.rules) ? context.rules : [];
|
|
21387
|
+
const enabling = rules.some((rule) => rule?.enabled !== false && (typeof rule?.name === "string" || typeof rule?.path === "string" && byPath.some((entry) => [entry.path, join31(entry.path, "SKILL.md")].includes(resolve23(rule.path)))));
|
|
21388
|
+
const rows = byPath.map((entry) => row(`path = ${JSON.stringify(join31(entry.path, "SKILL.md"))}`));
|
|
21389
|
+
steps.push(`For other Codex plugin-cache copies, add [[skills.config]] with the exact path of each SKILL.md and enabled = false to ${quote2(context.config)}, or remove the plugin in Codex.${enabling ? " First remove the [[skills.config]] rules there that enable skills; no path rule counts while one does." : ""}${followUp(rows, byPath, enabling)}`);
|
|
21390
|
+
}
|
|
21391
|
+
if (system.length || refresh.length) {
|
|
21392
|
+
const reasons = [
|
|
21393
|
+
...system.length ? ["Codex system skills are never archived by skills migrate native"] : [],
|
|
21394
|
+
...context.strict && refresh.length ? ["this check, unlike the live Codex prompt hook, does not accept a hand-added Codex name rule for a plugin-cache copy"] : [],
|
|
21395
|
+
...!context.strict && context.toleratedChange && refresh.length ? ["plugin-cache copies keep their exact-path exemption only while the recorded configuration is unchanged"] : [],
|
|
21396
|
+
...refresh.some(context.enrolled) ? ["only a fresh native catalog review exempts a skill of a plugin Skills has enrolled"] : []
|
|
21397
|
+
];
|
|
21398
|
+
const sentence = reasons.join("; ");
|
|
21399
|
+
steps.push(`${sentence[0].toUpperCase()}${sentence.slice(1)}, so refresh the binding instead: ${install(refresh.some(context.enrolled))}. It disables and records each copy.`);
|
|
21400
|
+
}
|
|
21401
|
+
if (otherVendor.length)
|
|
21402
|
+
steps.push(`For other vendor copies, review skills migrate native --agent ${agent} --project <working-directory> --include-vendor --discovery-inputs <reviewed-file> --json; --include-vendor requires a reviewed --discovery-inputs file when a configured plugin declares hooks.`);
|
|
21403
|
+
return steps.join(" ");
|
|
21404
|
+
}
|
|
21200
21405
|
function assertManagedAgentBridge(agent, options = {}) {
|
|
21201
21406
|
return assertManagedAgentBridgeWithDiscovery(agent, options);
|
|
21202
21407
|
}
|
|
21203
|
-
function
|
|
21204
|
-
const home =
|
|
21205
|
-
assertSafePath(
|
|
21408
|
+
function assertManagedBridgeCustody(agent, options = {}, provenDiscovery) {
|
|
21409
|
+
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
21410
|
+
assertSafePath(join31(dataDir2, "agent-policy.json"));
|
|
21206
21411
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
21207
21412
|
if (!snapshot2)
|
|
21208
21413
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
@@ -21213,11 +21418,10 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21213
21418
|
if (!Array.isArray(aliases))
|
|
21214
21419
|
throw new Error("NATIVE_SKILL_DRIFT: invalid root alias binding");
|
|
21215
21420
|
recheckRootAliases(aliases);
|
|
21216
|
-
const expected = canonicalAgentPath(
|
|
21421
|
+
const expected = canonicalAgentPath(join31(nativeAgentRoot(home, agent, options.projectDir ?? process.cwd()), CLI_BRIDGE_NAME), aliases);
|
|
21217
21422
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
21218
21423
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
21219
21424
|
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
21220
|
-
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep13}.agents${sep13}skills${sep13}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
|
|
21221
21425
|
const discovery = provenDiscovery ?? binding.discovery?.[agent];
|
|
21222
21426
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
|
|
21223
21427
|
for (const source of discovery?.sources ?? [])
|
|
@@ -21232,16 +21436,35 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21232
21436
|
throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
|
|
21233
21437
|
if (options.profileId !== undefined && options.profileId !== profile)
|
|
21234
21438
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
21439
|
+
return { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery, configPath, config, command, profile };
|
|
21440
|
+
}
|
|
21441
|
+
function assertUnrepairableBridgeProtection(custody, agent) {
|
|
21442
|
+
if (agent !== "hermes")
|
|
21443
|
+
return;
|
|
21444
|
+
const { home, dataDir: dataDir2, binding, config, command, profile } = custody;
|
|
21445
|
+
const supervisor = binding.supervisors?.hermes;
|
|
21446
|
+
if (supervisor?.path !== join31(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
|
|
21447
|
+
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
21448
|
+
assertHermesProtection(home, config, command, profile, supervisor);
|
|
21449
|
+
}
|
|
21450
|
+
function assertRetainedReviewState(agent, options) {
|
|
21451
|
+
const custody = assertManagedBridgeCustody(agent, options);
|
|
21452
|
+
assertUnrepairableBridgeProtection(custody, agent);
|
|
21453
|
+
assertManagedNativeDiscovery(custody, agent, options);
|
|
21454
|
+
}
|
|
21455
|
+
|
|
21456
|
+
class NativeSkillCopiesDriftError extends Error {
|
|
21457
|
+
}
|
|
21458
|
+
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
21459
|
+
const custody = assertManagedBridgeCustody(agent, options, provenDiscovery);
|
|
21460
|
+
const { home, aliases, expected, discovery, config, command, profile } = custody;
|
|
21235
21461
|
if (agent === "hermes") {
|
|
21236
|
-
|
|
21237
|
-
if (supervisor?.path !== join30(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
|
|
21238
|
-
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
21239
|
-
assertHermesProtection(home, config, command, profile, supervisor);
|
|
21462
|
+
assertUnrepairableBridgeProtection(custody, agent);
|
|
21240
21463
|
} else if (agent === "sumi") {
|
|
21241
|
-
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(
|
|
21464
|
+
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(join31(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js")), command, profile))
|
|
21242
21465
|
throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
|
|
21243
21466
|
} else if (agent === "opencode") {
|
|
21244
|
-
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(
|
|
21467
|
+
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join31(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
|
|
21245
21468
|
throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
|
|
21246
21469
|
} else {
|
|
21247
21470
|
const required = {};
|
|
@@ -21258,8 +21481,23 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21258
21481
|
if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
|
|
21259
21482
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
21260
21483
|
}
|
|
21261
|
-
|
|
21262
|
-
|
|
21484
|
+
if (agent === "codex") {
|
|
21485
|
+
const codexConfig = Bun.TOML.parse(readOptional(canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases)) ?? "");
|
|
21486
|
+
if (codexConfig.skills?.bundled?.enabled !== false)
|
|
21487
|
+
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
21488
|
+
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join31(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
21489
|
+
if ([...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
21490
|
+
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
21491
|
+
}
|
|
21492
|
+
if (agent === "gemini" && discovery && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
|
|
21493
|
+
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
21494
|
+
return assertManagedNativeDiscovery(custody, agent, options);
|
|
21495
|
+
}
|
|
21496
|
+
function assertManagedNativeDiscovery(custody, agent, options) {
|
|
21497
|
+
const { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery } = custody;
|
|
21498
|
+
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep14}.agents${sep14}skills${sep14}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
|
|
21499
|
+
const codexPath = canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases);
|
|
21500
|
+
const vendorCache = canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases);
|
|
21263
21501
|
const witnessTolerance = agent === "codex" && options.codexPromptHook === true ? {
|
|
21264
21502
|
changes: [],
|
|
21265
21503
|
acceptsInput: (source) => source.path === codexPath && codexVendorConfigChangeOnly(source, vendorCache),
|
|
@@ -21268,49 +21506,42 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21268
21506
|
if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
|
|
21269
21507
|
throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
|
|
21270
21508
|
const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
|
|
21271
|
-
if (agent === "codex" && codexConfig.skills?.
|
|
21272
|
-
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
21273
|
-
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join30(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
21274
|
-
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
21275
|
-
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
21276
|
-
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join30(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? []))
|
|
21509
|
+
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? []))
|
|
21277
21510
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
21278
21511
|
const disabledPlugin = (entry) => {
|
|
21279
21512
|
if (agent !== "codex" || !entry.vendor)
|
|
21280
21513
|
return false;
|
|
21281
|
-
const document2 =
|
|
21514
|
+
const document2 = join31(entry.path, "SKILL.md"), cache = canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
21282
21515
|
return isReviewedCodexPluginInactive(document2, cache, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document2, cache, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
|
|
21283
21516
|
};
|
|
21284
|
-
const inertVendorCopy = (entry) => agent === "codex" && options.codexPromptHook === true && entry.vendor && entry.path.startsWith(vendorCache +
|
|
21285
|
-
const vendorOnlyCacheCopy = (entry) => (witnessTolerance?.changes.length ?? 0) > 0 && entry.path.startsWith(vendorCache +
|
|
21517
|
+
const inertVendorCopy = (entry) => agent === "codex" && options.codexPromptHook === true && entry.vendor && entry.path.startsWith(vendorCache + sep14) && isInertCodexVendorPluginCopy(entry, vendorCache, codexConfig.skills?.config, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
21518
|
+
const vendorOnlyCacheCopy = (entry) => (witnessTolerance?.changes.length ?? 0) > 0 && entry.path.startsWith(vendorCache + sep14);
|
|
21286
21519
|
const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
|
|
21287
21520
|
const disabledVendorSkill = (entry) => {
|
|
21288
21521
|
if (agent !== "codex" || !entry.vendor)
|
|
21289
21522
|
return false;
|
|
21290
21523
|
if (vendorOnlyCacheCopy(entry))
|
|
21291
21524
|
return false;
|
|
21292
|
-
if ((binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent +
|
|
21525
|
+
if ((binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent + sep14)))
|
|
21293
21526
|
return disabledPlugin(entry);
|
|
21294
21527
|
if (disabledPlugin(entry))
|
|
21295
21528
|
return true;
|
|
21296
21529
|
if (!disabledPaths.has(entry.path))
|
|
21297
21530
|
return false;
|
|
21298
|
-
if (entry.path.startsWith(canonicalAgentPath(
|
|
21531
|
+
if (entry.path.startsWith(canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases) + sep14))
|
|
21299
21532
|
return true;
|
|
21300
|
-
const runtimeCache = canonicalAgentPath(
|
|
21301
|
-
if (entry.path.startsWith(runtimeCache +
|
|
21533
|
+
const runtimeCache = canonicalAgentPath(join31(home, ".cache", "codex-runtimes"), aliases);
|
|
21534
|
+
if (entry.path.startsWith(runtimeCache + sep14) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep14)))
|
|
21302
21535
|
return true;
|
|
21303
|
-
const bundledMarketplaces = canonicalAgentPath(
|
|
21304
|
-
if (entry.path.startsWith(bundledMarketplaces +
|
|
21536
|
+
const bundledMarketplaces = canonicalAgentPath(join31(home, ".codex", ".tmp", "bundled-marketplaces"), aliases);
|
|
21537
|
+
if (entry.path.startsWith(bundledMarketplaces + sep14) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep14)))
|
|
21305
21538
|
return true;
|
|
21306
|
-
if (!entry.path.startsWith(canonicalAgentPath(
|
|
21539
|
+
if (!entry.path.startsWith(canonicalAgentPath(join31(home, ".codex", "skills", ".system"), aliases) + sep14))
|
|
21307
21540
|
return false;
|
|
21308
21541
|
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
21309
21542
|
};
|
|
21310
21543
|
if (!discovery || discovery.agent !== agent)
|
|
21311
21544
|
throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
|
|
21312
|
-
if (agent === "gemini" && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
|
|
21313
|
-
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
21314
21545
|
try {
|
|
21315
21546
|
verifyAgentDiscovery(agent === "codex" ? { ...discovery, codexDisabledPluginSkills: binding.codexPluginSkills ?? [] } : discovery, options.codexDiscoveryRecovery, aliases, witnessTolerance);
|
|
21316
21547
|
if (agent === "sumi" && discovery.method === "reviewed")
|
|
@@ -21325,7 +21556,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21325
21556
|
let installationInputRoots = [];
|
|
21326
21557
|
if (agent === "codex") {
|
|
21327
21558
|
try {
|
|
21328
|
-
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(
|
|
21559
|
+
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
21329
21560
|
} catch {
|
|
21330
21561
|
throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
|
|
21331
21562
|
}
|
|
@@ -21335,7 +21566,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21335
21566
|
let adapterRefusal, inert = [];
|
|
21336
21567
|
if (agent === "codex" && unexpected.length && options.codexNativePolicy) {
|
|
21337
21568
|
const { inspector, helperRunner, deadlineMs, ...envelope } = options.codexNativePolicy;
|
|
21338
|
-
const cache = canonicalAgentPath(
|
|
21569
|
+
const cache = canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), bridgeDocument = join31(expected, "SKILL.md");
|
|
21339
21570
|
try {
|
|
21340
21571
|
const claimed = parseCodexNativeHookEnvelope(envelope, bridgeDocument).policy;
|
|
21341
21572
|
inert = unexpected.filter((entry) => isInertCodexPluginCacheCopy(entry, cache, claimed.allowedHostPaths, read2));
|
|
@@ -21354,7 +21585,18 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21354
21585
|
}
|
|
21355
21586
|
if (unexpected.length > inert.length + inertVendor.length) {
|
|
21356
21587
|
const blocking = unexpected.filter((entry) => !inertVendor.includes(entry));
|
|
21357
|
-
|
|
21588
|
+
const remediation = nativeSkillRemediation(agent, blocking, { cache: vendorCache, config: codexPath, aliases: aliases.length > 0, strict: options.codexPromptHook !== true, automatic: discovery.method === "automatic", toleratedChange: (witnessTolerance?.changes.length ?? 0) > 0, rules: codexConfig.skills?.config, enrolled: (entry) => (binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent + sep14)), keepsConfig: (rows) => {
|
|
21589
|
+
try {
|
|
21590
|
+
const text2 = readOptional(codexPath) ?? "";
|
|
21591
|
+
return discoveryTextKeepsWitness(discovery, codexPath, `${text2.trimEnd()}${text2.trim() ? `
|
|
21592
|
+
|
|
21593
|
+
` : ""}${rows.join(`
|
|
21594
|
+
`)}`);
|
|
21595
|
+
} catch {
|
|
21596
|
+
return false;
|
|
21597
|
+
}
|
|
21598
|
+
}, read: (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)) });
|
|
21599
|
+
throw new NativeSkillCopiesDriftError(`NATIVE_SKILL_DRIFT: ${blocking.length} unexpected native skill copies were found: ${nativeSkillPathList(blocking.map((entry) => entry.path))}. ${remediation}${adapterRefusal ? ` Native policy adapter refused: ${adapterRefusal}` : ""}`);
|
|
21358
21600
|
}
|
|
21359
21601
|
recheckRootAliases(aliases);
|
|
21360
21602
|
const discoveryChanges = (witnessTolerance?.changes ?? []).filter((change, index, all) => all.findIndex((other) => other.kind === change.kind && other.path === change.path) === index);
|
|
@@ -21378,11 +21620,11 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
21378
21620
|
return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
|
|
21379
21621
|
}
|
|
21380
21622
|
function hasSkillsOwnershipMarker(dir) {
|
|
21381
|
-
const path =
|
|
21623
|
+
const path = join32(dir, SYNC_MARKER_FILE);
|
|
21382
21624
|
try {
|
|
21383
|
-
if (!
|
|
21625
|
+
if (!lstatSync29(path).isFile())
|
|
21384
21626
|
return false;
|
|
21385
|
-
const marker = JSON.parse(
|
|
21627
|
+
const marker = JSON.parse(readFileSync20(path, "utf8"));
|
|
21386
21628
|
return isSkillsOwnershipMarker(marker);
|
|
21387
21629
|
} catch {
|
|
21388
21630
|
return false;
|
|
@@ -21402,9 +21644,9 @@ function resolveSyncAgents(arg) {
|
|
|
21402
21644
|
function agentGlobalSkillsDir(agent, homeDir2 = homedir3()) {
|
|
21403
21645
|
switch (agent) {
|
|
21404
21646
|
case "opencode":
|
|
21405
|
-
return
|
|
21647
|
+
return join32(homeDir2, ".config", "opencode", "skills");
|
|
21406
21648
|
default:
|
|
21407
|
-
return
|
|
21649
|
+
return join32(homeDir2, `.${agent}`, "skills");
|
|
21408
21650
|
}
|
|
21409
21651
|
}
|
|
21410
21652
|
function adaptSkillMdForAgent(skillMd, agent) {
|
|
@@ -21463,7 +21705,7 @@ function resolveSyncCorpus(options = {}) {
|
|
|
21463
21705
|
function packageSourceRoots(source) {
|
|
21464
21706
|
const roots = [];
|
|
21465
21707
|
for (const sub of ["skills"]) {
|
|
21466
|
-
const candidate =
|
|
21708
|
+
const candidate = join32(source, sub);
|
|
21467
21709
|
if (existsSync10(candidate) && isDirectory(candidate))
|
|
21468
21710
|
roots.push(candidate);
|
|
21469
21711
|
}
|
|
@@ -21479,10 +21721,10 @@ function containsSkillDirectories(path) {
|
|
|
21479
21721
|
return false;
|
|
21480
21722
|
}
|
|
21481
21723
|
return entries.some((entry) => {
|
|
21482
|
-
const candidate =
|
|
21724
|
+
const candidate = join32(path, entry);
|
|
21483
21725
|
if (!isDirectory(candidate))
|
|
21484
21726
|
return false;
|
|
21485
|
-
return existsSync10(
|
|
21727
|
+
return existsSync10(join32(candidate, "SKILL.md")) || existsSync10(join32(candidate, "skill.json")) || existsSync10(join32(candidate, "package.json"));
|
|
21486
21728
|
});
|
|
21487
21729
|
}
|
|
21488
21730
|
function isDirectory(path) {
|
|
@@ -21522,7 +21764,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
21522
21764
|
actions.push({
|
|
21523
21765
|
skill: name,
|
|
21524
21766
|
agent,
|
|
21525
|
-
path:
|
|
21767
|
+
path: join32(agentGlobalSkillsDir(agent, homeDir2), name, "SKILL.md"),
|
|
21526
21768
|
action: "skip",
|
|
21527
21769
|
reason: "not found in this machine's corpus"
|
|
21528
21770
|
});
|
|
@@ -21554,7 +21796,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
21554
21796
|
}
|
|
21555
21797
|
function writeManagedAgentSkill(params) {
|
|
21556
21798
|
const homeDir2 = params.homeDir ?? homedir3();
|
|
21557
|
-
const dir =
|
|
21799
|
+
const dir = join32(agentGlobalSkillsDir(params.agent, homeDir2), params.skill);
|
|
21558
21800
|
const result = writeManagedSkillDir(dir, params.skillMd, {
|
|
21559
21801
|
skill: params.skill,
|
|
21560
21802
|
source: params.source,
|
|
@@ -21577,8 +21819,8 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
21577
21819
|
return withCodexCorpusWrite(root ? [root] : [], (assertCurrent) => writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent), options);
|
|
21578
21820
|
}
|
|
21579
21821
|
function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
21580
|
-
const skillMdPath =
|
|
21581
|
-
const markerPath =
|
|
21822
|
+
const skillMdPath = join32(dir, "SKILL.md");
|
|
21823
|
+
const markerPath = join32(dir, SYNC_MARKER_FILE);
|
|
21582
21824
|
const dirExists = existsSync10(dir);
|
|
21583
21825
|
const managed = hasSkillsOwnershipMarker(dir);
|
|
21584
21826
|
const hasSkillMd = existsSync10(skillMdPath);
|
|
@@ -21599,7 +21841,7 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21599
21841
|
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
21600
21842
|
let existingIsStub = false;
|
|
21601
21843
|
try {
|
|
21602
|
-
existingIsStub = isPointerSkillMd(
|
|
21844
|
+
existingIsStub = isPointerSkillMd(readFileSync20(skillMdPath, "utf-8"));
|
|
21603
21845
|
} catch {
|
|
21604
21846
|
existingIsStub = false;
|
|
21605
21847
|
}
|
|
@@ -21614,13 +21856,13 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21614
21856
|
const action = dirExists ? "update" : "create";
|
|
21615
21857
|
if (options.dryRun)
|
|
21616
21858
|
return { action, path: skillMdPath };
|
|
21617
|
-
const parentDir =
|
|
21859
|
+
const parentDir = dirname23(dir);
|
|
21618
21860
|
mkdirSync9(parentDir, { recursive: true });
|
|
21619
|
-
const transactionDir = mkdtempSync(
|
|
21620
|
-
const candidateDir =
|
|
21621
|
-
const backupDir =
|
|
21622
|
-
const candidateSkillMdPath =
|
|
21623
|
-
const candidateMarkerPath =
|
|
21861
|
+
const transactionDir = mkdtempSync(join32(parentDir, `.hasna-skills-write-${basename12(dir)}-`));
|
|
21862
|
+
const candidateDir = join32(transactionDir, "candidate");
|
|
21863
|
+
const backupDir = join32(transactionDir, "backup");
|
|
21864
|
+
const candidateSkillMdPath = join32(candidateDir, "SKILL.md");
|
|
21865
|
+
const candidateMarkerPath = join32(candidateDir, SYNC_MARKER_FILE);
|
|
21624
21866
|
const marker = {
|
|
21625
21867
|
managedBy: SYNC_MARKER_MANAGED_BY,
|
|
21626
21868
|
skill: options.skill,
|
|
@@ -21649,7 +21891,7 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21649
21891
|
assertCurrent();
|
|
21650
21892
|
renameDirectory(candidateDir, dir);
|
|
21651
21893
|
assertCurrent();
|
|
21652
|
-
if (
|
|
21894
|
+
if (readFileSync20(skillMdPath, "utf8") !== (skillMd.endsWith(`
|
|
21653
21895
|
`) ? skillMd : `${skillMd}
|
|
21654
21896
|
`))
|
|
21655
21897
|
throw new Error("Managed skill readback failed");
|
|
@@ -21678,12 +21920,12 @@ function writeManagedSkillDirUnlocked(dir, skillMd, options, assertCurrent) {
|
|
|
21678
21920
|
return { action, path: skillMdPath };
|
|
21679
21921
|
}
|
|
21680
21922
|
function removeManagedAgentSkill(skill, agent, homeDir2 = homedir3(), options = {}) {
|
|
21681
|
-
const dir =
|
|
21923
|
+
const dir = join32(agentGlobalSkillsDir(agent, homeDir2), skill);
|
|
21682
21924
|
const root = codexCorpusRootForPath(dir);
|
|
21683
21925
|
return withCodexCorpusWrite(root ? [root] : [], () => removeManagedAgentSkillUnlocked(skill, agent, homeDir2, options), options);
|
|
21684
21926
|
}
|
|
21685
21927
|
function removeManagedAgentSkillUnlocked(skill, agent, homeDir2 = homedir3(), options = {}) {
|
|
21686
|
-
const dir =
|
|
21928
|
+
const dir = join32(agentGlobalSkillsDir(agent, homeDir2), skill);
|
|
21687
21929
|
if (!hasSkillsOwnershipMarker(dir))
|
|
21688
21930
|
return false;
|
|
21689
21931
|
rmSync2(dir, { recursive: true, force: true });
|
|
@@ -21691,9 +21933,9 @@ function removeManagedAgentSkillUnlocked(skill, agent, homeDir2 = homedir3(), op
|
|
|
21691
21933
|
}
|
|
21692
21934
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
21693
21935
|
if (kind === undefined || kind === "instruction" || preferBundledDocs) {
|
|
21694
|
-
const skillMdPath =
|
|
21936
|
+
const skillMdPath = join32(skillPath, "SKILL.md");
|
|
21695
21937
|
if (existsSync10(skillMdPath))
|
|
21696
|
-
return
|
|
21938
|
+
return readFileSync20(skillMdPath, "utf-8");
|
|
21697
21939
|
}
|
|
21698
21940
|
return pointerSkillMd(name, description);
|
|
21699
21941
|
}
|
|
@@ -21733,8 +21975,8 @@ function resolveSkillAlias(name) {
|
|
|
21733
21975
|
}
|
|
21734
21976
|
|
|
21735
21977
|
// src/lib/project-state.ts
|
|
21736
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as
|
|
21737
|
-
import { join as
|
|
21978
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync21, writeFileSync as writeFileSync8 } from "fs";
|
|
21979
|
+
import { join as join33 } from "path";
|
|
21738
21980
|
var VALID_PIN_SOURCES = [
|
|
21739
21981
|
"official",
|
|
21740
21982
|
"custom",
|
|
@@ -21749,17 +21991,17 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
21749
21991
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
21750
21992
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
21751
21993
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
21752
|
-
return
|
|
21994
|
+
return join33(targetDir, SKILLS_PROJECT_DIR);
|
|
21753
21995
|
}
|
|
21754
21996
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
21755
|
-
return
|
|
21997
|
+
return join33(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
21756
21998
|
}
|
|
21757
21999
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
21758
22000
|
const path = getProjectConfigPath(targetDir);
|
|
21759
22001
|
if (!existsSync11(path))
|
|
21760
22002
|
return null;
|
|
21761
22003
|
try {
|
|
21762
|
-
return normalizeProjectConfig(JSON.parse(
|
|
22004
|
+
return normalizeProjectConfig(JSON.parse(readFileSync21(path, "utf-8")));
|
|
21763
22005
|
} catch {
|
|
21764
22006
|
return null;
|
|
21765
22007
|
}
|
|
@@ -21881,7 +22123,7 @@ function getSkillPath(name) {
|
|
|
21881
22123
|
const extensionPath = findExtensionSkillPath(skillName);
|
|
21882
22124
|
if (extensionPath)
|
|
21883
22125
|
return extensionPath;
|
|
21884
|
-
return
|
|
22126
|
+
return join34(getPortableSkillsRoot(), skillName);
|
|
21885
22127
|
}
|
|
21886
22128
|
function getCanonicalSkillName(name) {
|
|
21887
22129
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
@@ -21944,9 +22186,9 @@ function createLocalSkillManifest(name, generateSkillMd) {
|
|
|
21944
22186
|
if (!existsSync12(sourcePath))
|
|
21945
22187
|
return null;
|
|
21946
22188
|
let skillMd = "";
|
|
21947
|
-
const skillMdPath =
|
|
22189
|
+
const skillMdPath = join34(sourcePath, "SKILL.md");
|
|
21948
22190
|
if (existsSync12(skillMdPath)) {
|
|
21949
|
-
skillMd =
|
|
22191
|
+
skillMd = readFileSync23(skillMdPath, "utf-8");
|
|
21950
22192
|
} else if (generateSkillMd) {
|
|
21951
22193
|
skillMd = generateSkillMd(name) ?? "";
|
|
21952
22194
|
} else {
|
|
@@ -22018,16 +22260,16 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
22018
22260
|
const base = projectDir || process.cwd();
|
|
22019
22261
|
switch (agent) {
|
|
22020
22262
|
case "pi":
|
|
22021
|
-
return scope === "project" ?
|
|
22263
|
+
return scope === "project" ? join34(base, ".pi", "skills") : join34(homedir4(), ".pi", "agent", "skills");
|
|
22022
22264
|
case "opencode":
|
|
22023
|
-
return scope === "project" ?
|
|
22265
|
+
return scope === "project" ? join34(base, ".opencode", "skills") : join34(homedir4(), ".config", "opencode", "skills");
|
|
22024
22266
|
default:
|
|
22025
|
-
return scope === "project" ?
|
|
22267
|
+
return scope === "project" ? join34(base, `.${agent}`, "skills") : join34(homedir4(), `.${agent}`, "skills");
|
|
22026
22268
|
}
|
|
22027
22269
|
}
|
|
22028
22270
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
22029
22271
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
22030
|
-
return
|
|
22272
|
+
return join34(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
22031
22273
|
}
|
|
22032
22274
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
22033
22275
|
assertNativeExportAllowed();
|
|
@@ -22070,9 +22312,9 @@ function removeSkillForAgentUnlocked(name, options) {
|
|
|
22070
22312
|
}
|
|
22071
22313
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
22072
22314
|
const sourcePath = getSkillPath(name);
|
|
22073
|
-
const skillMdPath =
|
|
22315
|
+
const skillMdPath = join34(sourcePath, "SKILL.md");
|
|
22074
22316
|
if (existsSync12(skillMdPath))
|
|
22075
|
-
return
|
|
22317
|
+
return readFileSync23(skillMdPath, "utf-8");
|
|
22076
22318
|
if (generateSkillMd)
|
|
22077
22319
|
return generateSkillMd(name);
|
|
22078
22320
|
return generateMinimalSkillMd(name);
|
|
@@ -22105,7 +22347,7 @@ function generateMinimalSkillMd(name) {
|
|
|
22105
22347
|
"---",
|
|
22106
22348
|
""
|
|
22107
22349
|
].filter(Boolean);
|
|
22108
|
-
const fallbackDoc = readFileIfExists(
|
|
22350
|
+
const fallbackDoc = readFileIfExists(join34(sourcePath, "README.md")) || readFileIfExists(join34(sourcePath, "CLAUDE.md"));
|
|
22109
22351
|
if (fallbackDoc)
|
|
22110
22352
|
return `${frontmatter.join(`
|
|
22111
22353
|
`)}${fallbackDoc.trim()}
|
|
@@ -22124,18 +22366,18 @@ skills run ${canonicalName}
|
|
|
22124
22366
|
`;
|
|
22125
22367
|
}
|
|
22126
22368
|
function readBundledSkillVersion(name) {
|
|
22127
|
-
const pkgPath =
|
|
22369
|
+
const pkgPath = join34(getSkillPath(name), "package.json");
|
|
22128
22370
|
if (!existsSync12(pkgPath))
|
|
22129
22371
|
return "unknown";
|
|
22130
22372
|
try {
|
|
22131
|
-
const pkg = JSON.parse(
|
|
22373
|
+
const pkg = JSON.parse(readFileSync23(pkgPath, "utf-8"));
|
|
22132
22374
|
return pkg.version || "unknown";
|
|
22133
22375
|
} catch {
|
|
22134
22376
|
return "unknown";
|
|
22135
22377
|
}
|
|
22136
22378
|
}
|
|
22137
22379
|
function readFileIfExists(path) {
|
|
22138
|
-
return existsSync12(path) ?
|
|
22380
|
+
return existsSync12(path) ? readFileSync23(path, "utf-8") : null;
|
|
22139
22381
|
}
|
|
22140
22382
|
function loadProjectConfigCompat(targetDir) {
|
|
22141
22383
|
return loadProjectConfig(targetDir);
|
|
@@ -22179,11 +22421,11 @@ function selectSkillDoc(docs, file) {
|
|
|
22179
22421
|
function isInstructionSkillDir(skillPath, meta) {
|
|
22180
22422
|
if (meta?.kind === "instruction")
|
|
22181
22423
|
return true;
|
|
22182
|
-
const skillMdPath =
|
|
22424
|
+
const skillMdPath = join35(skillPath, "SKILL.md");
|
|
22183
22425
|
if (!existsSync13(skillMdPath))
|
|
22184
22426
|
return false;
|
|
22185
22427
|
try {
|
|
22186
|
-
return parseSkillFrontmatter(
|
|
22428
|
+
return parseSkillFrontmatter(readFileSync25(skillMdPath, "utf-8"))?.kind === "instruction";
|
|
22187
22429
|
} catch {
|
|
22188
22430
|
return false;
|
|
22189
22431
|
}
|
|
@@ -22209,9 +22451,9 @@ function getSkillDocs(name) {
|
|
|
22209
22451
|
if (!existsSync13(skillPath))
|
|
22210
22452
|
return null;
|
|
22211
22453
|
return {
|
|
22212
|
-
skillMd: readIfExists(
|
|
22213
|
-
readme: readIfExists(
|
|
22214
|
-
claudeMd: readIfExists(
|
|
22454
|
+
skillMd: readIfExists(join35(skillPath, "SKILL.md")),
|
|
22455
|
+
readme: readIfExists(join35(skillPath, "README.md")),
|
|
22456
|
+
claudeMd: readIfExists(join35(skillPath, "CLAUDE.md"))
|
|
22215
22457
|
};
|
|
22216
22458
|
}
|
|
22217
22459
|
function getSkillBestDoc(name, file) {
|
|
@@ -22227,14 +22469,14 @@ function getSkillRequirements(name) {
|
|
|
22227
22469
|
return null;
|
|
22228
22470
|
const texts = [];
|
|
22229
22471
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
22230
|
-
const content = readIfExists(
|
|
22472
|
+
const content = readIfExists(join35(skillPath, file));
|
|
22231
22473
|
if (content)
|
|
22232
22474
|
texts.push(content);
|
|
22233
22475
|
}
|
|
22234
22476
|
const meta = getSkill(name);
|
|
22235
22477
|
let dependencies = {};
|
|
22236
22478
|
try {
|
|
22237
|
-
dependencies = JSON.parse(
|
|
22479
|
+
dependencies = JSON.parse(readFileSync25(join35(skillPath, "package.json"), "utf8")).dependencies || {};
|
|
22238
22480
|
} catch {}
|
|
22239
22481
|
return getSkillRequirementsFromContent(name, texts, dependencies, meta);
|
|
22240
22482
|
}
|
|
@@ -22296,13 +22538,13 @@ async function runSkill(name, args, options = {}) {
|
|
|
22296
22538
|
error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
|
|
22297
22539
|
};
|
|
22298
22540
|
}
|
|
22299
|
-
const pkgPath =
|
|
22541
|
+
const pkgPath = join35(skillPath, "package.json");
|
|
22300
22542
|
if (!existsSync13(pkgPath)) {
|
|
22301
22543
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
22302
22544
|
}
|
|
22303
22545
|
let entryPoint2;
|
|
22304
22546
|
try {
|
|
22305
|
-
const pkg = JSON.parse(
|
|
22547
|
+
const pkg = JSON.parse(readFileSync25(pkgPath, "utf-8"));
|
|
22306
22548
|
if (pkg.bin) {
|
|
22307
22549
|
const binValues = Object.values(pkg.bin);
|
|
22308
22550
|
entryPoint2 = binValues[0];
|
|
@@ -22316,12 +22558,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
22316
22558
|
} catch {
|
|
22317
22559
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
22318
22560
|
}
|
|
22319
|
-
const entryPath =
|
|
22561
|
+
const entryPath = join35(skillPath, entryPoint2);
|
|
22320
22562
|
if (!existsSync13(entryPath)) {
|
|
22321
22563
|
return { exitCode: 1, error: `Entry point '${entryPoint2}' not found in skill '${name}'` };
|
|
22322
22564
|
}
|
|
22323
22565
|
const env = { ...process.env, ...options.env };
|
|
22324
|
-
if (!existsSync13(
|
|
22566
|
+
if (!existsSync13(join35(skillPath, "node_modules")) || hasDependencyPreparationMarker(skillPath)) {
|
|
22325
22567
|
const failure = await prepareSkillDependencies(skillPath, env, options.preparationTimeoutMs);
|
|
22326
22568
|
if (failure)
|
|
22327
22569
|
return failure;
|
|
@@ -22399,13 +22641,13 @@ function generateSkillMd(name) {
|
|
|
22399
22641
|
"---"
|
|
22400
22642
|
].join(`
|
|
22401
22643
|
`);
|
|
22402
|
-
const readme = readIfExists(
|
|
22403
|
-
const claudeMd = readIfExists(
|
|
22644
|
+
const readme = readIfExists(join35(skillPath, "README.md"));
|
|
22645
|
+
const claudeMd = readIfExists(join35(skillPath, "CLAUDE.md"));
|
|
22404
22646
|
let cliCommand = null;
|
|
22405
|
-
const pkgPath =
|
|
22647
|
+
const pkgPath = join35(skillPath, "package.json");
|
|
22406
22648
|
if (existsSync13(pkgPath)) {
|
|
22407
22649
|
try {
|
|
22408
|
-
const pkg = JSON.parse(
|
|
22650
|
+
const pkg = JSON.parse(readFileSync25(pkgPath, "utf-8"));
|
|
22409
22651
|
if (pkg.bin) {
|
|
22410
22652
|
const binKeys = Object.keys(pkg.bin);
|
|
22411
22653
|
if (binKeys.length > 0)
|
|
@@ -22479,7 +22721,7 @@ function extractEnvVars(text2) {
|
|
|
22479
22721
|
function readIfExists(path) {
|
|
22480
22722
|
try {
|
|
22481
22723
|
if (existsSync13(path)) {
|
|
22482
|
-
return
|
|
22724
|
+
return readFileSync25(path, "utf-8");
|
|
22483
22725
|
}
|
|
22484
22726
|
} catch {}
|
|
22485
22727
|
return null;
|
|
@@ -22631,7 +22873,7 @@ function describeEntries(entries) {
|
|
|
22631
22873
|
|
|
22632
22874
|
// src/lib/plugin-admission.ts
|
|
22633
22875
|
function pluginAdmissionRoot() {
|
|
22634
|
-
return
|
|
22876
|
+
return join36(homedir5(), ".hasna", "skills", "plugin-admission");
|
|
22635
22877
|
}
|
|
22636
22878
|
function validatePluginTarget(value) {
|
|
22637
22879
|
pluginKeys(value, ["schemaVersion", "pluginId", "registrations", "native", "resolver"]);
|
|
@@ -22642,7 +22884,7 @@ function validatePluginTarget(value) {
|
|
|
22642
22884
|
for (const registration of value.registrations) {
|
|
22643
22885
|
pluginKeys(registration, ["scope", "projectPath"]);
|
|
22644
22886
|
pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
|
|
22645
|
-
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute25(registration.projectPath) &&
|
|
22887
|
+
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute25(registration.projectPath) && resolve24(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
|
|
22646
22888
|
const key2 = JSON.stringify([registration.scope, registration.projectPath]);
|
|
22647
22889
|
pluginNeed(!scopes.has(key2), "Duplicate plugin registration scope");
|
|
22648
22890
|
scopes.add(key2);
|
|
@@ -22650,7 +22892,7 @@ function validatePluginTarget(value) {
|
|
|
22650
22892
|
for (const key2 of ["native", "resolver"]) {
|
|
22651
22893
|
pluginKeys(value[key2], key2 === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
|
|
22652
22894
|
pluginText(value[key2].executable);
|
|
22653
|
-
pluginNeed(isAbsolute25(value[key2].executable) &&
|
|
22895
|
+
pluginNeed(isAbsolute25(value[key2].executable) && resolve24(value[key2].executable) === value[key2].executable, "Plugin executable must have a canonical absolute path");
|
|
22654
22896
|
pluginDigest(value[key2].digest);
|
|
22655
22897
|
}
|
|
22656
22898
|
pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
|
|
@@ -22802,17 +23044,17 @@ function pluginResolverCommand(binding) {
|
|
|
22802
23044
|
}
|
|
22803
23045
|
function root(options) {
|
|
22804
23046
|
const path = options.storeRoot ?? pluginAdmissionRoot();
|
|
22805
|
-
pluginNeed(isAbsolute25(path) &&
|
|
23047
|
+
pluginNeed(isAbsolute25(path) && resolve24(path) === path, "Invalid plugin admission store root");
|
|
22806
23048
|
return path;
|
|
22807
23049
|
}
|
|
22808
23050
|
function pluginBindingPath(storeRoot, id) {
|
|
22809
23051
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
22810
|
-
return
|
|
23052
|
+
return join36(storeRoot, "bindings", `${id}.json`);
|
|
22811
23053
|
}
|
|
22812
23054
|
function pluginReceiptPath(storeRoot, id, digest2) {
|
|
22813
23055
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
22814
23056
|
pluginDigest(digest2);
|
|
22815
|
-
return
|
|
23057
|
+
return join36(storeRoot, "receipts", id, `${digest2.slice(7)}.json`);
|
|
22816
23058
|
}
|
|
22817
23059
|
function readPluginBinding(storeRoot, id) {
|
|
22818
23060
|
const value = readPluginJson(pluginBindingPath(storeRoot, id));
|
|
@@ -22832,7 +23074,7 @@ function readPluginAdmissionReceipt(storeRoot, id, digest2) {
|
|
|
22832
23074
|
pluginNeed(receipt.plan.bindingId === id && receipt.plan.planDigest === digest2, "Plugin receipt identity differs from its filename");
|
|
22833
23075
|
const binding = readPluginBinding(storeRoot, id);
|
|
22834
23076
|
pluginNeed(canonicalJson(binding) === canonicalJson(receipt.plan.binding) && receipt.plan.sourceCommand === pluginResolverCommand(binding), "Plugin receipt binding changed");
|
|
22835
|
-
pluginNeed(receipt.materializedPath ===
|
|
23077
|
+
pluginNeed(receipt.materializedPath === join36(storeRoot, "objects", id, digest2.slice(7)), "Plugin receipt points outside its immutable materialization");
|
|
22836
23078
|
pluginText(receipt.admittedAt, 64);
|
|
22837
23079
|
pluginNeed(Number.isFinite(Date.parse(receipt.admittedAt)), "Invalid plugin admission time");
|
|
22838
23080
|
return receipt;
|
|
@@ -22973,7 +23215,7 @@ async function admitPlugin(spec, profileId, target, approvedDigest, approvedEvid
|
|
|
22973
23215
|
const { plan, projection } = await candidate(spec, profileId, target, options);
|
|
22974
23216
|
pluginNeed(plan.planDigest === approvedDigest, "The plugin plan changed since review; obtain and review a new plan");
|
|
22975
23217
|
pluginNeed(plan.evidenceDigest === approvedEvidenceDigest, "The plugin routing evidence changed since review; obtain and review a new plan");
|
|
22976
|
-
const store = root(options), materializedPath =
|
|
23218
|
+
const store = root(options), materializedPath = join36(store, "objects", plan.bindingId, plan.planDigest.slice(7));
|
|
22977
23219
|
materializePluginTree(materializedPath, projection.files);
|
|
22978
23220
|
writePluginJsonImmutable(pluginBindingPath(store, plan.bindingId), plan.binding);
|
|
22979
23221
|
const path = pluginReceiptPath(store, plan.bindingId, plan.planDigest), existing = readPluginJson(path);
|
|
@@ -23007,21 +23249,21 @@ function validateManagedPluginWitnesses(value) {
|
|
|
23007
23249
|
pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
|
|
23008
23250
|
seen.add(item.bindingId);
|
|
23009
23251
|
pluginText(item.storeRoot);
|
|
23010
|
-
pluginNeed(isAbsolute26(item.storeRoot) &&
|
|
23252
|
+
pluginNeed(isAbsolute26(item.storeRoot) && resolve25(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
|
|
23011
23253
|
}
|
|
23012
23254
|
}
|
|
23013
23255
|
function safe5(path) {
|
|
23014
23256
|
pluginText(path);
|
|
23015
|
-
pluginNeed(isAbsolute26(path) &&
|
|
23016
|
-
for (let at = path;; at =
|
|
23017
|
-
pluginNeed(!
|
|
23018
|
-
if (at ===
|
|
23257
|
+
pluginNeed(isAbsolute26(path) && resolve25(path) === path, "Invalid plugin registry path");
|
|
23258
|
+
for (let at = path;; at = dirname24(at)) {
|
|
23259
|
+
pluginNeed(!lstatSync30(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
23260
|
+
if (at === dirname24(at))
|
|
23019
23261
|
break;
|
|
23020
23262
|
}
|
|
23021
23263
|
}
|
|
23022
23264
|
function readRegistry(path) {
|
|
23023
23265
|
safe5(path);
|
|
23024
|
-
const initial =
|
|
23266
|
+
const initial = lstatSync30(path);
|
|
23025
23267
|
pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
23026
23268
|
const fd = openSync18(path, constants19.O_RDONLY | constants19.O_NOFOLLOW | constants19.O_NONBLOCK);
|
|
23027
23269
|
try {
|
|
@@ -23035,7 +23277,7 @@ function readRegistry(path) {
|
|
|
23035
23277
|
break;
|
|
23036
23278
|
length += count;
|
|
23037
23279
|
}
|
|
23038
|
-
const after = fstatSync18(fd), current =
|
|
23280
|
+
const after = fstatSync18(fd), current = lstatSync30(path);
|
|
23039
23281
|
safe5(path);
|
|
23040
23282
|
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");
|
|
23041
23283
|
let value;
|
|
@@ -23065,7 +23307,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23065
23307
|
seen.add(target.pluginId);
|
|
23066
23308
|
const rows = plugins[target.pluginId];
|
|
23067
23309
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
23068
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot =
|
|
23310
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join37(dirname24(path), "cache", market, name);
|
|
23069
23311
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
23070
23312
|
plugins[target.pluginId] = rows.map((row) => {
|
|
23071
23313
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -23078,7 +23320,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23078
23320
|
pluginText(row.sourceProducerPath);
|
|
23079
23321
|
pluginText(row.lastUpdated, 64);
|
|
23080
23322
|
pluginNeed(Number.isFinite(Date.parse(row.lastUpdated)), "Invalid managed plugin update timestamp");
|
|
23081
|
-
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath ===
|
|
23323
|
+
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join37(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
|
|
23082
23324
|
const approval = (producer) => {
|
|
23083
23325
|
pluginText(producer);
|
|
23084
23326
|
const digest2 = `sha256:${basename13(producer)}`;
|
|
@@ -23108,7 +23350,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23108
23350
|
return Object.fromEntries(Object.entries(row).filter(([key2]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key2)));
|
|
23109
23351
|
});
|
|
23110
23352
|
safe5(cacheRoot);
|
|
23111
|
-
const before =
|
|
23353
|
+
const before = lstatSync30(cacheRoot);
|
|
23112
23354
|
pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
|
|
23113
23355
|
const directory2 = opendirSync5(cacheRoot), names = [];
|
|
23114
23356
|
try {
|
|
@@ -23121,17 +23363,17 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
23121
23363
|
directory2.closeSync();
|
|
23122
23364
|
}
|
|
23123
23365
|
for (const version2 of names.sort()) {
|
|
23124
|
-
const files = pluginFileWitnesses(snapshotPluginTree(
|
|
23366
|
+
const files = pluginFileWitnesses(snapshotPluginTree(join37(cacheRoot, version2), { nativeRuntime: `claude-${target.native.version}` }));
|
|
23125
23367
|
verifiedBytes += files.reduce((total, file) => total + file.size, 0);
|
|
23126
23368
|
pluginNeed(verifiedBytes <= 256 * 1024 * 1024, "Managed plugin verification exceeds its aggregate byte budget");
|
|
23127
23369
|
const serialized = JSON.stringify(files);
|
|
23128
|
-
const expected = currentFiles.get(
|
|
23370
|
+
const expected = currentFiles.get(join37(cacheRoot, version2));
|
|
23129
23371
|
pluginNeed(expected === undefined || serialized === expected, "Current native cache differs from its exact registered producer receipt");
|
|
23130
23372
|
pluginNeed([...approvals.values()].some((receipt) => nativeVersionMatches(version2, receipt) && JSON.stringify(receipt.plan.files) === serialized), "A retained native plugin cache version differs from every approved projection");
|
|
23131
23373
|
}
|
|
23132
23374
|
for (const row of rows)
|
|
23133
23375
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
23134
|
-
const after =
|
|
23376
|
+
const after = lstatSync30(cacheRoot);
|
|
23135
23377
|
safe5(cacheRoot);
|
|
23136
23378
|
pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
|
|
23137
23379
|
}
|
|
@@ -23144,24 +23386,24 @@ function captureManagedPluginRegistry(path, managedPlugins) {
|
|
|
23144
23386
|
|
|
23145
23387
|
// src/lib/claude-marketplace-registry.ts
|
|
23146
23388
|
import { createHash as createHash28 } from "crypto";
|
|
23147
|
-
import { closeSync as closeSync19, constants as constants20, fstatSync as fstatSync19, lstatSync as
|
|
23148
|
-
import { basename as basename14, dirname as
|
|
23389
|
+
import { closeSync as closeSync19, constants as constants20, fstatSync as fstatSync19, lstatSync as lstatSync31, openSync as openSync19, readSync as readSync14 } from "fs";
|
|
23390
|
+
import { basename as basename14, dirname as dirname25, isAbsolute as isAbsolute27, resolve as resolve26 } from "path";
|
|
23149
23391
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
23150
23392
|
function need10(value, reason2) {
|
|
23151
23393
|
if (!value)
|
|
23152
23394
|
throw new Error(`Claude marketplace registry ${reason2}`);
|
|
23153
23395
|
}
|
|
23154
23396
|
function absolutePath4(value) {
|
|
23155
|
-
need10(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute27(value) &&
|
|
23397
|
+
need10(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute27(value) && resolve26(value) === value, "requires a normalized absolute path");
|
|
23156
23398
|
}
|
|
23157
23399
|
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;
|
|
23158
23400
|
function parents2(path) {
|
|
23159
23401
|
const result = [];
|
|
23160
|
-
for (let at =
|
|
23161
|
-
const stat2 =
|
|
23402
|
+
for (let at = dirname25(path);; at = dirname25(at)) {
|
|
23403
|
+
const stat2 = lstatSync31(at, { bigint: true });
|
|
23162
23404
|
need10(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
23163
23405
|
result.push([at, stat2]);
|
|
23164
|
-
if (at ===
|
|
23406
|
+
if (at === dirname25(at))
|
|
23165
23407
|
return result;
|
|
23166
23408
|
}
|
|
23167
23409
|
}
|
|
@@ -23169,7 +23411,7 @@ function readRegistry2(path, budget2) {
|
|
|
23169
23411
|
absolutePath4(path);
|
|
23170
23412
|
need10(basename14(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
23171
23413
|
need10(Number.isSafeInteger(budget2.remaining) && budget2.remaining >= 0, "has an invalid byte budget");
|
|
23172
|
-
const ancestors2 = parents2(path), initial =
|
|
23414
|
+
const ancestors2 = parents2(path), initial = lstatSync31(path, { bigint: true });
|
|
23173
23415
|
need10(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
23174
23416
|
need10(initial.size <= BigInt(budget2.remaining), "exceeds its aggregate byte limit");
|
|
23175
23417
|
const fd = openSync19(path, constants20.O_RDONLY | constants20.O_NOFOLLOW | constants20.O_NONBLOCK);
|
|
@@ -23184,10 +23426,10 @@ function readRegistry2(path, budget2) {
|
|
|
23184
23426
|
break;
|
|
23185
23427
|
length += count;
|
|
23186
23428
|
}
|
|
23187
|
-
const after = fstatSync19(fd, { bigint: true }), current =
|
|
23429
|
+
const after = fstatSync19(fd, { bigint: true }), current = lstatSync31(path, { bigint: true });
|
|
23188
23430
|
need10(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
23189
23431
|
for (const [ancestor, before] of ancestors2) {
|
|
23190
|
-
const now =
|
|
23432
|
+
const now = lstatSync31(ancestor, { bigint: true });
|
|
23191
23433
|
need10(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
23192
23434
|
}
|
|
23193
23435
|
budget2.remaining -= length;
|
|
@@ -23380,7 +23622,7 @@ function discoveryExecutable(agent) {
|
|
|
23380
23622
|
const found = command ? Bun.which(command) : null;
|
|
23381
23623
|
if (!command || !found)
|
|
23382
23624
|
return null;
|
|
23383
|
-
const path =
|
|
23625
|
+
const path = resolve27(found), target = realpathOrNull(path);
|
|
23384
23626
|
return target === null ? null : { command, path, target };
|
|
23385
23627
|
}
|
|
23386
23628
|
function recordedDiscoveryExecutable(agent, recorded) {
|
|
@@ -23404,10 +23646,10 @@ function parseConfig(text2, path, toml = false) {
|
|
|
23404
23646
|
function safe6(path) {
|
|
23405
23647
|
if (!isAbsolute28(path) || path.includes("\x00"))
|
|
23406
23648
|
throw new Error("Expected an absolute native discovery path");
|
|
23407
|
-
for (let cursor = path;; cursor =
|
|
23408
|
-
if (
|
|
23649
|
+
for (let cursor = path;; cursor = dirname26(cursor)) {
|
|
23650
|
+
if (lstatSync32(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
23409
23651
|
throw new Error(`Refusing symlink native discovery input: ${cursor}`);
|
|
23410
|
-
if (
|
|
23652
|
+
if (dirname26(cursor) === cursor)
|
|
23411
23653
|
break;
|
|
23412
23654
|
}
|
|
23413
23655
|
}
|
|
@@ -23415,19 +23657,19 @@ function read2(path, changes) {
|
|
|
23415
23657
|
safe6(path);
|
|
23416
23658
|
if (changes?.has(path))
|
|
23417
23659
|
return changes.get(path);
|
|
23418
|
-
const stat2 =
|
|
23660
|
+
const stat2 = lstatSync32(path, { throwIfNoEntry: false });
|
|
23419
23661
|
if (!stat2)
|
|
23420
23662
|
return null;
|
|
23421
23663
|
if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
|
|
23422
23664
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
23423
|
-
return
|
|
23665
|
+
return readFileSync26(path, "utf8");
|
|
23424
23666
|
}
|
|
23425
23667
|
function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
23426
23668
|
const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
|
|
23427
23669
|
if (enabledIds.length === 0)
|
|
23428
23670
|
return;
|
|
23429
|
-
const registryPath = canonical9(
|
|
23430
|
-
const cacheAliasRoot =
|
|
23671
|
+
const registryPath = canonical9(join38(home, ".claude/plugins/installed_plugins.json"));
|
|
23672
|
+
const cacheAliasRoot = join38(home, ".claude/plugins/cache");
|
|
23431
23673
|
const cacheRoot = canonical9(cacheAliasRoot);
|
|
23432
23674
|
const registryText = read2(registryPath);
|
|
23433
23675
|
if (registryText === null)
|
|
@@ -23435,7 +23677,7 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23435
23677
|
const registry = parseConfig(registryText, registryPath);
|
|
23436
23678
|
if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
|
|
23437
23679
|
throw new Error("Reviewed Claude installed plugin registrations are invalid");
|
|
23438
|
-
const cachePath = (path) => typeof path === "string" && isAbsolute28(path) &&
|
|
23680
|
+
const cachePath = (path) => typeof path === "string" && isAbsolute28(path) && resolve27(path) === path && [cacheAliasRoot, cacheRoot].some((root2) => path === root2 || path.startsWith(root2 + sep15));
|
|
23439
23681
|
const activeRoots = new Set;
|
|
23440
23682
|
for (const id of enabledIds) {
|
|
23441
23683
|
const rows = registry.plugins[id];
|
|
@@ -23447,11 +23689,11 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23447
23689
|
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
|
|
23448
23690
|
throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
|
|
23449
23691
|
}
|
|
23450
|
-
if ([cacheAliasRoot, cacheRoot].some((root2) => row.installPath === root2 || row.installPath.startsWith(root2 +
|
|
23692
|
+
if ([cacheAliasRoot, cacheRoot].some((root2) => row.installPath === root2 || row.installPath.startsWith(root2 + sep15))) {
|
|
23451
23693
|
if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename15(row.installPath) !== row.version)) {
|
|
23452
23694
|
throw new Error("Reviewed Claude discovery has a malformed active cache registration");
|
|
23453
23695
|
}
|
|
23454
|
-
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute28(row.projectPath) &&
|
|
23696
|
+
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute28(row.projectPath) && resolve27(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
|
|
23455
23697
|
throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
|
|
23456
23698
|
activeRoots.add(canonical9(row.installPath));
|
|
23457
23699
|
}
|
|
@@ -23469,11 +23711,11 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23469
23711
|
};
|
|
23470
23712
|
for (const root2 of activeRoots) {
|
|
23471
23713
|
safe6(root2);
|
|
23472
|
-
const rootStat =
|
|
23714
|
+
const rootStat = lstatSync32(root2, { throwIfNoEntry: false });
|
|
23473
23715
|
if (!rootStat?.isDirectory() || realpathSync13(root2) !== root2)
|
|
23474
23716
|
throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
|
|
23475
|
-
const manifestPath =
|
|
23476
|
-
const manifestStat =
|
|
23717
|
+
const manifestPath = join38(root2, ".claude-plugin/plugin.json");
|
|
23718
|
+
const manifestStat = lstatSync32(manifestPath, { throwIfNoEntry: false });
|
|
23477
23719
|
let manifest;
|
|
23478
23720
|
if (manifestStat) {
|
|
23479
23721
|
if (!manifestStat.isFile())
|
|
@@ -23488,30 +23730,30 @@ function assertReviewedClaudeCacheClosures(home, config, review, canonical9) {
|
|
|
23488
23730
|
} else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
|
|
23489
23731
|
throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
|
|
23490
23732
|
}
|
|
23491
|
-
const roots = new Set([
|
|
23492
|
-
if (
|
|
23733
|
+
const roots = new Set([join38(root2, "skills")]);
|
|
23734
|
+
if (lstatSync32(join38(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
|
|
23493
23735
|
roots.add(root2);
|
|
23494
23736
|
const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
23495
23737
|
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
23496
23738
|
throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
|
|
23497
23739
|
for (const value of declared) {
|
|
23498
|
-
const path =
|
|
23499
|
-
if (path !== root2 && !path.startsWith(root2 +
|
|
23740
|
+
const path = resolve27(root2, value);
|
|
23741
|
+
if (path !== root2 && !path.startsWith(root2 + sep15))
|
|
23500
23742
|
throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
|
|
23501
|
-
roots.add(path.endsWith(`${
|
|
23743
|
+
roots.add(path.endsWith(`${sep15}SKILL.md`) ? dirname26(path) : path);
|
|
23502
23744
|
}
|
|
23503
23745
|
for (const skillRoot of roots)
|
|
23504
23746
|
if (!review.roots.includes(skillRoot))
|
|
23505
23747
|
throw new Error("Reviewed Claude active cache plugin skill root is not covered");
|
|
23506
|
-
const hooksPath =
|
|
23507
|
-
const hooksStat =
|
|
23748
|
+
const hooksPath = join38(root2, "hooks/hooks.json");
|
|
23749
|
+
const hooksStat = lstatSync32(hooksPath, { throwIfNoEntry: false });
|
|
23508
23750
|
if (hooksStat) {
|
|
23509
23751
|
if (!hooksStat.isFile())
|
|
23510
23752
|
throw new Error("Reviewed Claude plugin hook manifest is unsupported");
|
|
23511
23753
|
requireSource(hooksPath, "hook manifest");
|
|
23512
23754
|
}
|
|
23513
23755
|
if (typeof manifest?.hooks === "string")
|
|
23514
|
-
requireSource(
|
|
23756
|
+
requireSource(resolve27(root2, manifest.hooks), "manifest-declared hook source");
|
|
23515
23757
|
}
|
|
23516
23758
|
}
|
|
23517
23759
|
function projectNativeDiscoveryFields(text2, format, fields2, path) {
|
|
@@ -23564,10 +23806,9 @@ function projected(source, changes, budget2 = discoveryByteBudget()) {
|
|
|
23564
23806
|
if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5")) {
|
|
23565
23807
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
23566
23808
|
throw new Error("Codex settings witnesses require exact typed metadata");
|
|
23567
|
-
|
|
23568
|
-
if (current !== source.sha256)
|
|
23809
|
+
if (codexSettingsWitnessMatch(source.path, source.hashMode, source.sha256, budget2) === null)
|
|
23569
23810
|
throw new NativeDiscoveryWitnessChange("input", source.path, `Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
23570
|
-
return changes?.has(source.path) ? (source.hashMode === "codex-settings-v5" ? hashCodexSettingsReplacementV5 : source.hashMode === "codex-settings-v4" ? hashCodexSettingsReplacementV4 : source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget2) :
|
|
23811
|
+
return changes?.has(source.path) ? (source.hashMode === "codex-settings-v5" ? hashCodexSettingsReplacementV5 : source.hashMode === "codex-settings-v4" ? hashCodexSettingsReplacementV4 : source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget2) : source.sha256;
|
|
23571
23812
|
}
|
|
23572
23813
|
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) {
|
|
23573
23814
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
@@ -23643,18 +23884,18 @@ function absentCodexSkillRoots(binding, tolerance) {
|
|
|
23643
23884
|
return [];
|
|
23644
23885
|
if (binding.agent !== "codex" || !Array.isArray(controls) || controls.length > 4096)
|
|
23645
23886
|
throw new Error("Invalid disabled Codex discovery proof");
|
|
23646
|
-
if (binding.method !== "reviewed" || controls.every((control) =>
|
|
23887
|
+
if (binding.method !== "reviewed" || controls.every((control) => lstatSync32(control.pluginParent, { throwIfNoEntry: false })))
|
|
23647
23888
|
return [];
|
|
23648
|
-
const cache =
|
|
23889
|
+
const cache = dirname26(dirname26(controls[0].pluginParent)), config = join38(dirname26(dirname26(cache)), "config.toml");
|
|
23649
23890
|
const source = binding.sources.find((source2) => source2.path === config && source2.sha256 !== null && source2.format === undefined && source2.fields === undefined);
|
|
23650
23891
|
if (!source || !codexConfigWitnessCurrent(source, tolerance))
|
|
23651
23892
|
throw new Error("Missing current disabled Codex configuration witness");
|
|
23652
23893
|
const rules = Bun.TOML.parse(read2(config)).skills?.config ?? [];
|
|
23653
23894
|
return [...new Set(controls.map((control) => control.pluginParent))].filter((parent) => !codexDirectHookSelects(cache, parent) && absentDisabledCodexPluginParent(cache, parent, controls, rules));
|
|
23654
23895
|
}
|
|
23655
|
-
function codexDirectHookSelects(cache, root2, configPath =
|
|
23656
|
-
const codex =
|
|
23657
|
-
const hooks = read2(
|
|
23896
|
+
function codexDirectHookSelects(cache, root2, configPath = join38(dirname26(dirname26(cache)), "config.toml")) {
|
|
23897
|
+
const codex = dirname26(configPath), config = Bun.TOML.parse(read2(configPath) ?? "");
|
|
23898
|
+
const hooks = read2(join38(codex, "hooks.json"));
|
|
23658
23899
|
if (hooks !== null)
|
|
23659
23900
|
hashNativeJsonControls(hooks);
|
|
23660
23901
|
return JSON.stringify(config.hooks ?? {}).includes(root2) || hooks !== null && JSON.stringify(JSON.parse(hooks)).includes(root2);
|
|
@@ -23666,7 +23907,7 @@ function retiredCodexRoots(binding, tolerance) {
|
|
|
23666
23907
|
const controls = binding.codexDisabledPluginSkills;
|
|
23667
23908
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !Array.isArray(controls) || !controls.length || !Array.isArray(proof.roots) || proof.roots.length > AGENT_POLICY_LIMITS.discoveryRoots || new Set(proof.roots).size !== proof.roots.length || !Array.isArray(proof.directories) || proof.parents !== undefined && (!Array.isArray(proof.parents) || proof.parents.length > AGENT_POLICY_LIMITS.discoveryRoots || new Set(proof.parents).size !== proof.parents.length) || !proof.roots.length && !proof.parents?.length)
|
|
23668
23909
|
throw new Error("Invalid retired Codex materialization proof");
|
|
23669
|
-
const cache =
|
|
23910
|
+
const cache = dirname26(dirname26(controls[0].pluginParent)), config = join38(dirname26(dirname26(cache)), "config.toml");
|
|
23670
23911
|
const source = binding.sources.find((source2) => source2.path === config && source2.sha256 !== null && source2.format === undefined && source2.fields === undefined);
|
|
23671
23912
|
if (!source || !codexConfigWitnessCurrent(source, tolerance))
|
|
23672
23913
|
throw new Error("Missing current retired Codex configuration witness");
|
|
@@ -23674,7 +23915,7 @@ function retiredCodexRoots(binding, tolerance) {
|
|
|
23674
23915
|
if ((proof.parents ?? []).some((parent) => codexDirectHookSelects(cache, parent) || !reviewedDisabledCodexPluginParent(cache, parent, controls, rules)) || !reviewedCodexPluginCapabilitiesUnchanged(cache, controls, (path) => read2(path), rules))
|
|
23675
23916
|
throw new Error("Retired Codex parent capabilities changed");
|
|
23676
23917
|
for (const root2 of proof.roots) {
|
|
23677
|
-
const manifest = binding.sources.find((source2) => source2.path ===
|
|
23918
|
+
const manifest = binding.sources.find((source2) => source2.path === join38(root2, ".codex-plugin/plugin.json") && source2.sha256 !== null && source2.format === undefined && source2.fields === undefined && [undefined, "bytes"].includes(source2.hashMode));
|
|
23678
23919
|
if (!manifest || codexDirectHookSelects(cache, root2) || !reviewedRetiredCodexRoot(cache, root2, controls, rules, (path) => read2(path), true))
|
|
23679
23920
|
throw new Error("Retired Codex materialization changed");
|
|
23680
23921
|
}
|
|
@@ -23683,23 +23924,35 @@ function retiredCodexRoots(binding, tolerance) {
|
|
|
23683
23924
|
function captureRetiredCodexDiscovery(binding, controls, rules) {
|
|
23684
23925
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !controls.length)
|
|
23685
23926
|
return binding;
|
|
23686
|
-
const cache =
|
|
23687
|
-
const candidates = binding.sources.filter((source) => source.path.endsWith(
|
|
23927
|
+
const cache = dirname26(dirname26(controls[0].pluginParent));
|
|
23928
|
+
const candidates = binding.sources.filter((source) => source.path.endsWith(sep15 + ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined && [undefined, "bytes"].includes(source.hashMode)).map((source) => dirname26(dirname26(source.path)));
|
|
23688
23929
|
const roots = [...new Set(candidates)].filter((root2) => !codexDirectHookSelects(cache, root2) && reviewedRetiredCodexRoot(cache, root2, controls, rules, (path) => read2(path), binding.codexRetiredMaterializations?.roots.includes(root2) ?? false)).sort();
|
|
23689
23930
|
const parents3 = reviewedCodexPluginCapabilitiesUnchanged(cache, controls, (path) => read2(path), rules) ? [...new Set(controls.map((control) => control.pluginParent))].filter((parent) => !codexDirectHookSelects(cache, parent) && reviewedDisabledCodexPluginParent(cache, parent, controls, rules)).sort() : [];
|
|
23690
23931
|
const { codexRetiredMaterializations: previous, ...current } = binding;
|
|
23691
23932
|
if (!roots.length && !parents3.length)
|
|
23692
23933
|
return current;
|
|
23693
23934
|
const installation = codexInstallationRoots(binding);
|
|
23694
|
-
const directories = (binding.directories ?? []).filter((directory2) => !installation.some((root2) => directory2.path === root2 || directory2.path.startsWith(root2 +
|
|
23935
|
+
const directories = (binding.directories ?? []).filter((directory2) => !installation.some((root2) => directory2.path === root2 || directory2.path.startsWith(root2 + sep15) || root2.startsWith(directory2.path + sep15)));
|
|
23695
23936
|
if (previous && isDeepStrictEqual9(previous.roots, roots) && isDeepStrictEqual9(previous.parents, parents3) && isDeepStrictEqual9(previous.directories.map((item) => item.path), directories.map((item) => item.path)))
|
|
23696
23937
|
return { ...current, codexRetiredMaterializations: previous };
|
|
23697
23938
|
return { ...current, codexRetiredMaterializations: { roots, parents: parents3, directories: captureDiscoveryDirectoryProjection(directories.map((item) => item.path), roots) } };
|
|
23698
23939
|
}
|
|
23699
|
-
function
|
|
23940
|
+
function retainableAbsentWitness(source) {
|
|
23700
23941
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined)
|
|
23701
|
-
return
|
|
23702
|
-
|
|
23942
|
+
return null;
|
|
23943
|
+
if ([undefined, "bytes"].includes(source.hashMode))
|
|
23944
|
+
return "bytes";
|
|
23945
|
+
if (source.hashMode === "claude-plugin-manifest-v1" && source.sha256 !== null && source.path.endsWith(join38(sep15, ".claude-plugin", "plugin.json")))
|
|
23946
|
+
return "claude-plugin-manifest-v1";
|
|
23947
|
+
if (source.hashMode === CLAUDE_ORPHAN_MARKER_MODE && source.sha256 !== null && isClaudePluginCacheOrphanMarker(source.path))
|
|
23948
|
+
return CLAUDE_ORPHAN_MARKER_MODE;
|
|
23949
|
+
return null;
|
|
23950
|
+
}
|
|
23951
|
+
function retainableAbsentSource(source) {
|
|
23952
|
+
return retainableAbsentWitness(source) !== null;
|
|
23953
|
+
}
|
|
23954
|
+
function retainedAbsentRoot(absentRoots, source) {
|
|
23955
|
+
return retainableAbsentSource(source) ? absentRoots.find((root2) => source.path.startsWith(root2 + sep15)) : undefined;
|
|
23703
23956
|
}
|
|
23704
23957
|
function absentRetiredClaudeRoots(binding) {
|
|
23705
23958
|
if (binding.agent !== "claude" || binding.method !== "reviewed")
|
|
@@ -23708,17 +23961,17 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23708
23961
|
for (const source of binding.sources) {
|
|
23709
23962
|
if (!retainableAbsentSource(source) || source.sha256 === null)
|
|
23710
23963
|
continue;
|
|
23711
|
-
const marker =
|
|
23964
|
+
const marker = sep15 + ".claude" + sep15 + "plugins" + sep15 + "cache" + sep15, at = source.path.indexOf(marker);
|
|
23712
23965
|
if (at < 0 || source.path.indexOf(marker, at + 1) >= 0)
|
|
23713
23966
|
continue;
|
|
23714
|
-
const cache = source.path.slice(0, at + marker.length - 1), parts = source.path.slice(at + marker.length).split(
|
|
23967
|
+
const cache = source.path.slice(0, at + marker.length - 1), parts = source.path.slice(at + marker.length).split(sep15);
|
|
23715
23968
|
if (parts.length < 4 || parts.slice(0, 3).some((part) => !part || part === "." || part === ".."))
|
|
23716
23969
|
continue;
|
|
23717
|
-
const root2 =
|
|
23970
|
+
const root2 = join38(cache, ...parts.slice(0, 3));
|
|
23718
23971
|
safe6(root2);
|
|
23719
|
-
if (
|
|
23972
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
23720
23973
|
continue;
|
|
23721
|
-
const claude =
|
|
23974
|
+
const claude = dirname26(dirname26(cache)), settings = join38(claude, "settings.json"), registry = join38(claude, "plugins/installed_plugins.json");
|
|
23722
23975
|
const full = (path) => binding.sources.find((item) => item.path === path && item.sha256 !== null && item.format === undefined && item.fields === undefined);
|
|
23723
23976
|
const configSource = full(settings), registrySource = full(registry);
|
|
23724
23977
|
if (!configSource || !registrySource || projected(configSource) !== configSource.sha256 || projected(registrySource) !== registrySource.sha256)
|
|
@@ -23735,15 +23988,15 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23735
23988
|
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
23736
23989
|
return null;
|
|
23737
23990
|
const value = row;
|
|
23738
|
-
if (typeof value.installPath !== "string" || /[\x00-\x1f\x7f]/.test(value.installPath) || !isAbsolute28(value.installPath) ||
|
|
23991
|
+
if (typeof value.installPath !== "string" || /[\x00-\x1f\x7f]/.test(value.installPath) || !isAbsolute28(value.installPath) || resolve27(value.installPath) !== value.installPath || value.installPath.length > AGENT_POLICY_LIMITS.pathCharacters)
|
|
23739
23992
|
return null;
|
|
23740
23993
|
if (value.scope === "user")
|
|
23741
23994
|
return value.projectPath === undefined || value.projectPath === null ? "user\x00" : null;
|
|
23742
23995
|
if (value.scope === "project")
|
|
23743
|
-
return typeof value.projectPath === "string" && !/[\x00-\x1f\x7f]/.test(value.projectPath) && value.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute28(value.projectPath) &&
|
|
23996
|
+
return typeof value.projectPath === "string" && !/[\x00-\x1f\x7f]/.test(value.projectPath) && value.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute28(value.projectPath) && resolve27(value.projectPath) === value.projectPath ? `project\x00${value.projectPath}` : null;
|
|
23744
23997
|
return null;
|
|
23745
23998
|
};
|
|
23746
|
-
if (Object.values(installed.plugins).some((value) => !Array.isArray(value) || value.some((row) => !row || typeof row.installPath !== "string" || row.installPath === root2 || row.installPath.startsWith(root2 +
|
|
23999
|
+
if (Object.values(installed.plugins).some((value) => !Array.isArray(value) || value.some((row) => !row || typeof row.installPath !== "string" || row.installPath === root2 || row.installPath.startsWith(root2 + sep15) || root2.startsWith(row.installPath + sep15))))
|
|
23747
24000
|
continue;
|
|
23748
24001
|
const rows = installed.plugins[id];
|
|
23749
24002
|
if (!Array.isArray(rows) || rows.length < 1)
|
|
@@ -23761,9 +24014,9 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23761
24014
|
if (!targetValid || !targetScopes.has("user\x00"))
|
|
23762
24015
|
continue;
|
|
23763
24016
|
const active = rows[0]?.installPath;
|
|
23764
|
-
if (typeof active !== "string" || rows[0]?.version !== basename15(active) ||
|
|
24017
|
+
if (typeof active !== "string" || rows[0]?.version !== basename15(active) || dirname26(active) !== dirname26(root2) || active === root2 || !lstatSync32(active, { throwIfNoEntry: false })?.isDirectory() || realpathSync13(active) !== active)
|
|
23765
24018
|
continue;
|
|
23766
|
-
const activeManifest = full(
|
|
24019
|
+
const activeManifest = full(join38(active, ".claude-plugin/plugin.json"));
|
|
23767
24020
|
if (!activeManifest || projected(activeManifest) !== activeManifest.sha256)
|
|
23768
24021
|
continue;
|
|
23769
24022
|
result.add(root2);
|
|
@@ -23771,24 +24024,24 @@ function absentRetiredClaudeRoots(binding) {
|
|
|
23771
24024
|
return [...result];
|
|
23772
24025
|
}
|
|
23773
24026
|
function codexSemanticCacheSourceRole(source, cacheRoot, parents3, hookParents = []) {
|
|
23774
|
-
if (!isAbsolute28(source.path) ||
|
|
24027
|
+
if (!isAbsolute28(source.path) || resolve27(source.path) !== source.path || /[\x00-\x1f\x7f]/.test(source.path) || source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || source.hashMode !== undefined && source.hashMode !== "bytes")
|
|
23775
24028
|
return false;
|
|
23776
|
-
const parts = relative10(cacheRoot, source.path).split(
|
|
23777
|
-
if (parts.length < 3 || !parents3.includes(
|
|
24029
|
+
const parts = relative10(cacheRoot, source.path).split(sep15);
|
|
24030
|
+
if (parts.length < 3 || !parents3.includes(join38(cacheRoot, parts[0], parts[1])))
|
|
23778
24031
|
return false;
|
|
23779
24032
|
const tail = parts.slice(3).join("/");
|
|
23780
|
-
const parent =
|
|
24033
|
+
const parent = join38(cacheRoot, parts[0], parts[1]);
|
|
23781
24034
|
return parts.length === 5 && tail === ".codex-plugin/plugin.json" && source.sha256 !== null || parts.length === 4 && [".app.json", ".mcp.json"].includes(tail) && source.sha256 !== null || parts.length === 5 && tail === "hooks/hooks.json" && hookParents.includes(parent) && source.sha256 !== null || source.sha256 === null && (parts.length === 4 && tail === "SKILL.md" || parts.length === 5 && tail === "hooks/hooks.json");
|
|
23782
24035
|
}
|
|
23783
24036
|
function codexSemanticCacheDirectoryRole(path, cacheRoot) {
|
|
23784
|
-
return isAbsolute28(path) &&
|
|
24037
|
+
return isAbsolute28(path) && resolve27(path) === path && !/[\x00-\x1f\x7f]/.test(path) && (path === cacheRoot || path.startsWith(cacheRoot + sep15));
|
|
23785
24038
|
}
|
|
23786
24039
|
function assertCodexSemanticCacheHookSafety(cacheRoot, parents3, configPath, hookRootAlias = null) {
|
|
23787
24040
|
const selectionRoots = [cacheRoot, ...parents3];
|
|
23788
24041
|
if (hookRootAlias !== null) {
|
|
23789
|
-
if (!hookRootAlias || Object.keys(hookRootAlias).length !== 2 || [hookRootAlias.alias, hookRootAlias.target].some((path) => typeof path !== "string" || !isAbsolute28(path) ||
|
|
24042
|
+
if (!hookRootAlias || Object.keys(hookRootAlias).length !== 2 || [hookRootAlias.alias, hookRootAlias.target].some((path) => typeof path !== "string" || !isAbsolute28(path) || resolve27(path) !== path || /[\x00-\x1f\x7f]/.test(path)) || hookRootAlias.target !== dirname26(configPath) || cacheRoot !== join38(hookRootAlias.target, "plugins/cache") || !lstatSync32(hookRootAlias.alias, { throwIfNoEntry: false })?.isSymbolicLink() || realpathSync13(hookRootAlias.alias) !== hookRootAlias.target || realpathSync13(hookRootAlias.target) !== hookRootAlias.target)
|
|
23790
24043
|
throw new Error("Reviewed semantic cache hook alias changed");
|
|
23791
|
-
selectionRoots.push(...selectionRoots.map((root2) =>
|
|
24044
|
+
selectionRoots.push(...selectionRoots.map((root2) => join38(hookRootAlias.alias, relative10(hookRootAlias.target, root2))));
|
|
23792
24045
|
}
|
|
23793
24046
|
selectionRoots.push("plugins/cache");
|
|
23794
24047
|
if (selectionRoots.some((parent) => codexDirectHookSelects(cacheRoot, parent, configPath)))
|
|
@@ -23808,7 +24061,7 @@ function verifyCodexSemanticCacheDiscovery(binding, reviewedRootAliases) {
|
|
|
23808
24061
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !controls?.length || proof.schema !== "hasna.codex-semantic-cache-discovery.v1" || !/^[a-f0-9]{64}$/.test(proof.previousPolicySha256) || !Array.isArray(proof.appOnlyParents) || !Array.isArray(proof.supersededSources) || !Array.isArray(proof.supersededDirectories))
|
|
23809
24062
|
throw new Error("Invalid Codex semantic cache discovery proof");
|
|
23810
24063
|
const cacheRoot = proof.witness.cacheRoot;
|
|
23811
|
-
if (controls.some((control) =>
|
|
24064
|
+
if (controls.some((control) => dirname26(dirname26(control.pluginParent)) !== cacheRoot))
|
|
23812
24065
|
throw new Error("Codex semantic cache names another enrolled cache");
|
|
23813
24066
|
const reviewedHookParents = proof.reviewedHookParents ?? [];
|
|
23814
24067
|
if (!Array.isArray(reviewedHookParents) || reviewedHookParents.length > 4096 || reviewedHookParents.some((parent) => !parent || !(parent.installation === "remote-receipt" && Object.keys(parent).sort().join(",") === "installation,namespace,pluginId,pluginParent,remotePluginId,role" && typeof parent.remotePluginId === "string" || parent.installation === "local-marketplace" && Object.keys(parent).sort().join(",") === "installation,marketplaceSha256,marketplaceSourcePath,namespace,pluginId,pluginParent,role" && typeof parent.marketplaceSourcePath === "string" && /^[a-f0-9]{64}$/.test(parent.marketplaceSha256 ?? "")) || parent.role !== "reviewed-hook" || typeof parent.pluginId !== "string" || typeof parent.namespace !== "string" || typeof parent.pluginParent !== "string") || new Set(reviewedHookParents.map((parent) => parent.pluginParent)).size !== reviewedHookParents.length || !isDeepStrictEqual9((proof.witness.hookParents ?? []).map(({ manifestSha256: _manifest, hooksSha256: _hooks, receiptSha256: _receipt, ...parent }) => parent), reviewedHookParents)) {
|
|
@@ -23824,7 +24077,7 @@ function verifyCodexSemanticCacheDiscovery(binding, reviewedRootAliases) {
|
|
|
23824
24077
|
if (!directories.length || !isDeepStrictEqual9(sources, proof.supersededSources) || !isDeepStrictEqual9(directories, proof.supersededDirectories))
|
|
23825
24078
|
throw new Error("Codex semantic cache provenance changed");
|
|
23826
24079
|
const configPath = proof.configPath;
|
|
23827
|
-
if (!isAbsolute28(configPath) ||
|
|
24080
|
+
if (!isAbsolute28(configPath) || resolve27(configPath) !== configPath || basename15(configPath) !== "config.toml" || /[\x00-\x1f\x7f]/.test(configPath))
|
|
23828
24081
|
throw new Error("Invalid semantic cache configuration source");
|
|
23829
24082
|
const configSource = binding.sources.find((source) => source.path === configPath && source.sha256 !== null && source.format === undefined && source.fields === undefined);
|
|
23830
24083
|
if (!configSource || projected(configSource) !== configSource.sha256)
|
|
@@ -23846,10 +24099,10 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23846
24099
|
const semanticDirectory = (path) => Boolean(semanticCache && codexSemanticCacheDirectoryRole(path, semanticCache.witness.cacheRoot));
|
|
23847
24100
|
const installationRoots = codexInstallationRoots(binding);
|
|
23848
24101
|
const retiredRoots = retiredCodexRoots(binding, witnessTolerance);
|
|
23849
|
-
const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 +
|
|
24102
|
+
const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 + sep15));
|
|
23850
24103
|
if (binding.directories !== undefined) {
|
|
23851
24104
|
const projected2 = binding.codexInstallationInputs?.directories ?? [];
|
|
23852
|
-
const ancestors2 = binding.directories.filter((directory2) => installationRoots.some((root2) => root2.startsWith(directory2.path +
|
|
24105
|
+
const ancestors2 = binding.directories.filter((directory2) => installationRoots.some((root2) => root2.startsWith(directory2.path + sep15)));
|
|
23853
24106
|
if (projected2.length !== ancestors2.length || projected2.some((directory2) => !ancestors2.some((original) => original.path === directory2.path)))
|
|
23854
24107
|
throw new Error("Missing installation input directory projection");
|
|
23855
24108
|
const ordinary = binding.directories.filter((directory2) => !installationInput(directory2.path) && !ancestors2.includes(directory2) && !semanticDirectory(directory2.path));
|
|
@@ -23857,7 +24110,7 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23857
24110
|
const projection = binding.codexRetiredMaterializations.directories.filter((directory2) => !semanticDirectory(directory2.path));
|
|
23858
24111
|
if (projection.length !== ordinary.length || projection.some((item, index) => item.path !== ordinary[index]?.path))
|
|
23859
24112
|
throw new Error("Missing retired Codex directory projection");
|
|
23860
|
-
const omitted = [...retiredRoots, ...(binding.codexRetiredMaterializations.parents ?? []).filter((parent) => !
|
|
24113
|
+
const omitted = [...retiredRoots, ...(binding.codexRetiredMaterializations.parents ?? []).filter((parent) => !lstatSync32(parent, { throwIfNoEntry: false }))];
|
|
23861
24114
|
if (projection.every((item) => Array.isArray(item.entries)))
|
|
23862
24115
|
verifyDiscoveryDirectoryProjection(projection, omitted, witnessTolerance);
|
|
23863
24116
|
else {
|
|
@@ -23874,7 +24127,7 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23874
24127
|
verifyDiscoveryDirectories(ordinary, [], witnessTolerance);
|
|
23875
24128
|
verifyDiscoveryDirectories(projected2, installationRoots, witnessTolerance);
|
|
23876
24129
|
}
|
|
23877
|
-
const absentRoots = [...absentCodexSkillRoots(binding, witnessTolerance), ...absentRetiredClaudeRoots(binding), ...retiredRoots.filter((root2) => !
|
|
24130
|
+
const absentRoots = [...absentCodexSkillRoots(binding, witnessTolerance), ...absentRetiredClaudeRoots(binding), ...retiredRoots.filter((root2) => !lstatSync32(root2, { throwIfNoEntry: false }))];
|
|
23878
24131
|
const budget2 = discoveryByteBudget();
|
|
23879
24132
|
for (const source of binding.sources) {
|
|
23880
24133
|
assertMarketplaceBinding(binding, source);
|
|
@@ -23892,7 +24145,7 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23892
24145
|
safe6(source.path);
|
|
23893
24146
|
continue;
|
|
23894
24147
|
}
|
|
23895
|
-
if (absentRoots
|
|
24148
|
+
if (retainedAbsentRoot(absentRoots, source) !== undefined) {
|
|
23896
24149
|
safe6(source.path);
|
|
23897
24150
|
continue;
|
|
23898
24151
|
}
|
|
@@ -23912,23 +24165,31 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
23912
24165
|
safe6(root2);
|
|
23913
24166
|
for (const root2 of absentRoots) {
|
|
23914
24167
|
safe6(root2);
|
|
23915
|
-
if (
|
|
24168
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
23916
24169
|
throw new Error("Retired native materialization changed during discovery verification");
|
|
23917
24170
|
}
|
|
23918
24171
|
}
|
|
24172
|
+
var CLAUDE_CONTROL_WITNESS_MODES = new Set(["claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry", "claude-marketplace-registry-v2", "claude-marketplace-entry-v1", "claude-plugin-registry"]);
|
|
24173
|
+
var CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
|
|
24174
|
+
function claudeControlSource(source) {
|
|
24175
|
+
return CLAUDE_CONTROL_WITNESS_MODES.has(source.hashMode ?? "") || CLAUDE_CONTROL_FILES.has(basename15(source.path)) && !source.path.includes(`${sep15}.claude${sep15}plugins${sep15}cache${sep15}`);
|
|
24176
|
+
}
|
|
24177
|
+
function retainedAbsentClaudeRoot(absentRoots, source) {
|
|
24178
|
+
return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
|
|
24179
|
+
}
|
|
23919
24180
|
function codexInstallationRoots(binding) {
|
|
23920
24181
|
const proof = binding.codexInstallationInputs;
|
|
23921
24182
|
if (proof === undefined)
|
|
23922
24183
|
return [];
|
|
23923
24184
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || !supportsCodexNativeCapability(proof.version, "installed-plugin-review") || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
|
|
23924
24185
|
throw new Error("Invalid native installation input proof");
|
|
23925
|
-
const cache =
|
|
23926
|
-
const config =
|
|
24186
|
+
const cache = dirname26(dirname26(proof.plugins[0].pluginParent));
|
|
24187
|
+
const config = join38(dirname26(dirname26(cache)), "config.toml"), home = dirname26(dirname26(dirname26(cache)));
|
|
23927
24188
|
const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
|
|
23928
|
-
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
23929
|
-
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot +
|
|
24189
|
+
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join38(home, path));
|
|
24190
|
+
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep15) || input.sourceRoot.startsWith(root2 + sep15)) || binding.sources.some((source) => basename15(source.path) === "config.toml" && (source.path === input.sourceRoot || source.path.startsWith(input.sourceRoot + sep15)))))
|
|
23930
24191
|
throw new Error("Native installation input overlaps configuration or loading roots");
|
|
23931
|
-
if (proof.plugins.some((input) =>
|
|
24192
|
+
if (proof.plugins.some((input) => dirname26(dirname26(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path === join38(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
|
|
23932
24193
|
throw new Error("Missing native installation input source witness");
|
|
23933
24194
|
return reviewedCodexPluginSourceRoots(cache, proof.plugins, (path) => {
|
|
23934
24195
|
const text2 = read2(path);
|
|
@@ -23944,7 +24205,7 @@ function assertMarketplaceBinding(binding, source) {
|
|
|
23944
24205
|
throw new Error("Provider executable witnesses require explicit reviewed Codex V6");
|
|
23945
24206
|
if (source.hashMode === "codex-settings-v6" && (new Set(binding.sources.map((row) => row.path)).size !== binding.sources.length || !Array.isArray(source.hookSources) || source.hookSources.some((hook) => !binding.sources.some((row) => row.path === hook.path && row.sha256 === hook.sha256 && [undefined, "bytes"].includes(row.hashMode) && row.format === undefined && row.fields === undefined))))
|
|
23946
24207
|
throw new Error("Missing independently reviewed Codex hook source population");
|
|
23947
|
-
if (source.hashMode === "claude-plugin-manifest-v1" && (binding.agent !== "claude" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(
|
|
24208
|
+
if (source.hashMode === "claude-plugin-manifest-v1" && (binding.agent !== "claude" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(join38(sep15, ".claude-plugin", "plugin.json"))))
|
|
23948
24209
|
throw new Error("Claude plugin manifest witnesses require Claude plugin discovery");
|
|
23949
24210
|
if (source.hashMode === "sumi-settings-v1" && (binding.agent !== "sumi" || binding.method !== "reviewed" || basename15(source.path) !== "sumi.json"))
|
|
23950
24211
|
throw new Error("Sumi settings witnesses require explicit reviewed Sumi configuration");
|
|
@@ -23957,10 +24218,11 @@ function assertMarketplaceBinding(binding, source) {
|
|
|
23957
24218
|
if (source.hashMode === "claude-marketplace-entry-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || !claudeMarketplaceEntrySourceValid(source)))
|
|
23958
24219
|
throw new Error("Claude marketplace entry witnesses require explicit reviewed Claude discovery of one named entry");
|
|
23959
24220
|
}
|
|
24221
|
+
var ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
|
|
23960
24222
|
function rebindAgentDiscovery(binding, changes) {
|
|
23961
24223
|
const semanticCache = verifyCodexSemanticCacheDiscovery(binding);
|
|
23962
24224
|
const budget2 = discoveryByteBudget();
|
|
23963
|
-
const installationRoots = codexInstallationRoots(binding), absentRoots = [...absentCodexSkillRoots(binding), ...absentRetiredClaudeRoots(binding), ...retiredCodexRoots(binding).filter((root2) => !
|
|
24225
|
+
const installationRoots = codexInstallationRoots(binding), absentRoots = [...absentCodexSkillRoots(binding), ...absentRetiredClaudeRoots(binding), ...retiredCodexRoots(binding).filter((root2) => !lstatSync32(root2, { throwIfNoEntry: false }))];
|
|
23964
24226
|
const rebound = { ...binding, sources: binding.sources.map((source) => {
|
|
23965
24227
|
assertMarketplaceBinding(binding, source);
|
|
23966
24228
|
if (semanticCache?.supersededSources.some((previous) => isDeepStrictEqual9(previous, source))) {
|
|
@@ -23968,26 +24230,28 @@ function rebindAgentDiscovery(binding, changes) {
|
|
|
23968
24230
|
throw new Error("Codex semantic cache sources cannot be written by hook rendering");
|
|
23969
24231
|
return source;
|
|
23970
24232
|
}
|
|
23971
|
-
if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 +
|
|
24233
|
+
if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep15)) || retainedAbsentRoot(absentRoots, source) !== undefined))
|
|
23972
24234
|
return source;
|
|
23973
24235
|
const sha2563 = projected(source, changes, budget2);
|
|
23974
24236
|
if (!changes.has(source.path) && !(source.hashMode === "codex-settings-v6" && source.hookSources.some((hook) => changes.has(hook.path))) && sha2563 !== source.sha256)
|
|
23975
24237
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
24238
|
+
if (changes.has(source.path) && (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5") && !ADOPTABLE_SETTINGS_MATCHES.includes(codexSettingsWitnessMatch(source.path, source.hashMode, source.sha256, budget2)))
|
|
24239
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
23976
24240
|
return source.hashMode === "codex-settings-v6" ? { ...source, ...captureCodexSettingsV6(source.path, source.hookSources, budget2, changes) } : { ...source, sha256: sha2563 };
|
|
23977
24241
|
}) };
|
|
23978
24242
|
for (const root2 of absentRoots) {
|
|
23979
24243
|
safe6(root2);
|
|
23980
|
-
if (
|
|
24244
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
23981
24245
|
throw new Error("Retired native materialization changed during discovery rebinding");
|
|
23982
24246
|
}
|
|
23983
24247
|
return rebound;
|
|
23984
24248
|
}
|
|
23985
24249
|
function hashReviewedExecutable(path, budget2) {
|
|
23986
|
-
const before =
|
|
24250
|
+
const before = lstatSync32(path, { bigint: true });
|
|
23987
24251
|
if (before.size < 0n || before.size > BigInt(budget2.remaining))
|
|
23988
24252
|
throw new Error("Discovery aggregate executable byte limit exceeded");
|
|
23989
24253
|
const digest3 = pluginExecutableDigest(path).slice(7);
|
|
23990
|
-
const after =
|
|
24254
|
+
const after = lstatSync32(path, { bigint: true });
|
|
23991
24255
|
for (const key2 of ["dev", "ino", "size", "mode", "uid", "gid", "nlink", "mtimeNs", "ctimeNs"]) {
|
|
23992
24256
|
if (before[key2] !== after[key2])
|
|
23993
24257
|
throw new Error("Discovery executable changed during budgeted capture");
|
|
@@ -24013,12 +24277,13 @@ function captureDiscoveryPathSources(paths) {
|
|
|
24013
24277
|
const budget2 = discoveryByteBudget();
|
|
24014
24278
|
return paths.map((path) => isClaudePluginCacheOrphanMarker(path) ? { path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(path, budget2) } : { path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget2) });
|
|
24015
24279
|
}
|
|
24016
|
-
|
|
24280
|
+
var CLAUDE_PROJECT_DISCOVERY_KEYS = Object.freeze(["enabledPlugins", "extraKnownMarketplaces", "additionalMarketplaces", "skillOverrides"]);
|
|
24281
|
+
function assertProjectDiscovery(agent, directories, home, canonical9 = resolve27, reviewed) {
|
|
24017
24282
|
const homeConfigPath = canonical9(agentDiscoveryConfigPath(home, agent));
|
|
24018
24283
|
const v6 = agent === "codex" && reviewed?.agent === "codex" && reviewed.method === "reviewed" ? reviewed.sources.find((source) => source.hashMode === "codex-settings-v6" && source.path === homeConfigPath) : undefined;
|
|
24019
24284
|
const referenced = v6 ? new Set(captureCodexHookMcpDependencies(v6.path, v6.hookSources).servers) : undefined;
|
|
24020
24285
|
for (const directory2 of directories) {
|
|
24021
|
-
const isHome =
|
|
24286
|
+
const isHome = resolve27(directory2) === resolve27(home);
|
|
24022
24287
|
if (agent === "hermes") {
|
|
24023
24288
|
assertHermesEnvironment(home);
|
|
24024
24289
|
continue;
|
|
@@ -24027,7 +24292,7 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24027
24292
|
continue;
|
|
24028
24293
|
const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : agent === "sumi" ? ["sumi.json", "sumi.jsonc", ".sumi/sumi.json", ".sumi/sumi.jsonc"] : [".cursor/hooks.json"];
|
|
24029
24294
|
for (const suffix of paths) {
|
|
24030
|
-
const path = canonical9(
|
|
24295
|
+
const path = canonical9(join38(directory2, suffix)), raw = read2(path);
|
|
24031
24296
|
if (raw === null)
|
|
24032
24297
|
continue;
|
|
24033
24298
|
const isRootAliasedHomeConfig = agent === "claude" && suffix === ".claude/settings.json" || agent === "codex" && suffix === ".codex/config.toml";
|
|
@@ -24042,7 +24307,7 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24042
24307
|
if (declaredHooks || providerOverride)
|
|
24043
24308
|
throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project hook dependency requires review: ${path}`);
|
|
24044
24309
|
}
|
|
24045
|
-
const keys2 = agent === "claude" ?
|
|
24310
|
+
const keys2 = agent === "claude" ? CLAUDE_PROJECT_DISCOVERY_KEYS : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? ["plugin", "plugins", "skills", "permissions"] : ["hooks"];
|
|
24046
24311
|
let admittedPlugins = false;
|
|
24047
24312
|
if (agent === "claude" && reviewed?.agent === agent && reviewed.method === "reviewed" && config.enabledPlugins && typeof config.enabledPlugins === "object" && !Array.isArray(config.enabledPlugins)) {
|
|
24048
24313
|
const exact = reviewed.sources.find((source) => source.path === path && source.format === undefined && source.fields === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
@@ -24056,8 +24321,8 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24056
24321
|
throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project skill or hook configuration requires review: ${path}`);
|
|
24057
24322
|
}
|
|
24058
24323
|
if (referenced) {
|
|
24059
|
-
const path = canonical9(
|
|
24060
|
-
if (path !== canonical9(
|
|
24324
|
+
const path = canonical9(join38(directory2, ".codex/hooks.json"));
|
|
24325
|
+
if (path !== canonical9(join38(dirname26(homeConfigPath), "hooks.json"))) {
|
|
24061
24326
|
const raw = read2(path);
|
|
24062
24327
|
if (raw !== null) {
|
|
24063
24328
|
const hooks = parseConfig(raw, path, false);
|
|
@@ -24069,15 +24334,15 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24069
24334
|
}
|
|
24070
24335
|
if (agent === "sumi")
|
|
24071
24336
|
for (const name of ["plugin", "plugins"]) {
|
|
24072
|
-
const path = canonical9(
|
|
24337
|
+
const path = canonical9(join38(directory2, ".sumi", name));
|
|
24073
24338
|
safe6(path);
|
|
24074
|
-
if (
|
|
24339
|
+
if (lstatSync32(path, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(path).length)
|
|
24075
24340
|
throw new Error(`NATIVE_SKILL_DRIFT: project Sumi plugins require a dedicated discovery review: ${path}`);
|
|
24076
24341
|
}
|
|
24077
24342
|
if (agent === "claude") {
|
|
24078
|
-
const commands = canonical9(
|
|
24343
|
+
const commands = canonical9(join38(directory2, ".claude/commands"));
|
|
24079
24344
|
safe6(commands);
|
|
24080
|
-
if (
|
|
24345
|
+
if (lstatSync32(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(commands).length)
|
|
24081
24346
|
throw new Error("NATIVE_SKILL_DRIFT: legacy project command discovery requires a dedicated format adapter");
|
|
24082
24347
|
}
|
|
24083
24348
|
}
|
|
@@ -24085,7 +24350,7 @@ function assertProjectDiscovery(agent, directories, home, canonical9 = resolve26
|
|
|
24085
24350
|
function agentDiscoveryConfigPath(home, agent) {
|
|
24086
24351
|
if (agent === "sumi")
|
|
24087
24352
|
return sumiConfigPath(home);
|
|
24088
|
-
return
|
|
24353
|
+
return join38(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
|
|
24089
24354
|
}
|
|
24090
24355
|
function verifyAutomaticDiscoveryClosure(binding, options) {
|
|
24091
24356
|
const command = DISCOVERY_RUNTIME_COMMANDS[binding.agent], recorded = options.executable;
|
|
@@ -24145,7 +24410,8 @@ function codexVendorConfigChangeOnly(source, cache) {
|
|
|
24145
24410
|
if (text2 === null)
|
|
24146
24411
|
return false;
|
|
24147
24412
|
const rowsAffectDigest = !["codex-settings-v2", "codex-settings-v3", "codex-settings-v4", "codex-settings-v5"].includes(source.hashMode ?? "");
|
|
24148
|
-
|
|
24413
|
+
const typed = source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5" ? source.hashMode : undefined;
|
|
24414
|
+
return codexVendorConfigCandidates(text2, cache, { rowsAffectDigest }).some((candidate2) => replacementDigest(source, candidate2) === source.sha256 || typed !== undefined && codexSettingsToleratesScreenReaderReceipt(candidate2, typed, source.sha256));
|
|
24149
24415
|
} catch {
|
|
24150
24416
|
return false;
|
|
24151
24417
|
}
|
|
@@ -24170,8 +24436,22 @@ function replacementDigest(source, text2) {
|
|
|
24170
24436
|
return hashCodexSettingsReplacementV5(text2, budget2);
|
|
24171
24437
|
return null;
|
|
24172
24438
|
}
|
|
24439
|
+
function discoveryTextKeepsWitness(binding, path, text2) {
|
|
24440
|
+
if (binding.codexSemanticCache?.configPath === path)
|
|
24441
|
+
return false;
|
|
24442
|
+
return binding.sources.filter((source) => source.path === path).every((source) => {
|
|
24443
|
+
if (source.reviewArtifact !== undefined || source.managedPlugins !== undefined || source.hookSources !== undefined || typeof source.sha256 !== "string")
|
|
24444
|
+
return false;
|
|
24445
|
+
const typed = source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4" || source.hashMode === "codex-settings-v5" ? source.hashMode : undefined;
|
|
24446
|
+
try {
|
|
24447
|
+
return replacementDigest(source, text2) === source.sha256 || typed !== undefined && codexSettingsToleratesScreenReaderReceipt(text2, typed, source.sha256);
|
|
24448
|
+
} catch {
|
|
24449
|
+
return false;
|
|
24450
|
+
}
|
|
24451
|
+
});
|
|
24452
|
+
}
|
|
24173
24453
|
function resolveAgentDiscovery(options) {
|
|
24174
|
-
const canonical9 = options.canonical ??
|
|
24454
|
+
const canonical9 = options.canonical ?? resolve27, home = resolve27(options.home), agent = options.agent;
|
|
24175
24455
|
const sources = [], roots = new Set, builtinNames = [];
|
|
24176
24456
|
const witness = (path, format, fields2, hashMode) => {
|
|
24177
24457
|
const source = { path: canonical9(path), sha256: null, ...format ? { format, fields: fields2 } : {}, ...hashMode ? { hashMode } : {} };
|
|
@@ -24192,42 +24472,42 @@ function resolveAgentDiscovery(options) {
|
|
|
24192
24472
|
const review = options.reviewed?.agents.find((item) => item.agent === agent);
|
|
24193
24473
|
if (agent === "sumi") {
|
|
24194
24474
|
const directory2 = sumiConfigDirectory(home);
|
|
24195
|
-
if (witness(
|
|
24475
|
+
if (witness(join38(directory2, "sumi.jsonc")) !== null)
|
|
24196
24476
|
unresolved("JSONC configuration requires a supported format adapter");
|
|
24197
|
-
for (const root2 of [directory2,
|
|
24477
|
+
for (const root2 of [directory2, join38(home, ".claude"), join38(home, ".agents")])
|
|
24198
24478
|
for (const name of ["skill", "skills"])
|
|
24199
|
-
roots.add(canonical9(
|
|
24479
|
+
roots.add(canonical9(join38(root2, name)));
|
|
24200
24480
|
if (config.skills !== undefined && (!Array.isArray(config.skills) || config.skills.some((path) => typeof path !== "string")))
|
|
24201
24481
|
unresolved("skills must be a string array");
|
|
24202
24482
|
for (const value of config.skills ?? []) {
|
|
24203
24483
|
if (typeof value !== "string" || /[\0$]/.test(value) || !value || !(isAbsolute28(value) || value.startsWith("~/")))
|
|
24204
24484
|
unresolved("remote or relative skill sources need a dedicated discovery adapter");
|
|
24205
|
-
roots.add(canonical9(value.startsWith("~/") ?
|
|
24485
|
+
roots.add(canonical9(value.startsWith("~/") ? join38(home, value.slice(2)) : value));
|
|
24206
24486
|
}
|
|
24207
24487
|
for (const key2 of ["plugin", "plugins"])
|
|
24208
24488
|
if (config[key2] !== undefined && !Array.isArray(config[key2]))
|
|
24209
24489
|
unresolved(`${key2} must be an array`);
|
|
24210
|
-
const plugins = [
|
|
24490
|
+
const plugins = [join38(directory2, "plugin"), join38(directory2, "plugins")];
|
|
24211
24491
|
for (const path of plugins) {
|
|
24212
24492
|
safe6(path);
|
|
24213
|
-
if (
|
|
24493
|
+
if (lstatSync32(path, { throwIfNoEntry: false })?.isDirectory())
|
|
24214
24494
|
for (const name of readdirSync12(path).sort()) {
|
|
24215
24495
|
if (name === "skills-cli.js" && path === plugins[1])
|
|
24216
24496
|
continue;
|
|
24217
|
-
const target =
|
|
24218
|
-
if (!
|
|
24497
|
+
const target = join38(path, name);
|
|
24498
|
+
if (!lstatSync32(target).isFile())
|
|
24219
24499
|
unresolved("local plugin packages need a dedicated discovery adapter");
|
|
24220
24500
|
witness(target);
|
|
24221
24501
|
}
|
|
24222
24502
|
}
|
|
24223
24503
|
for (const root2 of roots) {
|
|
24224
24504
|
safe6(root2);
|
|
24225
|
-
if (
|
|
24505
|
+
if (lstatSync32(root2, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(root2).some((name) => name.endsWith(".md")))
|
|
24226
24506
|
unresolved("flat markdown skills require a dedicated native migration adapter");
|
|
24227
24507
|
}
|
|
24228
24508
|
}
|
|
24229
24509
|
if (agent === "hermes") {
|
|
24230
|
-
const profile = witness(
|
|
24510
|
+
const profile = witness(join38(home, ".hermes/active_profile"));
|
|
24231
24511
|
if (profile?.trim() && profile.trim() !== "default")
|
|
24232
24512
|
unresolved("a non-default profile is active");
|
|
24233
24513
|
const declared = config.skills?.external_dirs ?? [], paths = typeof declared === "string" ? [declared] : declared;
|
|
@@ -24241,7 +24521,7 @@ function resolveAgentDiscovery(options) {
|
|
|
24241
24521
|
continue;
|
|
24242
24522
|
if (normalized2.startsWith("~") && normalized2 !== "~" && !normalized2.startsWith("~/"))
|
|
24243
24523
|
unresolved("user-specific tilde expansion requires a separate discovery adapter");
|
|
24244
|
-
const path = normalized2 === "~" ? home : normalized2.startsWith("~/") ?
|
|
24524
|
+
const path = normalized2 === "~" ? home : normalized2.startsWith("~/") ? join38(home, normalized2.slice(2)) : resolve27(home, ".hermes", normalized2);
|
|
24245
24525
|
safe6(path);
|
|
24246
24526
|
roots.add(path);
|
|
24247
24527
|
}
|
|
@@ -24250,24 +24530,24 @@ function resolveAgentDiscovery(options) {
|
|
|
24250
24530
|
const executable2 = options.executable === undefined ? discoveryExecutable(agent) : options.executable;
|
|
24251
24531
|
if (executable2) {
|
|
24252
24532
|
let packageRoot;
|
|
24253
|
-
for (let directory2 =
|
|
24254
|
-
const path =
|
|
24533
|
+
for (let directory2 = dirname26(executable2.target), depth = 0;depth < 8; directory2 = dirname26(directory2), depth++) {
|
|
24534
|
+
const path = join38(directory2, "package.json"), raw = read2(path);
|
|
24255
24535
|
if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
|
|
24256
24536
|
packageRoot = directory2;
|
|
24257
24537
|
witness(path);
|
|
24258
24538
|
break;
|
|
24259
24539
|
}
|
|
24260
|
-
if (
|
|
24540
|
+
if (dirname26(directory2) === directory2)
|
|
24261
24541
|
break;
|
|
24262
24542
|
}
|
|
24263
24543
|
if (!packageRoot)
|
|
24264
24544
|
unresolved("installed Gemini package layout is not recognized");
|
|
24265
|
-
const builtinRoot =
|
|
24545
|
+
const builtinRoot = join38(packageRoot, "bundle/builtin");
|
|
24266
24546
|
safe6(builtinRoot);
|
|
24267
|
-
if (!
|
|
24547
|
+
if (!lstatSync32(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
|
|
24268
24548
|
unresolved("installed Gemini builtin root is missing");
|
|
24269
24549
|
for (const name of readdirSync12(builtinRoot).sort()) {
|
|
24270
|
-
const text2 = witness(
|
|
24550
|
+
const text2 = witness(join38(builtinRoot, name, "SKILL.md"));
|
|
24271
24551
|
const match = text2?.match(/^---\r?\n[\s\S]*?^name:\s*([a-z0-9][a-z0-9-]*)\s*$/m);
|
|
24272
24552
|
if (!match?.[1] || builtinNames.includes(match[1]))
|
|
24273
24553
|
unresolved("installed Gemini builtin name is missing or ambiguous");
|
|
@@ -24306,14 +24586,17 @@ function resolveAgentDiscovery(options) {
|
|
|
24306
24586
|
if (!review.sources.some((source) => source.path === canonical9(configPath)))
|
|
24307
24587
|
throw new Error("Discovery review must include the agent configuration source");
|
|
24308
24588
|
const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
|
|
24589
|
+
const absentRoots = absentRetiredClaudeRoots(supplied);
|
|
24309
24590
|
const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
|
|
24591
|
+
if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
|
|
24592
|
+
return source;
|
|
24310
24593
|
if (agent === "claude" && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.reviewArtifact === undefined && [undefined, "bytes", "path-bytes"].includes(source.hashMode) && isClaudePluginCacheOrphanMarker(source.path)) {
|
|
24311
24594
|
return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
|
|
24312
24595
|
}
|
|
24313
|
-
if (agent !== "claude" || source.sha256 === null || source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.hashMode !== undefined && source.hashMode !== "bytes" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(
|
|
24596
|
+
if (agent !== "claude" || source.sha256 === null || source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.hashMode !== undefined && source.hashMode !== "bytes" || basename15(source.path) !== "plugin.json" || !source.path.endsWith(join38(sep15, ".claude-plugin", "plugin.json")))
|
|
24314
24597
|
return source;
|
|
24315
|
-
const pluginRoot =
|
|
24316
|
-
const hookStat =
|
|
24598
|
+
const pluginRoot = dirname26(dirname26(source.path)), hooksPath = join38(pluginRoot, "hooks/hooks.json");
|
|
24599
|
+
const hookStat = lstatSync32(hooksPath, { throwIfNoEntry: false });
|
|
24317
24600
|
const text2 = read2(source.path);
|
|
24318
24601
|
if (text2 === null)
|
|
24319
24602
|
throw new Error("Reviewed Claude plugin manifest is missing");
|
|
@@ -24327,8 +24610,8 @@ function resolveAgentDiscovery(options) {
|
|
|
24327
24610
|
if (manifest.hooks !== undefined) {
|
|
24328
24611
|
if (typeof manifest.hooks !== "string")
|
|
24329
24612
|
throw new Error("Reviewed Claude plugin manifest has an unsupported hooks target");
|
|
24330
|
-
const declaredHookPath =
|
|
24331
|
-
if (!declaredHookPath.startsWith(pluginRoot +
|
|
24613
|
+
const declaredHookPath = resolve27(pluginRoot, manifest.hooks);
|
|
24614
|
+
if (!declaredHookPath.startsWith(pluginRoot + sep15))
|
|
24332
24615
|
throw new Error("Reviewed Claude plugin hooks target escapes its root");
|
|
24333
24616
|
reviewedHookPaths.add(declaredHookPath);
|
|
24334
24617
|
}
|
|
@@ -24339,6 +24622,11 @@ function resolveAgentDiscovery(options) {
|
|
|
24339
24622
|
});
|
|
24340
24623
|
if (agent === "claude")
|
|
24341
24624
|
assertReviewedClaudeCacheClosures(home, config, review, canonical9);
|
|
24625
|
+
for (const root2 of absentRoots) {
|
|
24626
|
+
safe6(root2);
|
|
24627
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
24628
|
+
throw new Error("Retired native materialization changed during discovery review");
|
|
24629
|
+
}
|
|
24342
24630
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
24343
24631
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual9(candidate2, source)) === index);
|
|
24344
24632
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
@@ -24346,39 +24634,39 @@ function resolveAgentDiscovery(options) {
|
|
|
24346
24634
|
function plugin(root2) {
|
|
24347
24635
|
root2 = canonical9(root2);
|
|
24348
24636
|
safe6(root2);
|
|
24349
|
-
const manifestPath =
|
|
24637
|
+
const manifestPath = join38(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
24350
24638
|
const raw = witness(manifestPath, undefined, undefined, agent === "claude" ? "claude-plugin-manifest-v1" : undefined);
|
|
24351
24639
|
if (raw === null)
|
|
24352
24640
|
unresolved("plugin manifest missing");
|
|
24353
24641
|
const manifest = parseConfig(raw, manifestPath);
|
|
24354
|
-
const hooks = witness(
|
|
24642
|
+
const hooks = witness(join38(root2, "hooks/hooks.json"));
|
|
24355
24643
|
if (manifest.hooks !== undefined || hooks !== null)
|
|
24356
24644
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
24357
|
-
const commands =
|
|
24645
|
+
const commands = join38(root2, "commands");
|
|
24358
24646
|
safe6(commands);
|
|
24359
|
-
if (agent === "claude" && (manifest.commands !== undefined ||
|
|
24647
|
+
if (agent === "claude" && (manifest.commands !== undefined || lstatSync32(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(commands).length))
|
|
24360
24648
|
unresolved("legacy command discovery requires retirement or a dedicated format adapter");
|
|
24361
|
-
const rootSkill = witness(
|
|
24649
|
+
const rootSkill = witness(join38(root2, "SKILL.md"));
|
|
24362
24650
|
if (rootSkill !== null)
|
|
24363
24651
|
roots.add(root2);
|
|
24364
24652
|
const declared = manifest.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
24365
24653
|
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
24366
24654
|
unresolved("unsupported plugin skill paths");
|
|
24367
24655
|
if (agent === "claude")
|
|
24368
|
-
roots.add(
|
|
24656
|
+
roots.add(join38(root2, "skills"));
|
|
24369
24657
|
for (const value of declared) {
|
|
24370
|
-
const path =
|
|
24371
|
-
if (path !== root2 && !path.startsWith(root2 +
|
|
24658
|
+
const path = resolve27(root2, value);
|
|
24659
|
+
if (path !== root2 && !path.startsWith(root2 + sep15))
|
|
24372
24660
|
unresolved("plugin skill path escapes its root");
|
|
24373
|
-
roots.add(path.endsWith(`${
|
|
24661
|
+
roots.add(path.endsWith(`${sep15}SKILL.md`) ? dirname26(path) : path);
|
|
24374
24662
|
}
|
|
24375
24663
|
}
|
|
24376
24664
|
if (agent === "claude") {
|
|
24377
|
-
const registrations = witness(
|
|
24378
|
-
const installed = registrations === null ? {} : parseConfig(registrations,
|
|
24379
|
-
const commands = canonical9(
|
|
24665
|
+
const registrations = witness(join38(home, ".claude/plugins/installed_plugins.json"));
|
|
24666
|
+
const installed = registrations === null ? {} : parseConfig(registrations, join38(home, ".claude/plugins/installed_plugins.json")).plugins;
|
|
24667
|
+
const commands = canonical9(join38(home, ".claude/commands"));
|
|
24380
24668
|
safe6(commands);
|
|
24381
|
-
if (
|
|
24669
|
+
if (lstatSync32(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync12(commands).length)
|
|
24382
24670
|
unresolved("legacy home command discovery requires a dedicated format adapter");
|
|
24383
24671
|
for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
|
|
24384
24672
|
if (enabled === false)
|
|
@@ -24400,16 +24688,16 @@ function resolveAgentDiscovery(options) {
|
|
|
24400
24688
|
const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
|
|
24401
24689
|
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute28(market.source))
|
|
24402
24690
|
unresolved("enabled plugin marketplace is not an explicit local source");
|
|
24403
|
-
const catalogText = witness(
|
|
24691
|
+
const catalogText = witness(join38(market.source, ".agents/plugins/marketplace.json"));
|
|
24404
24692
|
if (catalogText === null)
|
|
24405
24693
|
unresolved("marketplace catalog missing");
|
|
24406
|
-
const catalog = parseConfig(catalogText,
|
|
24694
|
+
const catalog = parseConfig(catalogText, join38(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
24407
24695
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
24408
24696
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
24409
|
-
plugin(
|
|
24697
|
+
plugin(resolve27(market.source, matches[0].source.path));
|
|
24410
24698
|
}
|
|
24411
24699
|
} else if (agent === "opencode") {
|
|
24412
|
-
if (witness(
|
|
24700
|
+
if (witness(join38(home, ".config/opencode/opencode.jsonc")) !== null || witness(join38(home, ".config/opencode/config.json")) !== null)
|
|
24413
24701
|
unresolved("additional config layers require review");
|
|
24414
24702
|
if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
|
|
24415
24703
|
unresolved("external plugin or remote skill source");
|
|
@@ -24418,17 +24706,17 @@ function resolveAgentDiscovery(options) {
|
|
|
24418
24706
|
unresolved("relative or malformed added skill root");
|
|
24419
24707
|
roots.add(path);
|
|
24420
24708
|
}
|
|
24421
|
-
const localPlugins =
|
|
24709
|
+
const localPlugins = join38(home, ".config/opencode/plugins");
|
|
24422
24710
|
safe6(localPlugins);
|
|
24423
|
-
if (
|
|
24711
|
+
if (lstatSync32(localPlugins, { throwIfNoEntry: false }) && readdirSync12(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
24424
24712
|
unresolved("additional local plugin hooks require review");
|
|
24425
24713
|
} else if (agent === "sumi") {
|
|
24426
|
-
if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(
|
|
24714
|
+
if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join38(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null))
|
|
24427
24715
|
unresolved("external or foreign local plugins require review");
|
|
24428
24716
|
} else if (agent === "hermes") {
|
|
24429
|
-
for (const directory2 of [
|
|
24717
|
+
for (const directory2 of [join38(home, ".hermes/plugins"), join38(home, ".hermes/hermes-agent")]) {
|
|
24430
24718
|
safe6(directory2);
|
|
24431
|
-
if (
|
|
24719
|
+
if (lstatSync32(directory2, { throwIfNoEntry: false }))
|
|
24432
24720
|
unresolved("installed runtime/plugin discovery requires reviewed source bindings");
|
|
24433
24721
|
}
|
|
24434
24722
|
if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
|
|
@@ -24436,30 +24724,30 @@ function resolveAgentDiscovery(options) {
|
|
|
24436
24724
|
} else if (agent === "gemini") {
|
|
24437
24725
|
if (Object.keys(config.extensions ?? {}).length || config.skills?.paths?.length)
|
|
24438
24726
|
unresolved("custom extension or skill paths");
|
|
24439
|
-
const directory2 =
|
|
24727
|
+
const directory2 = join38(home, ".gemini/extensions");
|
|
24440
24728
|
safe6(directory2);
|
|
24441
|
-
if (
|
|
24729
|
+
if (lstatSync32(directory2, { throwIfNoEntry: false }))
|
|
24442
24730
|
for (const name of readdirSync12(directory2)) {
|
|
24443
|
-
const root2 =
|
|
24731
|
+
const root2 = join38(directory2, name);
|
|
24444
24732
|
safe6(root2);
|
|
24445
|
-
if (!
|
|
24733
|
+
if (!lstatSync32(root2)?.isDirectory())
|
|
24446
24734
|
continue;
|
|
24447
|
-
const raw = witness(
|
|
24735
|
+
const raw = witness(join38(root2, "gemini-extension.json"));
|
|
24448
24736
|
if (raw === null)
|
|
24449
24737
|
unresolved("extension registration missing");
|
|
24450
|
-
const manifest = parseConfig(raw,
|
|
24451
|
-
if (manifest.hooks !== undefined || witness(
|
|
24738
|
+
const manifest = parseConfig(raw, join38(root2, "gemini-extension.json"));
|
|
24739
|
+
if (manifest.hooks !== undefined || witness(join38(root2, "hooks/hooks.json")) !== null || witness(join38(root2, ".gemini-extension-install.json")) !== null)
|
|
24452
24740
|
unresolved("extension hooks or linked registration require review");
|
|
24453
|
-
roots.add(
|
|
24741
|
+
roots.add(join38(root2, "skills"));
|
|
24454
24742
|
}
|
|
24455
24743
|
}
|
|
24456
|
-
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([
|
|
24744
|
+
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join38(home, ".hermes/plugins"), join38(home, ".hermes/hermes-agent")].map((path) => canonical9(path))) } : {} };
|
|
24457
24745
|
}
|
|
24458
24746
|
|
|
24459
24747
|
// src/lib/authoring-path.ts
|
|
24460
24748
|
var NATIVE_DISCOVERY_ROOTS = NATIVE_SKILL_ROOTS.map(([, path]) => path.split("/"));
|
|
24461
24749
|
function pathParts(path) {
|
|
24462
|
-
return
|
|
24750
|
+
return resolve28(path).split(sep16).filter(Boolean);
|
|
24463
24751
|
}
|
|
24464
24752
|
function nativeRootMatch(path) {
|
|
24465
24753
|
const parts = pathParts(path);
|
|
@@ -24473,24 +24761,24 @@ function nativeRootMatch(path) {
|
|
|
24473
24761
|
return;
|
|
24474
24762
|
}
|
|
24475
24763
|
function physicalPath(path) {
|
|
24476
|
-
let cursor =
|
|
24764
|
+
let cursor = resolve28(path);
|
|
24477
24765
|
const suffix = [];
|
|
24478
|
-
while (!
|
|
24766
|
+
while (!lstatSync33(cursor, { throwIfNoEntry: false })) {
|
|
24479
24767
|
suffix.unshift(basename16(cursor));
|
|
24480
|
-
const parent =
|
|
24768
|
+
const parent = dirname27(cursor);
|
|
24481
24769
|
if (parent === cursor)
|
|
24482
|
-
return
|
|
24770
|
+
return resolve28(path);
|
|
24483
24771
|
cursor = parent;
|
|
24484
24772
|
}
|
|
24485
|
-
return
|
|
24773
|
+
return resolve28(realpathSync14(cursor), ...suffix);
|
|
24486
24774
|
}
|
|
24487
24775
|
function contains(root2, path) {
|
|
24488
|
-
return path === root2 || path.startsWith(root2 +
|
|
24776
|
+
return path === root2 || path.startsWith(root2 + sep16);
|
|
24489
24777
|
}
|
|
24490
24778
|
function configuredRoots(home, explicitHome) {
|
|
24491
|
-
const homeData =
|
|
24779
|
+
const homeData = join39(home, ".hasna", "skills");
|
|
24492
24780
|
const homePolicy = readManagedSkillPolicySnapshot(homeData);
|
|
24493
|
-
const configuredPolicy = !explicitHome &&
|
|
24781
|
+
const configuredPolicy = !explicitHome && resolve28(getDataDirReadOnly()) !== resolve28(homeData) ? readManagedSkillPolicySnapshot(getDataDirReadOnly()) : null;
|
|
24494
24782
|
const policy = homePolicy ?? configuredPolicy;
|
|
24495
24783
|
const bridge = policy?.value.bridge;
|
|
24496
24784
|
if (bridge && (typeof bridge.home !== "string" || physicalPath(bridge.home) !== physicalPath(home))) {
|
|
@@ -24519,8 +24807,8 @@ function configuredRoots(home, explicitHome) {
|
|
|
24519
24807
|
admitBinding(binding);
|
|
24520
24808
|
continue;
|
|
24521
24809
|
}
|
|
24522
|
-
const nativeHome =
|
|
24523
|
-
if (!
|
|
24810
|
+
const nativeHome = join39(home, agent === "opencode" ? ".config/opencode" : `.${agent}`);
|
|
24811
|
+
if (!lstatSync33(nativeHome, { throwIfNoEntry: false }))
|
|
24524
24812
|
continue;
|
|
24525
24813
|
roots.push(...resolveAgentDiscovery({ home, agent, canonical: physicalPath }).roots);
|
|
24526
24814
|
}
|
|
@@ -24529,22 +24817,22 @@ function configuredRoots(home, explicitHome) {
|
|
|
24529
24817
|
function assertPortableAuthoringPath(path, options = {}) {
|
|
24530
24818
|
if (typeof path !== "string" || !path || path.includes("\x00"))
|
|
24531
24819
|
throw new Error("Invalid portable authoring path");
|
|
24532
|
-
const lexical =
|
|
24820
|
+
const lexical = resolve28(path);
|
|
24533
24821
|
const physical = physicalPath(lexical);
|
|
24534
24822
|
const candidates = [lexical, physical];
|
|
24535
24823
|
let native = candidates.map(nativeRootMatch).find(Boolean);
|
|
24536
|
-
const home =
|
|
24537
|
-
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) =>
|
|
24538
|
-
defaults.push(
|
|
24824
|
+
const home = resolve28(options.homeDir ?? homedir6());
|
|
24825
|
+
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) => join39(home, ...parts));
|
|
24826
|
+
defaults.push(join39(home, ".codex/plugins/cache"), join39(home, ".claude/plugins/cache"), join39(home, ".claude/plugins/synced"), join39(home, ".gemini/extensions"));
|
|
24539
24827
|
for (const root2 of defaults) {
|
|
24540
|
-
if (candidates.some((candidate2) => contains(
|
|
24828
|
+
if (candidates.some((candidate2) => contains(resolve28(root2), candidate2) || contains(physicalPath(root2), candidate2)))
|
|
24541
24829
|
native ??= root2;
|
|
24542
24830
|
}
|
|
24543
24831
|
if (native) {
|
|
24544
24832
|
throw new Error(`Refusing portable Skills authoring below native discovery root '${native}': ${lexical}. Set HASNA_SKILLS_DIR to a private authoring corpus outside native roots.`);
|
|
24545
24833
|
}
|
|
24546
24834
|
for (const root2 of configuredRoots(home, options.homeDir !== undefined)) {
|
|
24547
|
-
if (candidates.some((candidate2) => contains(
|
|
24835
|
+
if (candidates.some((candidate2) => contains(resolve28(root2), candidate2) || contains(physicalPath(root2), candidate2))) {
|
|
24548
24836
|
throw new Error(`Refusing portable Skills authoring below configured native discovery root: ${lexical}`);
|
|
24549
24837
|
}
|
|
24550
24838
|
}
|
|
@@ -24556,15 +24844,15 @@ function getPortableSkillsRoot(options = {}) {
|
|
|
24556
24844
|
function resolvePortableSkillsRoot(options, migrate) {
|
|
24557
24845
|
if (options.rootDir)
|
|
24558
24846
|
return options.rootDir;
|
|
24559
|
-
const appDir = options.homeDir ?
|
|
24560
|
-
const cache =
|
|
24847
|
+
const appDir = options.homeDir ? join40(options.homeDir, ".hasna", "skills") : migrate ? getDataDir() : getDataDirReadOnly();
|
|
24848
|
+
const cache = join40(appDir, SKILLS_CACHE_DIRNAME);
|
|
24561
24849
|
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
24562
24850
|
return cache;
|
|
24563
|
-
const installed =
|
|
24851
|
+
const installed = join40(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
24564
24852
|
return installed;
|
|
24565
24853
|
}
|
|
24566
24854
|
function getPortableSkillPath(name, options = {}) {
|
|
24567
|
-
return
|
|
24855
|
+
return join40(getPortableSkillsRoot(options), normalizePortableSkillName(name));
|
|
24568
24856
|
}
|
|
24569
24857
|
function findPortableSkill(name, options = {}) {
|
|
24570
24858
|
let normalized2;
|
|
@@ -24590,7 +24878,7 @@ function listPortableSkills(options = {}) {
|
|
|
24590
24878
|
for (const entry of readdirSync13(root2).sort()) {
|
|
24591
24879
|
if (entry.startsWith("."))
|
|
24592
24880
|
continue;
|
|
24593
|
-
const path =
|
|
24881
|
+
const path = join40(root2, entry);
|
|
24594
24882
|
if (!safeIsDirectory(path))
|
|
24595
24883
|
continue;
|
|
24596
24884
|
try {
|
|
@@ -24624,9 +24912,9 @@ function isOfficialSkillName(name) {
|
|
|
24624
24912
|
function scaffoldPortableSkill(name, options = {}) {
|
|
24625
24913
|
const skillName = normalizeNewPortableSkillName(name);
|
|
24626
24914
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
24627
|
-
assertPortableAuthoringPath(
|
|
24915
|
+
assertPortableAuthoringPath(join40(readOnlyRoot, skillName), options);
|
|
24628
24916
|
const root2 = getPortableSkillsRoot(options);
|
|
24629
|
-
const skillPath =
|
|
24917
|
+
const skillPath = join40(root2, skillName);
|
|
24630
24918
|
assertPortableAuthoringPath(skillPath, options);
|
|
24631
24919
|
if (existsSync14(skillPath)) {
|
|
24632
24920
|
if (!options.overwrite)
|
|
@@ -24660,7 +24948,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
24660
24948
|
const skipped = [];
|
|
24661
24949
|
const entries = readdirSync13(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
|
|
24662
24950
|
for (const entryName of entries) {
|
|
24663
|
-
const childPath =
|
|
24951
|
+
const childPath = join40(absoluteSource, entryName);
|
|
24664
24952
|
if (!safeIsDirectory(childPath))
|
|
24665
24953
|
continue;
|
|
24666
24954
|
if (!isSkillCandidate(childPath)) {
|
|
@@ -24689,23 +24977,23 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
24689
24977
|
};
|
|
24690
24978
|
}
|
|
24691
24979
|
function isSkillCandidate(dir) {
|
|
24692
|
-
return existsSync14(
|
|
24980
|
+
return existsSync14(join40(dir, "SKILL.md")) || existsSync14(join40(dir, "skill.json")) || existsSync14(join40(dir, "package.json"));
|
|
24693
24981
|
}
|
|
24694
24982
|
function physicalImportPath(path) {
|
|
24695
|
-
let ancestor =
|
|
24983
|
+
let ancestor = resolve29(path);
|
|
24696
24984
|
const suffix = [];
|
|
24697
24985
|
while (!existsSync14(ancestor)) {
|
|
24698
24986
|
suffix.unshift(basename17(ancestor));
|
|
24699
|
-
ancestor =
|
|
24987
|
+
ancestor = dirname28(ancestor);
|
|
24700
24988
|
}
|
|
24701
|
-
return
|
|
24989
|
+
return join40(realpathSync15(ancestor), ...suffix);
|
|
24702
24990
|
}
|
|
24703
24991
|
function assertDisjointImportPaths(source, destination) {
|
|
24704
24992
|
const sourcePath = realpathSync15(source);
|
|
24705
24993
|
const destinationPath = physicalImportPath(destination);
|
|
24706
24994
|
const contains2 = (parent, child2) => {
|
|
24707
24995
|
const path = relative11(parent, child2);
|
|
24708
|
-
return path === "" || !isAbsolute29(path) && path !== ".." && !path.startsWith(`..${
|
|
24996
|
+
return path === "" || !isAbsolute29(path) && path !== ".." && !path.startsWith(`..${sep17}`);
|
|
24709
24997
|
};
|
|
24710
24998
|
if (contains2(sourcePath, destinationPath) || contains2(destinationPath, sourcePath)) {
|
|
24711
24999
|
throw new Error("Source and destination skill directories must not overlap");
|
|
@@ -24724,11 +25012,11 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
24724
25012
|
const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename17(absoluteSource)}')` : ""} matches a bundled official skill.`;
|
|
24725
25013
|
throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
|
|
24726
25014
|
}
|
|
24727
|
-
const readOnlyDestination =
|
|
25015
|
+
const readOnlyDestination = join40(resolvePortableSkillsRoot(options, false), skillName);
|
|
24728
25016
|
assertPortableAuthoringPath(readOnlyDestination, options);
|
|
24729
25017
|
assertDisjointImportPaths(absoluteSource, readOnlyDestination);
|
|
24730
25018
|
const root2 = getPortableSkillsRoot(options);
|
|
24731
|
-
const destination =
|
|
25019
|
+
const destination = join40(root2, skillName);
|
|
24732
25020
|
assertPortableAuthoringPath(destination, options);
|
|
24733
25021
|
assertDisjointImportPaths(absoluteSource, destination);
|
|
24734
25022
|
if (existsSync14(destination)) {
|
|
@@ -24736,7 +25024,7 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
24736
25024
|
throw new Error(`Skill '${skillName}' already exists at ${destination}`);
|
|
24737
25025
|
rmSync4(destination, { recursive: true, force: true });
|
|
24738
25026
|
}
|
|
24739
|
-
mkdirSync11(
|
|
25027
|
+
mkdirSync11(dirname28(destination), { recursive: true });
|
|
24740
25028
|
copySkillDirectory(absoluteSource, destination);
|
|
24741
25029
|
const base = {
|
|
24742
25030
|
...inferred,
|
|
@@ -24773,13 +25061,13 @@ function buildCorpusManifest(input, name) {
|
|
|
24773
25061
|
function writeCorpusSkill(input, options = {}) {
|
|
24774
25062
|
const name = normalizePortableSkillName(input.name);
|
|
24775
25063
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
24776
|
-
assertPortableAuthoringPath(
|
|
25064
|
+
assertPortableAuthoringPath(join40(readOnlyRoot, name), options);
|
|
24777
25065
|
const root2 = getPortableSkillsRoot(options);
|
|
24778
|
-
const skillPath =
|
|
25066
|
+
const skillPath = join40(root2, name);
|
|
24779
25067
|
assertPortableAuthoringPath(skillPath, options);
|
|
24780
25068
|
const created = !existsSync14(skillPath);
|
|
24781
25069
|
mkdirSync11(skillPath, { recursive: true });
|
|
24782
|
-
writeFileSync9(
|
|
25070
|
+
writeFileSync9(join40(skillPath, "SKILL.md"), input.skillMd);
|
|
24783
25071
|
const manifest = buildCorpusManifest(input, name);
|
|
24784
25072
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
24785
25073
|
return { name, path: skillPath, manifest, created };
|
|
@@ -24787,22 +25075,22 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
24787
25075
|
function installCorpusSkillAtomically(input, options = {}) {
|
|
24788
25076
|
const name = normalizePortableSkillName(input.name);
|
|
24789
25077
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
24790
|
-
assertPortableAuthoringPath(
|
|
25078
|
+
assertPortableAuthoringPath(join40(readOnlyRoot, name), options);
|
|
24791
25079
|
const root2 = getPortableSkillsRoot(options);
|
|
24792
|
-
const target =
|
|
25080
|
+
const target = join40(root2, name);
|
|
24793
25081
|
assertPortableAuthoringPath(target, options);
|
|
24794
25082
|
const created = !existsSync14(target);
|
|
24795
25083
|
mkdirSync11(root2, { recursive: true });
|
|
24796
|
-
const staging = mkdtempSync2(
|
|
25084
|
+
const staging = mkdtempSync2(join40(root2, `.pull-${name}-`));
|
|
24797
25085
|
let moved = false;
|
|
24798
25086
|
let backup = null;
|
|
24799
25087
|
try {
|
|
24800
|
-
writeFileSync9(
|
|
25088
|
+
writeFileSync9(join40(staging, "SKILL.md"), input.skillMd);
|
|
24801
25089
|
const manifest = buildCorpusManifest(input, name);
|
|
24802
25090
|
writeSkillJsonWithHash(staging, manifest);
|
|
24803
25091
|
if (existsSync14(target)) {
|
|
24804
|
-
backup = mkdtempSync2(
|
|
24805
|
-
renameSync6(target,
|
|
25092
|
+
backup = mkdtempSync2(join40(root2, `.pull-backup-${name}-`));
|
|
25093
|
+
renameSync6(target, join40(backup, name));
|
|
24806
25094
|
moved = true;
|
|
24807
25095
|
}
|
|
24808
25096
|
renameSync6(staging, target);
|
|
@@ -24811,9 +25099,9 @@ function installCorpusSkillAtomically(input, options = {}) {
|
|
|
24811
25099
|
return { name, path: target, manifest, created };
|
|
24812
25100
|
} catch (error) {
|
|
24813
25101
|
rmSync4(staging, { recursive: true, force: true });
|
|
24814
|
-
if (moved && backup && existsSync14(
|
|
25102
|
+
if (moved && backup && existsSync14(join40(backup, name))) {
|
|
24815
25103
|
try {
|
|
24816
|
-
renameSync6(
|
|
25104
|
+
renameSync6(join40(backup, name), target);
|
|
24817
25105
|
} catch {}
|
|
24818
25106
|
}
|
|
24819
25107
|
throw error;
|
|
@@ -24826,8 +25114,8 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24826
25114
|
const warnings = [...base.warnings];
|
|
24827
25115
|
let manifest;
|
|
24828
25116
|
if (existsSync14(skillPath)) {
|
|
24829
|
-
const skillJsonPath =
|
|
24830
|
-
const skillMdPath =
|
|
25117
|
+
const skillJsonPath = join40(skillPath, "skill.json");
|
|
25118
|
+
const skillMdPath = join40(skillPath, "SKILL.md");
|
|
24831
25119
|
if (!existsSync14(skillJsonPath) && !existsSync14(skillMdPath)) {
|
|
24832
25120
|
add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
|
|
24833
25121
|
}
|
|
@@ -24847,7 +25135,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24847
25135
|
add3(issues, "portable.version_missing", "Portable manifest missing version");
|
|
24848
25136
|
}
|
|
24849
25137
|
const contractIssues = validatePortableManifestContract(manifest, {
|
|
24850
|
-
strict: existsSync14(
|
|
25138
|
+
strict: existsSync14(join40(skillPath, "skill.json")),
|
|
24851
25139
|
skillPath
|
|
24852
25140
|
});
|
|
24853
25141
|
for (const issue of contractIssues)
|
|
@@ -24887,7 +25175,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24887
25175
|
add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
|
|
24888
25176
|
continue;
|
|
24889
25177
|
}
|
|
24890
|
-
const entryPath =
|
|
25178
|
+
const entryPath = join40(skillPath, command.entry);
|
|
24891
25179
|
if (!existsSync14(entryPath))
|
|
24892
25180
|
add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
|
|
24893
25181
|
else if (statSync13(entryPath).isDirectory())
|
|
@@ -24898,7 +25186,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
24898
25186
|
} catch (error) {
|
|
24899
25187
|
add3(issues, "portable.manifest_invalid", error.message);
|
|
24900
25188
|
}
|
|
24901
|
-
if (manifest?.kind !== "instruction" && !existsSync14(
|
|
25189
|
+
if (manifest?.kind !== "instruction" && !existsSync14(join40(skillPath, "AGENTS.md"))) {
|
|
24902
25190
|
add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
|
|
24903
25191
|
}
|
|
24904
25192
|
}
|
|
@@ -24934,12 +25222,12 @@ async function runPortableSkill(name, args, options = {}) {
|
|
|
24934
25222
|
if (!isSafeRelativePath2(command.entry)) {
|
|
24935
25223
|
return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
|
|
24936
25224
|
}
|
|
24937
|
-
const entryPath =
|
|
25225
|
+
const entryPath = join40(skill.path, command.entry);
|
|
24938
25226
|
if (!existsSync14(entryPath)) {
|
|
24939
25227
|
return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
|
|
24940
25228
|
}
|
|
24941
|
-
const pkgPath =
|
|
24942
|
-
const nodeModules =
|
|
25229
|
+
const pkgPath = join40(skill.path, "package.json");
|
|
25230
|
+
const nodeModules = join40(skill.path, "node_modules");
|
|
24943
25231
|
if (existsSync14(pkgPath) && !existsSync14(nodeModules) && hasPackageDependencies(pkgPath)) {
|
|
24944
25232
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
24945
25233
|
cwd: skill.path,
|
|
@@ -25196,12 +25484,12 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
25196
25484
|
for (const entry of entries) {
|
|
25197
25485
|
if (!entry.isDirectory())
|
|
25198
25486
|
continue;
|
|
25199
|
-
const skillMdPath =
|
|
25487
|
+
const skillMdPath = join41(dir, entry.name, "SKILL.md");
|
|
25200
25488
|
if (!existsSync15(skillMdPath))
|
|
25201
25489
|
continue;
|
|
25202
25490
|
let content;
|
|
25203
25491
|
try {
|
|
25204
|
-
content =
|
|
25492
|
+
content = readFileSync27(skillMdPath, "utf-8");
|
|
25205
25493
|
} catch {
|
|
25206
25494
|
continue;
|
|
25207
25495
|
}
|
|
@@ -25216,7 +25504,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
25216
25504
|
category: fm.category || "Development Tools",
|
|
25217
25505
|
tags: fm.tags || [],
|
|
25218
25506
|
...fm.kind ? { kind: fm.kind } : {},
|
|
25219
|
-
...isHostedMetadataSkillDir(
|
|
25507
|
+
...isHostedMetadataSkillDir(join41(dir, entry.name)) ? { serverOwned: true } : {},
|
|
25220
25508
|
source
|
|
25221
25509
|
});
|
|
25222
25510
|
}
|
|
@@ -25232,13 +25520,13 @@ function findExtensionSkillPath(name) {
|
|
|
25232
25520
|
for (const entry of entries) {
|
|
25233
25521
|
if (!entry.isDirectory())
|
|
25234
25522
|
continue;
|
|
25235
|
-
const skillDir =
|
|
25236
|
-
const skillMdPath =
|
|
25523
|
+
const skillDir = join41(config.extensionsDir, entry.name);
|
|
25524
|
+
const skillMdPath = join41(skillDir, "SKILL.md");
|
|
25237
25525
|
if (!existsSync15(skillMdPath))
|
|
25238
25526
|
continue;
|
|
25239
25527
|
let content;
|
|
25240
25528
|
try {
|
|
25241
|
-
content =
|
|
25529
|
+
content = readFileSync27(skillMdPath, "utf-8");
|
|
25242
25530
|
} catch {
|
|
25243
25531
|
continue;
|
|
25244
25532
|
}
|
|
@@ -25303,20 +25591,20 @@ function getAllTags(catalog) {
|
|
|
25303
25591
|
}
|
|
25304
25592
|
// src/lib/run-state.ts
|
|
25305
25593
|
import { createHash as createHash31, randomBytes } from "crypto";
|
|
25306
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as
|
|
25307
|
-
import { extname, join as
|
|
25594
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync28, readdirSync as readdirSync15, statSync as statSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
25595
|
+
import { extname, join as join42, relative as relative12 } from "path";
|
|
25308
25596
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
25309
25597
|
const now = new Date;
|
|
25310
25598
|
const id = createRunId(now);
|
|
25311
25599
|
const day = now.toISOString().slice(0, 10);
|
|
25312
25600
|
const skillName = normalizeSkillName(params.skill);
|
|
25313
25601
|
const root2 = getProjectStateDir(targetDir);
|
|
25314
|
-
const runDir =
|
|
25315
|
-
const logsDir =
|
|
25316
|
-
const exportDir =
|
|
25602
|
+
const runDir = join42(root2, "runs", day, id);
|
|
25603
|
+
const logsDir = join42(runDir, "logs");
|
|
25604
|
+
const exportDir = join42(root2, "exports", skillName, id);
|
|
25317
25605
|
mkdirSync12(logsDir, { recursive: true });
|
|
25318
25606
|
mkdirSync12(exportDir, { recursive: true });
|
|
25319
|
-
mkdirSync12(
|
|
25607
|
+
mkdirSync12(join42(root2, "tmp"), { recursive: true });
|
|
25320
25608
|
const record5 = {
|
|
25321
25609
|
id,
|
|
25322
25610
|
skill: skillName,
|
|
@@ -25368,27 +25656,27 @@ function updateSkillRun(context, patch) {
|
|
|
25368
25656
|
return context.record;
|
|
25369
25657
|
}
|
|
25370
25658
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
25371
|
-
writeFileSync10(
|
|
25372
|
-
writeFileSync10(
|
|
25659
|
+
writeFileSync10(join42(context.logsDir, "stdout.log"), stdout);
|
|
25660
|
+
writeFileSync10(join42(context.logsDir, "stderr.log"), stderr);
|
|
25373
25661
|
}
|
|
25374
25662
|
function appendRunEvent(context, event, data = {}) {
|
|
25375
25663
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
25376
25664
|
`;
|
|
25377
|
-
const path =
|
|
25378
|
-
const previous = existsSync16(path) ?
|
|
25665
|
+
const path = join42(context.runDir, "events.ndjson");
|
|
25666
|
+
const previous = existsSync16(path) ? readFileSync28(path, "utf-8") : "";
|
|
25379
25667
|
writeFileSync10(path, previous + line);
|
|
25380
25668
|
}
|
|
25381
25669
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
25382
|
-
const runsRoot =
|
|
25670
|
+
const runsRoot = join42(getProjectStateDir(targetDir), "runs");
|
|
25383
25671
|
if (!existsSync16(runsRoot))
|
|
25384
25672
|
return [];
|
|
25385
25673
|
const records = [];
|
|
25386
25674
|
for (const day of readdirSync15(runsRoot).sort().reverse()) {
|
|
25387
|
-
const dayDir =
|
|
25675
|
+
const dayDir = join42(runsRoot, day);
|
|
25388
25676
|
if (!statSync14(dayDir).isDirectory())
|
|
25389
25677
|
continue;
|
|
25390
25678
|
for (const runId of readdirSync15(dayDir).sort().reverse()) {
|
|
25391
|
-
const record5 = readRunRecord(
|
|
25679
|
+
const record5 = readRunRecord(join42(dayDir, runId));
|
|
25392
25680
|
if (record5)
|
|
25393
25681
|
records.push(record5);
|
|
25394
25682
|
if (records.length >= limit)
|
|
@@ -25398,25 +25686,25 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
25398
25686
|
return records;
|
|
25399
25687
|
}
|
|
25400
25688
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
25401
|
-
const runsRoot =
|
|
25689
|
+
const runsRoot = join42(getProjectStateDir(targetDir), "runs");
|
|
25402
25690
|
if (!existsSync16(runsRoot))
|
|
25403
25691
|
return null;
|
|
25404
25692
|
for (const day of readdirSync15(runsRoot)) {
|
|
25405
|
-
const record5 = readRunRecord(
|
|
25693
|
+
const record5 = readRunRecord(join42(runsRoot, day, runId));
|
|
25406
25694
|
if (record5)
|
|
25407
25695
|
return record5;
|
|
25408
25696
|
}
|
|
25409
25697
|
return null;
|
|
25410
25698
|
}
|
|
25411
25699
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
25412
|
-
return
|
|
25700
|
+
return join42(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
25413
25701
|
}
|
|
25414
25702
|
function writeRunRecord(context) {
|
|
25415
|
-
writeFileSync10(
|
|
25703
|
+
writeFileSync10(join42(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
25416
25704
|
`);
|
|
25417
25705
|
}
|
|
25418
25706
|
function writeArtifactsManifest(context, artifacts) {
|
|
25419
|
-
writeFileSync10(
|
|
25707
|
+
writeFileSync10(join42(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
25420
25708
|
`);
|
|
25421
25709
|
}
|
|
25422
25710
|
function collectRunArtifacts(context) {
|
|
@@ -25425,7 +25713,7 @@ function collectRunArtifacts(context) {
|
|
|
25425
25713
|
const artifacts = [];
|
|
25426
25714
|
for (const path of walkFiles(context.exportDir)) {
|
|
25427
25715
|
const stat2 = statSync14(path);
|
|
25428
|
-
const bytes =
|
|
25716
|
+
const bytes = readFileSync28(path);
|
|
25429
25717
|
artifacts.push({
|
|
25430
25718
|
path: toProjectRelative(context.targetDir, path),
|
|
25431
25719
|
mime: mimeForPath(path),
|
|
@@ -25436,11 +25724,11 @@ function collectRunArtifacts(context) {
|
|
|
25436
25724
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
25437
25725
|
}
|
|
25438
25726
|
function readRunRecord(runDir) {
|
|
25439
|
-
const path =
|
|
25727
|
+
const path = join42(runDir, "run.json");
|
|
25440
25728
|
if (!existsSync16(path))
|
|
25441
25729
|
return null;
|
|
25442
25730
|
try {
|
|
25443
|
-
return JSON.parse(
|
|
25731
|
+
return JSON.parse(readFileSync28(path, "utf-8"));
|
|
25444
25732
|
} catch {
|
|
25445
25733
|
return null;
|
|
25446
25734
|
}
|
|
@@ -25448,7 +25736,7 @@ function readRunRecord(runDir) {
|
|
|
25448
25736
|
function walkFiles(dir) {
|
|
25449
25737
|
const files = [];
|
|
25450
25738
|
for (const entry of readdirSync15(dir)) {
|
|
25451
|
-
const full =
|
|
25739
|
+
const full = join42(dir, entry);
|
|
25452
25740
|
if (statSync14(full).isDirectory())
|
|
25453
25741
|
files.push(...walkFiles(full));
|
|
25454
25742
|
else
|
|
@@ -45853,10 +46141,10 @@ async function readQuoteUnavailableCode(response) {
|
|
|
45853
46141
|
let timer;
|
|
45854
46142
|
let deadlineExceeded = false;
|
|
45855
46143
|
const expired = Symbol("quote body deadline");
|
|
45856
|
-
const deadline = new Promise((
|
|
46144
|
+
const deadline = new Promise((resolve30) => {
|
|
45857
46145
|
timer = setTimeout(() => {
|
|
45858
46146
|
deadlineExceeded = true;
|
|
45859
|
-
|
|
46147
|
+
resolve30(expired);
|
|
45860
46148
|
reader.cancel().catch(() => {});
|
|
45861
46149
|
}, 1500);
|
|
45862
46150
|
});
|
|
@@ -46955,23 +47243,23 @@ async function createRemoteSkillsClient(env = process.env) {
|
|
|
46955
47243
|
return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
|
|
46956
47244
|
}
|
|
46957
47245
|
// src/lib/scheduler.ts
|
|
46958
|
-
import { existsSync as existsSync17, readFileSync as
|
|
46959
|
-
import { join as
|
|
47246
|
+
import { existsSync as existsSync17, readFileSync as readFileSync29, writeFileSync as writeFileSync11, mkdirSync as mkdirSync13 } from "fs";
|
|
47247
|
+
import { join as join43 } from "path";
|
|
46960
47248
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
46961
|
-
return
|
|
47249
|
+
return join43(targetDir, ".skills", "schedules.json");
|
|
46962
47250
|
}
|
|
46963
47251
|
function loadSchedules(targetDir = process.cwd()) {
|
|
46964
47252
|
const path = getSchedulesPath(targetDir);
|
|
46965
47253
|
if (existsSync17(path)) {
|
|
46966
47254
|
try {
|
|
46967
|
-
return JSON.parse(
|
|
47255
|
+
return JSON.parse(readFileSync29(path, "utf-8"));
|
|
46968
47256
|
} catch {}
|
|
46969
47257
|
}
|
|
46970
47258
|
return { version: 1, schedules: [] };
|
|
46971
47259
|
}
|
|
46972
47260
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
46973
47261
|
const path = getSchedulesPath(targetDir);
|
|
46974
|
-
const dir =
|
|
47262
|
+
const dir = join43(targetDir, ".skills");
|
|
46975
47263
|
if (!existsSync17(dir))
|
|
46976
47264
|
mkdirSync13(dir, { recursive: true });
|
|
46977
47265
|
writeFileSync11(path, JSON.stringify(data, null, 2));
|
|
@@ -47187,8 +47475,8 @@ function revisionIdOfRecord(record10) {
|
|
|
47187
47475
|
return revisionIdOf(record10);
|
|
47188
47476
|
}
|
|
47189
47477
|
// src/lib/pull.ts
|
|
47190
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync3, readFileSync as
|
|
47191
|
-
import { dirname as
|
|
47478
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync3, readFileSync as readFileSync30, renameSync as renameSync7, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "fs";
|
|
47479
|
+
import { dirname as dirname29, join as join44 } from "path";
|
|
47192
47480
|
|
|
47193
47481
|
// src/lib/skill-bundles.ts
|
|
47194
47482
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -47282,7 +47570,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
47282
47570
|
return reconcileTombstone(slug2, corpusOptions);
|
|
47283
47571
|
}
|
|
47284
47572
|
if (bundleResponse.status === 404) {
|
|
47285
|
-
const marker = readPullMarker(
|
|
47573
|
+
const marker = readPullMarker(join44(getPortableSkillsRoot(corpusOptions), slug2));
|
|
47286
47574
|
if (isPublishedInstallMarker(marker)) {
|
|
47287
47575
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
47288
47576
|
}
|
|
@@ -47321,7 +47609,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
47321
47609
|
return { name: slug2, success: false, error: `Skill '${slug2}' was not found on the configured Skills instance.` };
|
|
47322
47610
|
}
|
|
47323
47611
|
if (!meta3?.revisionId) {
|
|
47324
|
-
const marker = readPullMarker(
|
|
47612
|
+
const marker = readPullMarker(join44(getPortableSkillsRoot(corpusOptions), slug2));
|
|
47325
47613
|
if (isPublishedInstallMarker(marker)) {
|
|
47326
47614
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
47327
47615
|
}
|
|
@@ -47378,7 +47666,7 @@ function provenRevision(meta3, slug2, bundle2) {
|
|
|
47378
47666
|
return declared;
|
|
47379
47667
|
}
|
|
47380
47668
|
function reconcileTombstone(slug2, corpusOptions) {
|
|
47381
|
-
const target =
|
|
47669
|
+
const target = join44(getPortableSkillsRoot(corpusOptions), slug2);
|
|
47382
47670
|
if (!hasSkillsOwnershipMarker(target)) {
|
|
47383
47671
|
return { name: slug2, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
47384
47672
|
}
|
|
@@ -47387,7 +47675,7 @@ function reconcileTombstone(slug2, corpusOptions) {
|
|
|
47387
47675
|
}
|
|
47388
47676
|
function readPullMarker(dir) {
|
|
47389
47677
|
try {
|
|
47390
|
-
return JSON.parse(
|
|
47678
|
+
return JSON.parse(readFileSync30(join44(dir, PULL_MARKER_FILE), "utf-8"));
|
|
47391
47679
|
} catch {
|
|
47392
47680
|
return null;
|
|
47393
47681
|
}
|
|
@@ -47522,15 +47810,15 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
47522
47810
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
47523
47811
|
const root2 = getPortableSkillsRoot(options);
|
|
47524
47812
|
mkdirSync14(root2, { recursive: true });
|
|
47525
|
-
const target =
|
|
47813
|
+
const target = join44(root2, name);
|
|
47526
47814
|
const created = !existsSync18(target);
|
|
47527
|
-
const staging = mkdtempSync3(
|
|
47815
|
+
const staging = mkdtempSync3(join44(root2, `.pull-${name}-`));
|
|
47528
47816
|
let moved = false;
|
|
47529
47817
|
let backup = null;
|
|
47530
47818
|
try {
|
|
47531
47819
|
for (const entry of entries) {
|
|
47532
|
-
const destination =
|
|
47533
|
-
mkdirSync14(
|
|
47820
|
+
const destination = join44(staging, entry.path);
|
|
47821
|
+
mkdirSync14(dirname29(destination), { recursive: true });
|
|
47534
47822
|
writeFileSync12(destination, entry.bytes, { mode: entry.mode });
|
|
47535
47823
|
}
|
|
47536
47824
|
writePullMarker(staging, {
|
|
@@ -47542,8 +47830,8 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
47542
47830
|
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
47543
47831
|
});
|
|
47544
47832
|
if (existsSync18(target)) {
|
|
47545
|
-
backup = mkdtempSync3(
|
|
47546
|
-
renameSync7(target,
|
|
47833
|
+
backup = mkdtempSync3(join44(root2, `.pull-backup-${name}-`));
|
|
47834
|
+
renameSync7(target, join44(backup, name));
|
|
47547
47835
|
moved = true;
|
|
47548
47836
|
}
|
|
47549
47837
|
renameSync7(staging, target);
|
|
@@ -47551,9 +47839,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
47551
47839
|
rmSync5(backup, { recursive: true, force: true });
|
|
47552
47840
|
} catch (error61) {
|
|
47553
47841
|
rmSync5(staging, { recursive: true, force: true });
|
|
47554
|
-
if (moved && backup && existsSync18(
|
|
47842
|
+
if (moved && backup && existsSync18(join44(backup, name))) {
|
|
47555
47843
|
try {
|
|
47556
|
-
renameSync7(
|
|
47844
|
+
renameSync7(join44(backup, name), target);
|
|
47557
47845
|
} catch {}
|
|
47558
47846
|
}
|
|
47559
47847
|
throw error61;
|
|
@@ -47572,7 +47860,7 @@ function writePullMarker(dir, record10) {
|
|
|
47572
47860
|
...record10.revisionId ? { revisionId: record10.revisionId } : {},
|
|
47573
47861
|
syncedAt: new Date().toISOString()
|
|
47574
47862
|
};
|
|
47575
|
-
writeFileSync12(
|
|
47863
|
+
writeFileSync12(join44(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
47576
47864
|
`);
|
|
47577
47865
|
}
|
|
47578
47866
|
async function safeMeta(client2, slug2) {
|
|
@@ -48014,12 +48302,12 @@ class RemotePrivatePublicationsClient {
|
|
|
48014
48302
|
previous = view;
|
|
48015
48303
|
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
48016
48304
|
return view;
|
|
48017
|
-
await new Promise((
|
|
48305
|
+
await new Promise((resolve30) => {
|
|
48018
48306
|
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
48019
48307
|
function done() {
|
|
48020
48308
|
clearTimeout(timer);
|
|
48021
48309
|
options.signal?.removeEventListener("abort", done);
|
|
48022
|
-
|
|
48310
|
+
resolve30();
|
|
48023
48311
|
}
|
|
48024
48312
|
options.signal?.addEventListener("abort", done, { once: true });
|
|
48025
48313
|
});
|
|
@@ -48434,7 +48722,7 @@ function findSkillsParityForMcpTool(tool) {
|
|
|
48434
48722
|
}
|
|
48435
48723
|
// src/lib/registry-sync.ts
|
|
48436
48724
|
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
48437
|
-
import { dirname as
|
|
48725
|
+
import { dirname as dirname30, relative as relative13 } from "path";
|
|
48438
48726
|
function createRegistrySyncArtifact(options = {}) {
|
|
48439
48727
|
const profile = options.profile ?? "all";
|
|
48440
48728
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -48491,7 +48779,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
48491
48779
|
};
|
|
48492
48780
|
}
|
|
48493
48781
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
48494
|
-
mkdirSync15(
|
|
48782
|
+
mkdirSync15(dirname30(path), { recursive: true });
|
|
48495
48783
|
writeFileSync13(path, `${JSON.stringify(artifact, null, 2)}
|
|
48496
48784
|
`);
|
|
48497
48785
|
}
|
|
@@ -49576,14 +49864,14 @@ function clone3(value) {
|
|
|
49576
49864
|
}
|
|
49577
49865
|
// src/lib/feedback.ts
|
|
49578
49866
|
import { appendFileSync, existsSync as existsSync19, mkdirSync as mkdirSync16 } from "fs";
|
|
49579
|
-
import { dirname as
|
|
49867
|
+
import { dirname as dirname31, join as join45 } from "path";
|
|
49580
49868
|
import { Database } from "bun:sqlite";
|
|
49581
49869
|
function getFeedbackDbPath() {
|
|
49582
|
-
return
|
|
49870
|
+
return join45(getDataDir(), "skills.db");
|
|
49583
49871
|
}
|
|
49584
49872
|
function getFeedbackDb() {
|
|
49585
49873
|
const dbPath = getFeedbackDbPath();
|
|
49586
|
-
const dir =
|
|
49874
|
+
const dir = dirname31(dbPath);
|
|
49587
49875
|
if (!existsSync19(dir))
|
|
49588
49876
|
mkdirSync16(dir, { recursive: true });
|
|
49589
49877
|
const db = new Database(dbPath);
|
|
@@ -49611,8 +49899,8 @@ function saveFeedback(input2) {
|
|
|
49611
49899
|
throw new Error("Feedback message is required");
|
|
49612
49900
|
const category = input2.category ?? "general";
|
|
49613
49901
|
if (isApiMode()) {
|
|
49614
|
-
const path =
|
|
49615
|
-
const dir =
|
|
49902
|
+
const path = join45(getDataDir(), "feedback.jsonl");
|
|
49903
|
+
const dir = dirname31(path);
|
|
49616
49904
|
if (!existsSync19(dir))
|
|
49617
49905
|
mkdirSync16(dir, { recursive: true });
|
|
49618
49906
|
appendFileSync(path, JSON.stringify({ message, category, email: input2.email ?? null, agent: input2.agent ?? null, version: input2.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
@@ -49639,12 +49927,12 @@ import { createHash as createHash35, createHmac as createHmac2 } from "crypto";
|
|
|
49639
49927
|
import {
|
|
49640
49928
|
existsSync as existsSync20,
|
|
49641
49929
|
mkdirSync as mkdirSync17,
|
|
49642
|
-
readFileSync as
|
|
49930
|
+
readFileSync as readFileSync31,
|
|
49643
49931
|
readdirSync as readdirSync16,
|
|
49644
49932
|
statSync as statSync15,
|
|
49645
49933
|
writeFileSync as writeFileSync14
|
|
49646
49934
|
} from "fs";
|
|
49647
|
-
import { dirname as
|
|
49935
|
+
import { dirname as dirname32, join as join46, normalize as normalize5, relative as relative14, sep as sep18 } from "path";
|
|
49648
49936
|
var SKILLS_STORAGE_TABLES = [
|
|
49649
49937
|
"skills_sync_records",
|
|
49650
49938
|
"skills_sync_cursors"
|
|
@@ -49781,7 +50069,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
49781
50069
|
local: {
|
|
49782
50070
|
dataDir: getDataDir(),
|
|
49783
50071
|
projectStateDir: getProjectStateDir(targetDir),
|
|
49784
|
-
feedbackDbPath:
|
|
50072
|
+
feedbackDbPath: join46(getDataDir(), "skills.db")
|
|
49785
50073
|
},
|
|
49786
50074
|
remote: {
|
|
49787
50075
|
databaseConfigured: Boolean(config2.databaseUrl),
|
|
@@ -49806,7 +50094,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
49806
50094
|
const files = [];
|
|
49807
50095
|
if (existsSync20(projectStateDir)) {
|
|
49808
50096
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
49809
|
-
const bytes =
|
|
50097
|
+
const bytes = readFileSync31(filePath);
|
|
49810
50098
|
const relativePath = toPosix(relative14(targetDir, filePath));
|
|
49811
50099
|
files.push({
|
|
49812
50100
|
path: relativePath,
|
|
@@ -49840,7 +50128,7 @@ function importSkillsLocalSnapshot(snapshot2, targetDir = process.cwd(), options
|
|
|
49840
50128
|
if (hash5 !== file2.sha256) {
|
|
49841
50129
|
throw new Error(`Snapshot file checksum mismatch: ${file2.path}`);
|
|
49842
50130
|
}
|
|
49843
|
-
mkdirSync17(
|
|
50131
|
+
mkdirSync17(dirname32(absolutePath5), { recursive: true });
|
|
49844
50132
|
writeFileSync14(absolutePath5, bytes);
|
|
49845
50133
|
written += 1;
|
|
49846
50134
|
}
|
|
@@ -50138,7 +50426,7 @@ function parsePositiveInteger(value) {
|
|
|
50138
50426
|
function walkFiles2(dir) {
|
|
50139
50427
|
const files = [];
|
|
50140
50428
|
for (const entry of readdirSync16(dir)) {
|
|
50141
|
-
const full =
|
|
50429
|
+
const full = join46(dir, entry);
|
|
50142
50430
|
const stats = statSync15(full);
|
|
50143
50431
|
if (stats.isDirectory())
|
|
50144
50432
|
files.push(...walkFiles2(full));
|
|
@@ -50149,13 +50437,13 @@ function walkFiles2(dir) {
|
|
|
50149
50437
|
}
|
|
50150
50438
|
function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
50151
50439
|
const normalizedPath = normalize5(snapshotPath);
|
|
50152
|
-
if (normalizedPath.startsWith("..") || normalizedPath.includes(`${
|
|
50440
|
+
if (normalizedPath.startsWith("..") || normalizedPath.includes(`${sep18}..${sep18}`) || normalizedPath.startsWith(sep18)) {
|
|
50153
50441
|
throw new Error(`Unsafe snapshot path: ${snapshotPath}`);
|
|
50154
50442
|
}
|
|
50155
50443
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
50156
50444
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
50157
50445
|
}
|
|
50158
|
-
return
|
|
50446
|
+
return join46(targetDir, normalizedPath);
|
|
50159
50447
|
}
|
|
50160
50448
|
function normalizeS3Prefix(prefix) {
|
|
50161
50449
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -50224,16 +50512,16 @@ import { createHash as createHash36 } from "crypto";
|
|
|
50224
50512
|
import {
|
|
50225
50513
|
copyFileSync,
|
|
50226
50514
|
mkdirSync as mkdirSync18,
|
|
50227
|
-
readFileSync as
|
|
50515
|
+
readFileSync as readFileSync32,
|
|
50228
50516
|
statSync as statSync17,
|
|
50229
50517
|
writeFileSync as writeFileSync15
|
|
50230
50518
|
} from "fs";
|
|
50231
|
-
import { dirname as
|
|
50519
|
+
import { dirname as dirname33, isAbsolute as isAbsolute31, relative as relative15, resolve as resolve30, sep as sep20 } from "path";
|
|
50232
50520
|
|
|
50233
50521
|
// src/lib/portable-snapshot-filter.ts
|
|
50234
50522
|
import { readdirSync as readdirSync17, statSync as statSync16 } from "fs";
|
|
50235
50523
|
import { homedir as homedir7 } from "os";
|
|
50236
|
-
import { join as
|
|
50524
|
+
import { join as join47, sep as sep19 } from "path";
|
|
50237
50525
|
var SYNC_HOMES = [
|
|
50238
50526
|
{ name: "skills", subClass: "skills", agent: null },
|
|
50239
50527
|
{ name: "custom", subClass: "custom", agent: null },
|
|
@@ -50326,16 +50614,16 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
50326
50614
|
function homePathFor(definition, homesRoot) {
|
|
50327
50615
|
const home = homesRoot ?? homedir7();
|
|
50328
50616
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
50329
|
-
return
|
|
50617
|
+
return join47(skillsDataRootForHome(home), definition.name);
|
|
50330
50618
|
}
|
|
50331
50619
|
if (definition.agent === "opencode") {
|
|
50332
|
-
return
|
|
50620
|
+
return join47(home, ".config", "opencode", "skills");
|
|
50333
50621
|
}
|
|
50334
|
-
return
|
|
50622
|
+
return join47(home, `.${definition.agent}`, "skills");
|
|
50335
50623
|
}
|
|
50336
50624
|
function destinationFor(definition, stationId, relativePath) {
|
|
50337
|
-
const category = definition.subClass === "agent-homes" ?
|
|
50338
|
-
return
|
|
50625
|
+
const category = definition.subClass === "agent-homes" ? join47("agent-homes", definition.agent ?? "") : definition.name;
|
|
50626
|
+
return join47("resources", stationId, "skills", category, ...relativePath.split(sep19));
|
|
50339
50627
|
}
|
|
50340
50628
|
function walkEntries(absoluteRoot) {
|
|
50341
50629
|
let entries;
|
|
@@ -50346,7 +50634,7 @@ function walkEntries(absoluteRoot) {
|
|
|
50346
50634
|
}
|
|
50347
50635
|
const output2 = [];
|
|
50348
50636
|
for (const entry of entries) {
|
|
50349
|
-
const childFull =
|
|
50637
|
+
const childFull = join47(absoluteRoot, entry.name);
|
|
50350
50638
|
if (entry.isSymbolicLink()) {
|
|
50351
50639
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
50352
50640
|
continue;
|
|
@@ -50357,7 +50645,7 @@ function walkEntries(absoluteRoot) {
|
|
|
50357
50645
|
}
|
|
50358
50646
|
const nested = walkEntries(childFull);
|
|
50359
50647
|
for (const item of nested) {
|
|
50360
|
-
output2.push({ ...item, relativePath:
|
|
50648
|
+
output2.push({ ...item, relativePath: join47(entry.name, item.relativePath) });
|
|
50361
50649
|
}
|
|
50362
50650
|
continue;
|
|
50363
50651
|
}
|
|
@@ -50395,7 +50683,7 @@ function validateStationId(stationId) {
|
|
|
50395
50683
|
}
|
|
50396
50684
|
}
|
|
50397
50685
|
function sha256File(filePath) {
|
|
50398
|
-
return createHash36("sha256").update(
|
|
50686
|
+
return createHash36("sha256").update(readFileSync32(filePath)).digest("hex");
|
|
50399
50687
|
}
|
|
50400
50688
|
function scanHome(definition, homesRoot) {
|
|
50401
50689
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -50403,7 +50691,7 @@ function scanHome(definition, homesRoot) {
|
|
|
50403
50691
|
const portable = [];
|
|
50404
50692
|
const skipped = [];
|
|
50405
50693
|
for (const entry of entries) {
|
|
50406
|
-
const relativeParts = entry.relativePath.split(
|
|
50694
|
+
const relativeParts = entry.relativePath.split(sep20);
|
|
50407
50695
|
const fileName = relativeParts[relativeParts.length - 1];
|
|
50408
50696
|
if (entry.kind === "symlink") {
|
|
50409
50697
|
skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
|
|
@@ -50466,7 +50754,7 @@ function humanHomes(scanned) {
|
|
|
50466
50754
|
}));
|
|
50467
50755
|
}
|
|
50468
50756
|
function writeStationSnapshot(options) {
|
|
50469
|
-
const repoRoot =
|
|
50757
|
+
const repoRoot = resolve30(options.repoRoot ?? process.cwd());
|
|
50470
50758
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
50471
50759
|
const manifestFiles = plans.map((plan) => ({
|
|
50472
50760
|
relativePath: plan.source.relativePath,
|
|
@@ -50492,9 +50780,9 @@ function writeStationSnapshot(options) {
|
|
|
50492
50780
|
const conflicts = [];
|
|
50493
50781
|
const untouched = [];
|
|
50494
50782
|
for (const plan of plans) {
|
|
50495
|
-
const destination =
|
|
50783
|
+
const destination = resolve30(repoRoot, plan.destination);
|
|
50496
50784
|
const destinationRelative = relative15(repoRoot, destination);
|
|
50497
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(
|
|
50785
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep20) || isAbsolute31(destinationRelative)) {
|
|
50498
50786
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
50499
50787
|
}
|
|
50500
50788
|
let existingDigest = null;
|
|
@@ -50515,8 +50803,8 @@ function writeStationSnapshot(options) {
|
|
|
50515
50803
|
}
|
|
50516
50804
|
let written = 0;
|
|
50517
50805
|
for (const plan of untouched) {
|
|
50518
|
-
const destination =
|
|
50519
|
-
mkdirSync18(
|
|
50806
|
+
const destination = resolve30(repoRoot, plan.destination);
|
|
50807
|
+
mkdirSync18(dirname33(destination), { recursive: true });
|
|
50520
50808
|
copyFileSync(plan.source.fullPath, destination);
|
|
50521
50809
|
written += 1;
|
|
50522
50810
|
}
|
|
@@ -50534,8 +50822,8 @@ function writeStationSnapshot(options) {
|
|
|
50534
50822
|
},
|
|
50535
50823
|
files: manifestFiles
|
|
50536
50824
|
};
|
|
50537
|
-
const manifestPath =
|
|
50538
|
-
mkdirSync18(
|
|
50825
|
+
const manifestPath = resolve30(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
50826
|
+
mkdirSync18(dirname33(manifestPath), { recursive: true });
|
|
50539
50827
|
writeFileSync15(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
50540
50828
|
`);
|
|
50541
50829
|
return {
|
|
@@ -50551,11 +50839,11 @@ import {
|
|
|
50551
50839
|
copyFileSync as copyFileSync2,
|
|
50552
50840
|
mkdirSync as mkdirSync19,
|
|
50553
50841
|
readdirSync as readdirSync18,
|
|
50554
|
-
readFileSync as
|
|
50842
|
+
readFileSync as readFileSync33,
|
|
50555
50843
|
statSync as statSync18,
|
|
50556
50844
|
writeFileSync as writeFileSync16
|
|
50557
50845
|
} from "fs";
|
|
50558
|
-
import { dirname as
|
|
50846
|
+
import { dirname as dirname34, join as join48, resolve as resolve31, sep as sep21 } from "path";
|
|
50559
50847
|
var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
|
|
50560
50848
|
var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
50561
50849
|
var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
@@ -50563,14 +50851,14 @@ function fail2(code2, message, detail = []) {
|
|
|
50563
50851
|
throw new StationSnapshotError(code2, message, detail);
|
|
50564
50852
|
}
|
|
50565
50853
|
function snapshotRootFor(repoRoot, stationId) {
|
|
50566
|
-
return
|
|
50854
|
+
return join48(repoRoot, "resources", stationId, "skills");
|
|
50567
50855
|
}
|
|
50568
50856
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
50569
50857
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
50570
|
-
const manifestPath =
|
|
50858
|
+
const manifestPath = join48(snapshotRoot, "sync-manifest.json");
|
|
50571
50859
|
let manifest;
|
|
50572
50860
|
try {
|
|
50573
|
-
manifest = JSON.parse(
|
|
50861
|
+
manifest = JSON.parse(readFileSync33(manifestPath, "utf8"));
|
|
50574
50862
|
} catch (error61) {
|
|
50575
50863
|
fail2("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
50576
50864
|
}
|
|
@@ -50594,7 +50882,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50594
50882
|
const hashMismatches = [];
|
|
50595
50883
|
const skippedByRule = [];
|
|
50596
50884
|
for (const agent of SYNC_AGENTS) {
|
|
50597
|
-
const agentRoot =
|
|
50885
|
+
const agentRoot = join48(snapshotRoot, "agent-homes", agent);
|
|
50598
50886
|
let identEntries;
|
|
50599
50887
|
try {
|
|
50600
50888
|
identEntries = readdirSync18(agentRoot, { withFileTypes: true });
|
|
@@ -50605,10 +50893,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50605
50893
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
50606
50894
|
continue;
|
|
50607
50895
|
}
|
|
50608
|
-
const identRoot =
|
|
50896
|
+
const identRoot = join48(agentRoot, identEntry.name);
|
|
50609
50897
|
const entries = walkEntries(identRoot);
|
|
50610
50898
|
for (const entry of entries) {
|
|
50611
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
50899
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep21)];
|
|
50612
50900
|
if (entry.kind === "symlink") {
|
|
50613
50901
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
50614
50902
|
continue;
|
|
@@ -50632,8 +50920,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50632
50920
|
});
|
|
50633
50921
|
continue;
|
|
50634
50922
|
}
|
|
50635
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
50636
|
-
const homeRelative = relativeParts.join(
|
|
50923
|
+
const withinIdent = relativeParts.slice(1).join(sep21);
|
|
50924
|
+
const homeRelative = relativeParts.join(sep21);
|
|
50637
50925
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
50638
50926
|
skippedByRule.push({
|
|
50639
50927
|
ident: identEntry.name,
|
|
@@ -50706,7 +50994,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
50706
50994
|
for (const copy of copies) {
|
|
50707
50995
|
let isStub = false;
|
|
50708
50996
|
try {
|
|
50709
|
-
isStub = isPointerSkillMd(
|
|
50997
|
+
isStub = isPointerSkillMd(readFileSync33(copy.fullPath, "utf8"));
|
|
50710
50998
|
} catch {
|
|
50711
50999
|
isStub = false;
|
|
50712
51000
|
}
|
|
@@ -50756,8 +51044,8 @@ function skillSha256(skill) {
|
|
|
50756
51044
|
`)).digest("hex");
|
|
50757
51045
|
}
|
|
50758
51046
|
function writeStationHydration(options) {
|
|
50759
|
-
const repoRoot =
|
|
50760
|
-
const cacheRoot =
|
|
51047
|
+
const repoRoot = resolve31(options.repoRoot ?? process.cwd());
|
|
51048
|
+
const cacheRoot = resolve31(options.cacheRoot ?? resolveCorpusRoot());
|
|
50761
51049
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
50762
51050
|
const resultSkills = plan.winners.map((skill) => ({
|
|
50763
51051
|
ident: skill.ident,
|
|
@@ -50790,7 +51078,7 @@ function writeStationHydration(options) {
|
|
|
50790
51078
|
const toWrite = [];
|
|
50791
51079
|
for (const skill of plan.winners) {
|
|
50792
51080
|
for (const file2 of skill.files) {
|
|
50793
|
-
const destination =
|
|
51081
|
+
const destination = join48(cacheRoot, skill.ident, file2.withinIdent);
|
|
50794
51082
|
const digest4 = sha256File(file2.winner.fullPath);
|
|
50795
51083
|
let existingDigest = null;
|
|
50796
51084
|
try {
|
|
@@ -50811,7 +51099,7 @@ function writeStationHydration(options) {
|
|
|
50811
51099
|
}
|
|
50812
51100
|
let written = 0;
|
|
50813
51101
|
for (const entry of toWrite) {
|
|
50814
|
-
mkdirSync19(
|
|
51102
|
+
mkdirSync19(dirname34(entry.destination), { recursive: true });
|
|
50815
51103
|
copyFileSync2(entry.fullPath, entry.destination);
|
|
50816
51104
|
written += 1;
|
|
50817
51105
|
}
|
|
@@ -50832,8 +51120,8 @@ function writeStationHydration(options) {
|
|
|
50832
51120
|
},
|
|
50833
51121
|
skills: resultSkills
|
|
50834
51122
|
};
|
|
50835
|
-
const hydrationManifestPath =
|
|
50836
|
-
mkdirSync19(
|
|
51123
|
+
const hydrationManifestPath = join48(dirname34(cacheRoot), `hydration-${options.stationId}.json`);
|
|
51124
|
+
mkdirSync19(dirname34(hydrationManifestPath), { recursive: true });
|
|
50837
51125
|
writeFileSync16(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
50838
51126
|
`);
|
|
50839
51127
|
return {
|
|
@@ -50844,23 +51132,23 @@ function writeStationHydration(options) {
|
|
|
50844
51132
|
};
|
|
50845
51133
|
}
|
|
50846
51134
|
// src/lib/private-publication-recovery.ts
|
|
50847
|
-
import { constants as constants21, closeSync as closeSync20, fsyncSync as fsyncSync8, fstatSync as fstatSync20, lstatSync as
|
|
50848
|
-
import { dirname as
|
|
51135
|
+
import { constants as constants21, closeSync as closeSync20, fsyncSync as fsyncSync8, fstatSync as fstatSync20, lstatSync as lstatSync34, mkdirSync as mkdirSync20, openSync as openSync20, readSync as readSync15, realpathSync as realpathSync16, renameSync as renameSync8, unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "fs";
|
|
51136
|
+
import { dirname as dirname35, isAbsolute as isAbsolute32, join as join49, resolve as resolve32 } from "path";
|
|
50849
51137
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
50850
51138
|
var fail3 = () => {
|
|
50851
51139
|
throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
|
|
50852
51140
|
};
|
|
50853
51141
|
function safeDirectory(directory3) {
|
|
50854
|
-
if (!isAbsolute32(directory3) || directory3 !==
|
|
51142
|
+
if (!isAbsolute32(directory3) || directory3 !== resolve32(directory3) || realpathSync16(directory3) !== directory3)
|
|
50855
51143
|
return fail3();
|
|
50856
|
-
for (let path = directory3;; path =
|
|
50857
|
-
const stat2 =
|
|
51144
|
+
for (let path = directory3;; path = dirname35(path)) {
|
|
51145
|
+
const stat2 = lstatSync34(path);
|
|
50858
51146
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
50859
51147
|
return fail3();
|
|
50860
|
-
if (path ===
|
|
51148
|
+
if (path === dirname35(path))
|
|
50861
51149
|
break;
|
|
50862
51150
|
}
|
|
50863
|
-
const own2 =
|
|
51151
|
+
const own2 = lstatSync34(directory3);
|
|
50864
51152
|
if ((own2.mode & 63) !== 0 || process.getuid && own2.uid !== process.getuid())
|
|
50865
51153
|
return fail3();
|
|
50866
51154
|
return { dev: own2.dev, ino: own2.ino };
|
|
@@ -50871,7 +51159,7 @@ function unchangedDirectory(directory3, identity5) {
|
|
|
50871
51159
|
return fail3();
|
|
50872
51160
|
}
|
|
50873
51161
|
function readOwned(directory3, name, max) {
|
|
50874
|
-
const identity5 = safeDirectory(directory3), file2 =
|
|
51162
|
+
const identity5 = safeDirectory(directory3), file2 = join49(directory3, name);
|
|
50875
51163
|
const fd = openSync20(file2, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
50876
51164
|
try {
|
|
50877
51165
|
const stat2 = fstatSync20(fd);
|
|
@@ -50895,7 +51183,7 @@ function readOwned(directory3, name, max) {
|
|
|
50895
51183
|
}
|
|
50896
51184
|
}
|
|
50897
51185
|
function writeOwned(directory3, name, bytes) {
|
|
50898
|
-
const fd = openSync20(
|
|
51186
|
+
const fd = openSync20(join49(directory3, name), constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
50899
51187
|
try {
|
|
50900
51188
|
writeFileSync17(fd, bytes);
|
|
50901
51189
|
fsyncSync8(fd);
|
|
@@ -50908,7 +51196,7 @@ function save(directory3, value) {
|
|
|
50908
51196
|
writeOwned(directory3, temporary, JSON.stringify(value) + `
|
|
50909
51197
|
`);
|
|
50910
51198
|
unchangedDirectory(directory3, identity5);
|
|
50911
|
-
renameSync8(
|
|
51199
|
+
renameSync8(join49(directory3, temporary), join49(directory3, "receipt.json"));
|
|
50912
51200
|
const fd = openSync20(directory3, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
50913
51201
|
try {
|
|
50914
51202
|
fsyncSync8(fd);
|
|
@@ -50921,7 +51209,7 @@ function bind(client2, receipt) {
|
|
|
50921
51209
|
throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
|
|
50922
51210
|
}
|
|
50923
51211
|
async function locked(directory3, action) {
|
|
50924
|
-
const identity5 = safeDirectory(directory3), file2 =
|
|
51212
|
+
const identity5 = safeDirectory(directory3), file2 = join49(directory3, "operation.lock");
|
|
50925
51213
|
let fd;
|
|
50926
51214
|
try {
|
|
50927
51215
|
fd = openSync20(file2, constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
@@ -50937,7 +51225,7 @@ async function locked(directory3, action) {
|
|
|
50937
51225
|
} finally {
|
|
50938
51226
|
closeSync20(fd);
|
|
50939
51227
|
unchangedDirectory(directory3, identity5);
|
|
50940
|
-
const now =
|
|
51228
|
+
const now = lstatSync34(file2);
|
|
50941
51229
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
50942
51230
|
fail3();
|
|
50943
51231
|
unlinkSync6(file2);
|
|
@@ -50989,11 +51277,11 @@ async function preparePrivatePublication(client2, sourceDirectory, recoveryDirec
|
|
|
50989
51277
|
phase: "prepared",
|
|
50990
51278
|
intent: null
|
|
50991
51279
|
};
|
|
50992
|
-
if (!isAbsolute32(recoveryDirectory) ||
|
|
51280
|
+
if (!isAbsolute32(recoveryDirectory) || resolve32(recoveryDirectory) !== recoveryDirectory || realpathSync16(dirname35(recoveryDirectory)) !== dirname35(recoveryDirectory))
|
|
50993
51281
|
return fail3();
|
|
50994
51282
|
mkdirSync20(recoveryDirectory, { mode: 448 });
|
|
50995
51283
|
safeDirectory(recoveryDirectory);
|
|
50996
|
-
const parent = openSync20(
|
|
51284
|
+
const parent = openSync20(dirname35(recoveryDirectory), constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
50997
51285
|
try {
|
|
50998
51286
|
fsyncSync8(parent);
|
|
50999
51287
|
} finally {
|
|
@@ -51081,7 +51369,7 @@ async function inspectLocked(client2, directory3, cancel) {
|
|
|
51081
51369
|
}
|
|
51082
51370
|
// src/lib/skill-context.ts
|
|
51083
51371
|
import { createHash as createHash38 } from "crypto";
|
|
51084
|
-
import { resolve as
|
|
51372
|
+
import { resolve as resolve33 } from "path";
|
|
51085
51373
|
function words(value) {
|
|
51086
51374
|
return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
|
|
51087
51375
|
}
|
|
@@ -51120,7 +51408,7 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
51120
51408
|
const promptWords = words(prompt);
|
|
51121
51409
|
const promptNames = new Set(prompt.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []);
|
|
51122
51410
|
const loaded = new Set(resolved.session?.loaded ?? []);
|
|
51123
|
-
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd,
|
|
51411
|
+
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve33(input2.cwd)] : []];
|
|
51124
51412
|
const candidates = profile.selections.map((selection) => {
|
|
51125
51413
|
const key2 = selectionKey(selection);
|
|
51126
51414
|
if (input2.restore && loaded.has(key2))
|
|
@@ -51315,8 +51603,8 @@ function applySessionReconciliation(snapshot2, target, planned, record12, verifi
|
|
|
51315
51603
|
}
|
|
51316
51604
|
// src/lib/claude-prospective-review.ts
|
|
51317
51605
|
import { createHash as createHash40 } from "crypto";
|
|
51318
|
-
import { lstatSync as
|
|
51319
|
-
import { basename as basename18, dirname as
|
|
51606
|
+
import { lstatSync as lstatSync35, readFileSync as readFileSync34, readdirSync as readdirSync19, realpathSync as realpathSync17 } from "fs";
|
|
51607
|
+
import { basename as basename18, dirname as dirname36, isAbsolute as isAbsolute33, join as join50, relative as relative16, resolve as resolve34, sep as sep22 } from "path";
|
|
51320
51608
|
var MAX_CANDIDATE_FILES = 16384;
|
|
51321
51609
|
var MAX_CANDIDATE_BYTES = 256 * 1024 * 1024;
|
|
51322
51610
|
var sha7 = (data) => createHash40("sha256").update(data).digest("hex");
|
|
@@ -51327,32 +51615,32 @@ var GENERATED_MARKETPLACE_TIME = Object.freeze({ $generated: "claude-utc-timesta
|
|
|
51327
51615
|
var GENERATED_FRESH_PLUGIN_INSTALLED_AT = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.fresh-row.installedAt" });
|
|
51328
51616
|
var GENERATED_PLUGIN_UPDATE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.selected-row.lastUpdated" });
|
|
51329
51617
|
function abs(path, name) {
|
|
51330
|
-
if (typeof path !== "string" || !path || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path) || !isAbsolute33(path) ||
|
|
51618
|
+
if (typeof path !== "string" || !path || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path) || !isAbsolute33(path) || resolve34(path) !== path)
|
|
51331
51619
|
refuse6(`${name} must be a normalized absolute path`);
|
|
51332
51620
|
}
|
|
51333
51621
|
function safeFile(path, name) {
|
|
51334
51622
|
abs(path, name);
|
|
51335
|
-
for (let at =
|
|
51336
|
-
const stat3 =
|
|
51623
|
+
for (let at = dirname36(path);; at = dirname36(at)) {
|
|
51624
|
+
const stat3 = lstatSync35(at, { throwIfNoEntry: false });
|
|
51337
51625
|
if (!stat3?.isDirectory() || stat3.isSymbolicLink())
|
|
51338
51626
|
refuse6(`${name} traverses a symlink or non-directory`);
|
|
51339
|
-
if (at ===
|
|
51627
|
+
if (at === dirname36(at))
|
|
51340
51628
|
break;
|
|
51341
51629
|
}
|
|
51342
|
-
const stat2 =
|
|
51630
|
+
const stat2 = lstatSync35(path, { throwIfNoEntry: false });
|
|
51343
51631
|
if (!stat2?.isFile() || stat2.isSymbolicLink() || stat2.size > MAX_CANDIDATE_BYTES)
|
|
51344
51632
|
refuse6(`${name} is not a bounded regular file`);
|
|
51345
|
-
return
|
|
51633
|
+
return readFileSync34(path);
|
|
51346
51634
|
}
|
|
51347
51635
|
function canonicalDir(path, name) {
|
|
51348
51636
|
abs(path, name);
|
|
51349
|
-
if (realpathSync17(path) !== path || !
|
|
51637
|
+
if (realpathSync17(path) !== path || !lstatSync35(path).isDirectory())
|
|
51350
51638
|
refuse6(`${name} is not a canonical directory`);
|
|
51351
|
-
for (let at = path;; at =
|
|
51352
|
-
const stat2 =
|
|
51639
|
+
for (let at = path;; at = dirname36(at)) {
|
|
51640
|
+
const stat2 = lstatSync35(at);
|
|
51353
51641
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
51354
51642
|
refuse6(`${name} traverses a symlink`);
|
|
51355
|
-
if (at ===
|
|
51643
|
+
if (at === dirname36(at))
|
|
51356
51644
|
break;
|
|
51357
51645
|
}
|
|
51358
51646
|
}
|
|
@@ -51361,17 +51649,17 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
51361
51649
|
const entries = [];
|
|
51362
51650
|
let bytes = 0;
|
|
51363
51651
|
const walk2 = (directory3) => {
|
|
51364
|
-
const beforeDirectory =
|
|
51652
|
+
const beforeDirectory = lstatSync35(directory3);
|
|
51365
51653
|
const names = readdirSync19(directory3).sort();
|
|
51366
51654
|
if (new Set(names).size !== names.length)
|
|
51367
51655
|
refuse6("candidate contains ambiguous directory entries");
|
|
51368
51656
|
for (const name of names) {
|
|
51369
51657
|
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
|
|
51370
51658
|
refuse6("candidate contains an unsupported path member");
|
|
51371
|
-
const path =
|
|
51659
|
+
const path = join50(directory3, name), rel = relative16(root2, path).split(sep22).join("/");
|
|
51372
51660
|
if (entries.length >= MAX_CANDIDATE_FILES)
|
|
51373
51661
|
refuse6("candidate closure exceeds its member limit");
|
|
51374
|
-
const stat2 =
|
|
51662
|
+
const stat2 = lstatSync35(path);
|
|
51375
51663
|
if (stat2.isSymbolicLink())
|
|
51376
51664
|
refuse6("candidate closure contains a symlink");
|
|
51377
51665
|
if (stat2.isDirectory()) {
|
|
@@ -51381,14 +51669,14 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
51381
51669
|
bytes += stat2.size;
|
|
51382
51670
|
if (bytes > MAX_CANDIDATE_BYTES)
|
|
51383
51671
|
refuse6("candidate closure exceeds its byte limit");
|
|
51384
|
-
const content = safeFile(path, "candidate member"), after =
|
|
51672
|
+
const content = safeFile(path, "candidate member"), after = lstatSync35(path);
|
|
51385
51673
|
if (!after.isFile() || after.dev !== stat2.dev || after.ino !== stat2.ino || after.size !== stat2.size || after.mtimeMs !== stat2.mtimeMs || after.ctimeMs !== stat2.ctimeMs)
|
|
51386
51674
|
refuse6("candidate member changed while being read");
|
|
51387
51675
|
entries.push([rel, "file", sha7(content)]);
|
|
51388
51676
|
} else
|
|
51389
51677
|
refuse6("candidate closure contains a special file");
|
|
51390
51678
|
}
|
|
51391
|
-
const afterDirectory =
|
|
51679
|
+
const afterDirectory = lstatSync35(directory3);
|
|
51392
51680
|
if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
|
|
51393
51681
|
refuse6("candidate directory changed while being read");
|
|
51394
51682
|
};
|
|
@@ -51396,6 +51684,21 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
51396
51684
|
entries.sort((a, b) => a[0].localeCompare(b[0]));
|
|
51397
51685
|
return sha7(JSON.stringify(entries));
|
|
51398
51686
|
}
|
|
51687
|
+
var EXEMPT_PROJECT_SETTINGS = new Map([
|
|
51688
|
+
["permissions", "object"],
|
|
51689
|
+
["outputStyle", "string"],
|
|
51690
|
+
["model", "string"],
|
|
51691
|
+
["theme", "string"],
|
|
51692
|
+
["effortLevel", "string"],
|
|
51693
|
+
["verbose", "boolean"],
|
|
51694
|
+
["spinnerTipsEnabled", "boolean"]
|
|
51695
|
+
]);
|
|
51696
|
+
var EXEMPT_PROJECT_PERMISSIONS = new Map([
|
|
51697
|
+
["allow", "strings"],
|
|
51698
|
+
["deny", "strings"],
|
|
51699
|
+
["ask", "strings"],
|
|
51700
|
+
["defaultMode", "string"]
|
|
51701
|
+
]);
|
|
51399
51702
|
export {
|
|
51400
51703
|
writeStationSnapshot,
|
|
51401
51704
|
writeStationHydration,
|