@hasna/skills 0.10.55 → 0.10.57
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 +4 -0
- package/bin/index.js +787 -107
- package/bin/mcp.js +94 -1
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +179 -1
- package/dist/lib/agent-integration.d.ts +2 -0
- package/dist/lib/claude-prospective-review.d.ts +140 -0
- package/dist/sdk/index.js +94 -1
- package/docs/plugin-admission.md +59 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36888,7 +36888,7 @@ var package_default;
|
|
|
36888
36888
|
var init_package = __esm(() => {
|
|
36889
36889
|
package_default = {
|
|
36890
36890
|
name: "@hasna/skills",
|
|
36891
|
-
version: "0.10.
|
|
36891
|
+
version: "0.10.57",
|
|
36892
36892
|
description: "Skills library for AI coding agents",
|
|
36893
36893
|
type: "module",
|
|
36894
36894
|
bin: {
|
|
@@ -59361,6 +59361,97 @@ function read2(path, changes) {
|
|
|
59361
59361
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
59362
59362
|
return readFileSync21(path, "utf8");
|
|
59363
59363
|
}
|
|
59364
|
+
function assertReviewedClaudeCacheClosures(home, config, review) {
|
|
59365
|
+
const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
|
|
59366
|
+
if (enabledIds.length === 0)
|
|
59367
|
+
return;
|
|
59368
|
+
const registryPath = join37(home, ".claude/plugins/installed_plugins.json");
|
|
59369
|
+
const cacheRoot = join37(home, ".claude/plugins/cache");
|
|
59370
|
+
const registryText = read2(registryPath);
|
|
59371
|
+
if (registryText === null)
|
|
59372
|
+
return;
|
|
59373
|
+
const registry = parseConfig(registryText, registryPath);
|
|
59374
|
+
if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
|
|
59375
|
+
throw new Error("Reviewed Claude installed plugin registrations are invalid");
|
|
59376
|
+
const cachePath = (path) => typeof path === "string" && isAbsolute22(path) && resolve21(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep8));
|
|
59377
|
+
const activeRoots = new Set;
|
|
59378
|
+
for (const id of enabledIds) {
|
|
59379
|
+
const rows = registry.plugins[id];
|
|
59380
|
+
if (rows === undefined)
|
|
59381
|
+
continue;
|
|
59382
|
+
if (!Array.isArray(rows))
|
|
59383
|
+
throw new Error("Reviewed Claude enabled plugin registrations must be an array");
|
|
59384
|
+
for (const row of rows) {
|
|
59385
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
|
|
59386
|
+
throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
|
|
59387
|
+
}
|
|
59388
|
+
if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep8))) {
|
|
59389
|
+
if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename11(row.installPath) !== row.version)) {
|
|
59390
|
+
throw new Error("Reviewed Claude discovery has a malformed active cache registration");
|
|
59391
|
+
}
|
|
59392
|
+
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute22(row.projectPath) && resolve21(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
|
|
59393
|
+
throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
|
|
59394
|
+
activeRoots.add(row.installPath);
|
|
59395
|
+
}
|
|
59396
|
+
}
|
|
59397
|
+
}
|
|
59398
|
+
if (activeRoots.size === 0)
|
|
59399
|
+
return;
|
|
59400
|
+
const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
59401
|
+
if (!registrySource)
|
|
59402
|
+
throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
|
|
59403
|
+
const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
|
|
59404
|
+
const requireSource = (path, description, manifest = false) => {
|
|
59405
|
+
if (!reviewedFile(path, manifest))
|
|
59406
|
+
throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
|
|
59407
|
+
};
|
|
59408
|
+
for (const root2 of activeRoots) {
|
|
59409
|
+
safe4(root2);
|
|
59410
|
+
const rootStat = lstatSync25(root2, { throwIfNoEntry: false });
|
|
59411
|
+
if (!rootStat?.isDirectory() || realpathSync11(root2) !== root2)
|
|
59412
|
+
throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
|
|
59413
|
+
const manifestPath = join37(root2, ".claude-plugin/plugin.json");
|
|
59414
|
+
const manifestStat = lstatSync25(manifestPath, { throwIfNoEntry: false });
|
|
59415
|
+
let manifest;
|
|
59416
|
+
if (manifestStat) {
|
|
59417
|
+
if (!manifestStat.isFile())
|
|
59418
|
+
throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
|
|
59419
|
+
requireSource(manifestPath, "manifest", true);
|
|
59420
|
+
const raw = read2(manifestPath);
|
|
59421
|
+
if (raw === null)
|
|
59422
|
+
throw new Error("Reviewed Claude active cache plugin manifest is missing");
|
|
59423
|
+
manifest = JSON.parse(raw);
|
|
59424
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
59425
|
+
throw new Error("Reviewed Claude active cache plugin manifest is invalid");
|
|
59426
|
+
} 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)) {
|
|
59427
|
+
throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
|
|
59428
|
+
}
|
|
59429
|
+
const roots = new Set([join37(root2, "skills")]);
|
|
59430
|
+
if (lstatSync25(join37(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
|
|
59431
|
+
roots.add(root2);
|
|
59432
|
+
const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
59433
|
+
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
59434
|
+
throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
|
|
59435
|
+
for (const value of declared) {
|
|
59436
|
+
const path = resolve21(root2, value);
|
|
59437
|
+
if (path !== root2 && !path.startsWith(root2 + sep8))
|
|
59438
|
+
throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
|
|
59439
|
+
roots.add(path.endsWith(`${sep8}SKILL.md`) ? dirname20(path) : path);
|
|
59440
|
+
}
|
|
59441
|
+
for (const skillRoot of roots)
|
|
59442
|
+
if (!review.roots.includes(skillRoot))
|
|
59443
|
+
throw new Error("Reviewed Claude active cache plugin skill root is not covered");
|
|
59444
|
+
const hooksPath = join37(root2, "hooks/hooks.json");
|
|
59445
|
+
const hooksStat = lstatSync25(hooksPath, { throwIfNoEntry: false });
|
|
59446
|
+
if (hooksStat) {
|
|
59447
|
+
if (!hooksStat.isFile())
|
|
59448
|
+
throw new Error("Reviewed Claude plugin hook manifest is unsupported");
|
|
59449
|
+
requireSource(hooksPath, "hook manifest");
|
|
59450
|
+
}
|
|
59451
|
+
if (typeof manifest?.hooks === "string")
|
|
59452
|
+
requireSource(resolve21(root2, manifest.hooks), "manifest-declared hook source");
|
|
59453
|
+
}
|
|
59454
|
+
}
|
|
59364
59455
|
function projectNativeDiscoveryFields(text3, format, fields2, path) {
|
|
59365
59456
|
const object6 = format === "yaml" ? parseHermesConfig(text3) : parseConfig(text3, path, format === "toml");
|
|
59366
59457
|
if (!object6 || typeof object6 !== "object" || Array.isArray(object6))
|
|
@@ -59937,6 +60028,8 @@ function resolveAgentDiscovery(options) {
|
|
|
59937
60028
|
throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
|
|
59938
60029
|
return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
|
|
59939
60030
|
});
|
|
60031
|
+
if (agent === "claude")
|
|
60032
|
+
assertReviewedClaudeCacheClosures(home, config, review);
|
|
59940
60033
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
59941
60034
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual11(candidate2, source)) === index);
|
|
59942
60035
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
@@ -116524,14 +116617,584 @@ var init_hook_diagnostics = __esm(() => {
|
|
|
116524
116617
|
]);
|
|
116525
116618
|
});
|
|
116526
116619
|
|
|
116620
|
+
// src/lib/claude-prospective-review.ts
|
|
116621
|
+
import { createHash as createHash41 } from "crypto";
|
|
116622
|
+
import { lstatSync as lstatSync37, readFileSync as readFileSync45, readdirSync as readdirSync24, realpathSync as realpathSync21 } from "fs";
|
|
116623
|
+
import { basename as basename18, dirname as dirname38, isAbsolute as isAbsolute34, join as join68, relative as relative12, resolve as resolve33, sep as sep15 } from "path";
|
|
116624
|
+
import { isDeepStrictEqual as isDeepStrictEqual18 } from "util";
|
|
116625
|
+
function refuse6(reason2) {
|
|
116626
|
+
throw new Error(`CLAUDE_CANDIDATE_REVIEW_REFUSED: ${reason2}`);
|
|
116627
|
+
}
|
|
116628
|
+
function preserveOrGenerateTimestamp(previous, field3) {
|
|
116629
|
+
return { $allowed: [previous, { $generated: "claude-utc-timestamp", field: field3 }] };
|
|
116630
|
+
}
|
|
116631
|
+
function object11(value) {
|
|
116632
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
116633
|
+
}
|
|
116634
|
+
function hasOwn2(value, key) {
|
|
116635
|
+
return object11(value) && Object.hasOwn(value, key);
|
|
116636
|
+
}
|
|
116637
|
+
function assertJsonValue(value, name, depth = 0) {
|
|
116638
|
+
if (depth > 32)
|
|
116639
|
+
refuse6(`${name} exceeds the JSON nesting bound`);
|
|
116640
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
116641
|
+
return;
|
|
116642
|
+
if (typeof value === "number") {
|
|
116643
|
+
if (!Number.isFinite(value))
|
|
116644
|
+
refuse6(`${name} contains a non-finite number`);
|
|
116645
|
+
return;
|
|
116646
|
+
}
|
|
116647
|
+
if (Array.isArray(value)) {
|
|
116648
|
+
for (const item of value)
|
|
116649
|
+
assertJsonValue(item, name, depth + 1);
|
|
116650
|
+
return;
|
|
116651
|
+
}
|
|
116652
|
+
if (object11(value)) {
|
|
116653
|
+
for (const item of Object.values(value))
|
|
116654
|
+
assertJsonValue(item, name, depth + 1);
|
|
116655
|
+
return;
|
|
116656
|
+
}
|
|
116657
|
+
refuse6(`${name} contains a non-JSON value`);
|
|
116658
|
+
}
|
|
116659
|
+
function canonicalJson3(value) {
|
|
116660
|
+
if (Array.isArray(value))
|
|
116661
|
+
return `[${value.map(canonicalJson3).join(",")}]`;
|
|
116662
|
+
if (object11(value))
|
|
116663
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson3(value[key])}`).join(",")}}`;
|
|
116664
|
+
return JSON.stringify(value);
|
|
116665
|
+
}
|
|
116666
|
+
function keys3(value, expected, name) {
|
|
116667
|
+
if (!object11(value) || Object.keys(value).sort().join("\x00") !== [...expected].sort().join("\x00"))
|
|
116668
|
+
refuse6(`${name} has an unsupported shape`);
|
|
116669
|
+
}
|
|
116670
|
+
function abs(path, name) {
|
|
116671
|
+
if (typeof path !== "string" || !path || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path) || !isAbsolute34(path) || resolve33(path) !== path)
|
|
116672
|
+
refuse6(`${name} must be a normalized absolute path`);
|
|
116673
|
+
}
|
|
116674
|
+
function validUtcTimestamp(value) {
|
|
116675
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value;
|
|
116676
|
+
}
|
|
116677
|
+
function safeFile(path, name) {
|
|
116678
|
+
abs(path, name);
|
|
116679
|
+
for (let at2 = dirname38(path);; at2 = dirname38(at2)) {
|
|
116680
|
+
const stat4 = lstatSync37(at2, { throwIfNoEntry: false });
|
|
116681
|
+
if (!stat4?.isDirectory() || stat4.isSymbolicLink())
|
|
116682
|
+
refuse6(`${name} traverses a symlink or non-directory`);
|
|
116683
|
+
if (at2 === dirname38(at2))
|
|
116684
|
+
break;
|
|
116685
|
+
}
|
|
116686
|
+
const stat3 = lstatSync37(path, { throwIfNoEntry: false });
|
|
116687
|
+
if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > MAX_CANDIDATE_BYTES)
|
|
116688
|
+
refuse6(`${name} is not a bounded regular file`);
|
|
116689
|
+
return readFileSync45(path);
|
|
116690
|
+
}
|
|
116691
|
+
function canonicalDir(path, name) {
|
|
116692
|
+
abs(path, name);
|
|
116693
|
+
if (realpathSync21(path) !== path || !lstatSync37(path).isDirectory())
|
|
116694
|
+
refuse6(`${name} is not a canonical directory`);
|
|
116695
|
+
for (let at2 = path;; at2 = dirname38(at2)) {
|
|
116696
|
+
const stat3 = lstatSync37(at2);
|
|
116697
|
+
if (!stat3.isDirectory() || stat3.isSymbolicLink())
|
|
116698
|
+
refuse6(`${name} traverses a symlink`);
|
|
116699
|
+
if (at2 === dirname38(at2))
|
|
116700
|
+
break;
|
|
116701
|
+
}
|
|
116702
|
+
}
|
|
116703
|
+
function captureClaudeProspectiveCandidateClosure(root2) {
|
|
116704
|
+
canonicalDir(root2, "candidate root");
|
|
116705
|
+
const entries = [];
|
|
116706
|
+
let bytes = 0;
|
|
116707
|
+
const walk2 = (directory2) => {
|
|
116708
|
+
const beforeDirectory = lstatSync37(directory2);
|
|
116709
|
+
const names = readdirSync24(directory2).sort();
|
|
116710
|
+
if (new Set(names).size !== names.length)
|
|
116711
|
+
refuse6("candidate contains ambiguous directory entries");
|
|
116712
|
+
for (const name of names) {
|
|
116713
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
|
|
116714
|
+
refuse6("candidate contains an unsupported path member");
|
|
116715
|
+
const path = join68(directory2, name), rel = relative12(root2, path).split(sep15).join("/");
|
|
116716
|
+
if (entries.length >= MAX_CANDIDATE_FILES)
|
|
116717
|
+
refuse6("candidate closure exceeds its member limit");
|
|
116718
|
+
const stat3 = lstatSync37(path);
|
|
116719
|
+
if (stat3.isSymbolicLink())
|
|
116720
|
+
refuse6("candidate closure contains a symlink");
|
|
116721
|
+
if (stat3.isDirectory()) {
|
|
116722
|
+
entries.push([rel, "directory", null]);
|
|
116723
|
+
walk2(path);
|
|
116724
|
+
} else if (stat3.isFile()) {
|
|
116725
|
+
bytes += stat3.size;
|
|
116726
|
+
if (bytes > MAX_CANDIDATE_BYTES)
|
|
116727
|
+
refuse6("candidate closure exceeds its byte limit");
|
|
116728
|
+
const content = safeFile(path, "candidate member"), after2 = lstatSync37(path);
|
|
116729
|
+
if (!after2.isFile() || after2.dev !== stat3.dev || after2.ino !== stat3.ino || after2.size !== stat3.size || after2.mtimeMs !== stat3.mtimeMs || after2.ctimeMs !== stat3.ctimeMs)
|
|
116730
|
+
refuse6("candidate member changed while being read");
|
|
116731
|
+
entries.push([rel, "file", sha7(content)]);
|
|
116732
|
+
} else
|
|
116733
|
+
refuse6("candidate closure contains a special file");
|
|
116734
|
+
}
|
|
116735
|
+
const afterDirectory = lstatSync37(directory2);
|
|
116736
|
+
if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
|
|
116737
|
+
refuse6("candidate directory changed while being read");
|
|
116738
|
+
};
|
|
116739
|
+
walk2(root2);
|
|
116740
|
+
entries.sort((a, b) => a[0].localeCompare(b[0]));
|
|
116741
|
+
return sha7(JSON.stringify(entries));
|
|
116742
|
+
}
|
|
116743
|
+
function readWitness(path, expected, name) {
|
|
116744
|
+
abs(path, `${name} path`);
|
|
116745
|
+
const stat3 = lstatSync37(path, { throwIfNoEntry: false });
|
|
116746
|
+
if (expected === null) {
|
|
116747
|
+
if (stat3)
|
|
116748
|
+
refuse6(`${name} was absent at review but is now present`);
|
|
116749
|
+
return;
|
|
116750
|
+
}
|
|
116751
|
+
if (!SHA256.test(expected))
|
|
116752
|
+
refuse6(`${name} has an invalid SHA-256`);
|
|
116753
|
+
if (!stat3?.isFile() || stat3.isSymbolicLink() || sha7(safeFile(path, name)) !== expected)
|
|
116754
|
+
refuse6(`${name} preimage changed`);
|
|
116755
|
+
}
|
|
116756
|
+
function jsonFile(path, name) {
|
|
116757
|
+
const stat3 = lstatSync37(path, { throwIfNoEntry: false });
|
|
116758
|
+
if (!stat3)
|
|
116759
|
+
return null;
|
|
116760
|
+
let value;
|
|
116761
|
+
try {
|
|
116762
|
+
value = JSON.parse(safeFile(path, name).toString("utf8"));
|
|
116763
|
+
} catch {
|
|
116764
|
+
refuse6(`${name} is not valid JSON`);
|
|
116765
|
+
}
|
|
116766
|
+
if (!object11(value))
|
|
116767
|
+
refuse6(`${name} must be a JSON object`);
|
|
116768
|
+
return value;
|
|
116769
|
+
}
|
|
116770
|
+
function assertNoUnprojectedSkillPayload(root2, catalogPath, marketplace, plugin, manifest) {
|
|
116771
|
+
if (manifest.skills !== undefined || manifest.commands !== undefined)
|
|
116772
|
+
refuse6("candidate plugin declares native skill or command payloads; project those through the Skills plugin admission flow first");
|
|
116773
|
+
let catalog;
|
|
116774
|
+
try {
|
|
116775
|
+
catalog = JSON.parse(safeFile(catalogPath, "candidate catalog").toString("utf8"));
|
|
116776
|
+
} catch {
|
|
116777
|
+
refuse6("candidate catalog is not valid JSON");
|
|
116778
|
+
}
|
|
116779
|
+
if (!object11(catalog) || catalog.name !== marketplace || !Array.isArray(catalog.plugins))
|
|
116780
|
+
refuse6("candidate catalog identity is invalid");
|
|
116781
|
+
const entries = catalog.plugins.filter((entry) => object11(entry) && entry.name === plugin);
|
|
116782
|
+
if (entries.length !== 1 || !object11(entries[0]))
|
|
116783
|
+
refuse6("candidate catalog plugin entry is missing or ambiguous");
|
|
116784
|
+
if (entries[0].skills !== undefined || entries[0].commands !== undefined)
|
|
116785
|
+
refuse6("candidate marketplace entry declares native skill or command payloads");
|
|
116786
|
+
const walk2 = (directory2) => {
|
|
116787
|
+
for (const name of readdirSync24(directory2).sort()) {
|
|
116788
|
+
const path = join68(directory2, name), rel = relative12(root2, path).split(sep15).join("/");
|
|
116789
|
+
const stat3 = lstatSync37(path);
|
|
116790
|
+
if (stat3.isDirectory())
|
|
116791
|
+
walk2(path);
|
|
116792
|
+
else if (stat3.isFile()) {
|
|
116793
|
+
const parts = rel.toLowerCase().split("/");
|
|
116794
|
+
if (parts.includes("skills") || parts.includes("commands") || name.toLowerCase() === "skill.md")
|
|
116795
|
+
refuse6("candidate closure contains native skill or command payload paths");
|
|
116796
|
+
if (parts.includes("agents") && /\.md$/i.test(name)) {
|
|
116797
|
+
let text4;
|
|
116798
|
+
try {
|
|
116799
|
+
text4 = new TextDecoder("utf-8", { fatal: true }).decode(safeFile(path, "candidate agent file"));
|
|
116800
|
+
} catch {
|
|
116801
|
+
refuse6("candidate agent file is not strict UTF-8");
|
|
116802
|
+
}
|
|
116803
|
+
if (/^skills\s*:/m.test(text4))
|
|
116804
|
+
refuse6("candidate agent preloads native skills; project it through the Skills plugin admission flow first");
|
|
116805
|
+
}
|
|
116806
|
+
}
|
|
116807
|
+
}
|
|
116808
|
+
};
|
|
116809
|
+
walk2(root2);
|
|
116810
|
+
}
|
|
116811
|
+
function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
116812
|
+
if (!SHA256.test(requestFileSha256))
|
|
116813
|
+
refuse6("request file SHA-256 is invalid");
|
|
116814
|
+
keys3(request2, ["schema", "scope", "native", "skills", "candidate", "preimages", "delta", "review"], "request");
|
|
116815
|
+
if (request2.schema !== CLAUDE_PROSPECTIVE_REVIEW_SCHEMA)
|
|
116816
|
+
refuse6("unsupported schema");
|
|
116817
|
+
keys3(request2.scope, ["kind", "projectPath"], "scope");
|
|
116818
|
+
if (request2.scope.kind !== "user" && request2.scope.kind !== "project")
|
|
116819
|
+
refuse6("scope must be user or project");
|
|
116820
|
+
abs(request2.scope.projectPath, "project path");
|
|
116821
|
+
canonicalDir(request2.scope.projectPath, "project path");
|
|
116822
|
+
keys3(request2.native, ["home", "executable", "configRoot", "pluginRoot"], "native identity");
|
|
116823
|
+
canonicalDir(request2.native.home, "Claude home");
|
|
116824
|
+
abs(request2.native.configRoot, "Claude config root");
|
|
116825
|
+
keys3(request2.native.executable, ["path", "target", "version", "sha256"], "Claude executable identity");
|
|
116826
|
+
abs(request2.native.executable.path, "Claude executable path");
|
|
116827
|
+
abs(request2.native.executable.target, "Claude executable target");
|
|
116828
|
+
if (typeof request2.native.executable.version !== "string" || !request2.native.executable.version || request2.native.executable.version.length > 256 || /[\x00-\x1f\x7f]/.test(request2.native.executable.version) || !SHA256.test(request2.native.executable.sha256) || realpathSync21(request2.native.executable.path) !== request2.native.executable.target || hashExecutableFile(request2.native.executable.target) !== request2.native.executable.sha256)
|
|
116829
|
+
refuse6("Claude executable identity changed");
|
|
116830
|
+
keys3(request2.skills, ["dataDir", "policySha256", "discoverySha256", "discoveryInputs"], "Skills preimage");
|
|
116831
|
+
abs(request2.skills.dataDir, "Skills data directory");
|
|
116832
|
+
if (!SHA256.test(request2.skills.policySha256) || !SHA256.test(request2.skills.discoverySha256))
|
|
116833
|
+
refuse6("invalid Skills policy or discovery SHA-256");
|
|
116834
|
+
const snapshot2 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
|
|
116835
|
+
if (!snapshot2 || sha7(snapshot2.text) !== request2.skills.policySha256)
|
|
116836
|
+
refuse6("Skills policy preimage changed or is absent");
|
|
116837
|
+
const aliases = snapshot2.value.bridge?.rootAliases ?? [];
|
|
116838
|
+
if (!Array.isArray(aliases))
|
|
116839
|
+
refuse6("Skills policy root aliases are invalid");
|
|
116840
|
+
try {
|
|
116841
|
+
recheckRootAliases(aliases);
|
|
116842
|
+
} catch {
|
|
116843
|
+
refuse6("Skills policy root alias changed or is invalid");
|
|
116844
|
+
}
|
|
116845
|
+
const canonical8 = (path) => canonicalAgentPath(path, aliases);
|
|
116846
|
+
const configRoot = canonical8(join68(request2.native.home, ".claude"));
|
|
116847
|
+
canonicalDir(configRoot, "Claude config root");
|
|
116848
|
+
if (request2.native.configRoot !== configRoot)
|
|
116849
|
+
refuse6("Claude config root differs from the current resolver and reviewed root alias");
|
|
116850
|
+
const pluginRoot = join68(configRoot, "plugins");
|
|
116851
|
+
if (process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR)
|
|
116852
|
+
refuse6("CLAUDE_CODE_PLUGIN_CACHE_DIR overrides are unsupported by this prospective schema; bind and review the actual cache root first");
|
|
116853
|
+
if (request2.native.pluginRoot !== pluginRoot)
|
|
116854
|
+
refuse6("Claude plugin root differs from the supported standard plugins directory; Cowork and custom cache roots require a separately bound schema");
|
|
116855
|
+
const pluginRootStat = lstatSync37(pluginRoot, { throwIfNoEntry: false });
|
|
116856
|
+
if (pluginRootStat && (!pluginRootStat.isDirectory() || pluginRootStat.isSymbolicLink()))
|
|
116857
|
+
refuse6("Claude plugin root is not a regular directory");
|
|
116858
|
+
keys3(request2.candidate, ["marketplace", "root", "catalogPath", "catalogSha256", "closureSha256", "plugins", ...hasOwn2(request2.candidate, "releaseManifest") ? ["releaseManifest"] : [], ...hasOwn2(request2.candidate, "functionHooksEnv") ? ["functionHooksEnv"] : []], "candidate");
|
|
116859
|
+
const candidate2 = request2.candidate;
|
|
116860
|
+
canonicalDir(candidate2.root, "candidate root");
|
|
116861
|
+
if (typeof candidate2.marketplace !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(candidate2.marketplace))
|
|
116862
|
+
refuse6("invalid candidate marketplace");
|
|
116863
|
+
if (!Array.isArray(candidate2.plugins) || candidate2.plugins.length < 1 || candidate2.plugins.length > 32)
|
|
116864
|
+
refuse6("candidate must select between one and 32 plugins from one marketplace");
|
|
116865
|
+
if (candidate2.functionHooksEnv !== undefined && candidate2.functionHooksEnv !== "1")
|
|
116866
|
+
refuse6("function-hook environment mutation only supports the explicit value 1");
|
|
116867
|
+
abs(candidate2.catalogPath, "catalog path");
|
|
116868
|
+
if (!candidate2.catalogPath.startsWith(candidate2.root + sep15))
|
|
116869
|
+
refuse6("catalog must be inside the frozen candidate root");
|
|
116870
|
+
if (candidate2.catalogPath !== join68(candidate2.root, ".claude-plugin", "marketplace.json"))
|
|
116871
|
+
refuse6("marketplace catalog must be at the frozen root's .claude-plugin/marketplace.json path");
|
|
116872
|
+
if (!SHA256.test(candidate2.catalogSha256) || !SHA256.test(candidate2.closureSha256))
|
|
116873
|
+
refuse6("candidate digests must be lowercase SHA-256");
|
|
116874
|
+
if (captureClaudeProspectiveCandidateClosure(candidate2.root) !== candidate2.closureSha256)
|
|
116875
|
+
refuse6("complete candidate closure changed");
|
|
116876
|
+
if (sha7(safeFile(candidate2.catalogPath, "candidate catalog")) !== candidate2.catalogSha256)
|
|
116877
|
+
refuse6("candidate catalog changed");
|
|
116878
|
+
let candidateCatalog;
|
|
116879
|
+
try {
|
|
116880
|
+
candidateCatalog = JSON.parse(safeFile(candidate2.catalogPath, "candidate catalog").toString("utf8"));
|
|
116881
|
+
} catch {
|
|
116882
|
+
refuse6("candidate catalog is not valid JSON");
|
|
116883
|
+
}
|
|
116884
|
+
if (!object11(candidateCatalog) || candidateCatalog.name !== candidate2.marketplace || !Array.isArray(candidateCatalog.plugins))
|
|
116885
|
+
refuse6("candidate catalog identity is invalid");
|
|
116886
|
+
const catalogMetadata = candidateCatalog.metadata;
|
|
116887
|
+
if (catalogMetadata !== undefined && !object11(catalogMetadata))
|
|
116888
|
+
refuse6("candidate catalog metadata has an unsupported shape");
|
|
116889
|
+
if (catalogMetadata?.pluginRoot !== undefined && catalogMetadata.pluginRoot !== "." && catalogMetadata.pluginRoot !== "./")
|
|
116890
|
+
refuse6("nontrivial marketplace metadata.pluginRoot is unsupported by this local-closure schema");
|
|
116891
|
+
for (const [name, dependencies] of [["catalog", candidateCatalog.dependencies], ["catalog metadata", catalogMetadata?.dependencies]]) {
|
|
116892
|
+
if (dependencies !== undefined && (!Array.isArray(dependencies) || dependencies.length !== 0))
|
|
116893
|
+
refuse6(`${name} declares unsupported native dependencies`);
|
|
116894
|
+
}
|
|
116895
|
+
const selectedPlugins = [];
|
|
116896
|
+
const pluginNames = new Set, sanitizedCachePaths = new Set;
|
|
116897
|
+
for (const selected of candidate2.plugins) {
|
|
116898
|
+
keys3(selected, ["plugin", "manifestPath", "manifestSha256", "optionsPatch"], "selected plugin");
|
|
116899
|
+
if (typeof selected.plugin !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(selected.plugin))
|
|
116900
|
+
refuse6("invalid selected plugin name");
|
|
116901
|
+
if (pluginNames.has(selected.plugin))
|
|
116902
|
+
refuse6("candidate repeats a selected plugin");
|
|
116903
|
+
pluginNames.add(selected.plugin);
|
|
116904
|
+
if (!object11(selected.optionsPatch))
|
|
116905
|
+
refuse6("selected plugin optionsPatch must be a JSON object");
|
|
116906
|
+
assertJsonValue(selected.optionsPatch, "selected plugin optionsPatch");
|
|
116907
|
+
if (selected.optionsPatch.enabled !== true)
|
|
116908
|
+
refuse6("selected plugin optionsPatch must explicitly declare enabled: true");
|
|
116909
|
+
if (Buffer.byteLength(canonicalJson3(selected.optionsPatch), "utf8") > 16 * 1024)
|
|
116910
|
+
refuse6("selected plugin optionsPatch exceeds its byte bound");
|
|
116911
|
+
abs(selected.manifestPath, "manifest path");
|
|
116912
|
+
if (!selected.manifestPath.startsWith(candidate2.root + sep15) || basename18(dirname38(selected.manifestPath)) !== ".claude-plugin" || selected.manifestPath !== join68(dirname38(dirname38(selected.manifestPath)), ".claude-plugin", "plugin.json"))
|
|
116913
|
+
refuse6("plugin manifest must be under a plugin root inside the frozen marketplace closure");
|
|
116914
|
+
const pluginRoot2 = dirname38(dirname38(selected.manifestPath));
|
|
116915
|
+
const relativePluginRoot = relative12(candidate2.root, pluginRoot2);
|
|
116916
|
+
if (!relativePluginRoot || relativePluginRoot === "." || relativePluginRoot === ".." || relativePluginRoot.startsWith(`..${sep15}`) || isAbsolute34(relativePluginRoot))
|
|
116917
|
+
refuse6("plugin root must be a child of the frozen marketplace root");
|
|
116918
|
+
canonicalDir(pluginRoot2, "candidate plugin root");
|
|
116919
|
+
if (!SHA256.test(selected.manifestSha256))
|
|
116920
|
+
refuse6("selected plugin manifest digest must be lowercase SHA-256");
|
|
116921
|
+
if (hashClaudePluginManifest(safeFile(selected.manifestPath, "candidate plugin manifest").toString("utf8")) !== selected.manifestSha256)
|
|
116922
|
+
refuse6("candidate plugin manifest changed or is invalid");
|
|
116923
|
+
const manifest = JSON.parse(safeFile(selected.manifestPath, "candidate plugin manifest").toString("utf8"));
|
|
116924
|
+
if (!object11(manifest) || manifest.name !== selected.plugin)
|
|
116925
|
+
refuse6("candidate plugin manifest identity differs from the selected entry");
|
|
116926
|
+
const entries = candidateCatalog.plugins.filter((entry2) => object11(entry2) && entry2.name === selected.plugin);
|
|
116927
|
+
if (entries.length !== 1 || !object11(entries[0]))
|
|
116928
|
+
refuse6("candidate marketplace plugin entry is missing or ambiguous");
|
|
116929
|
+
const selectedEntry = entries[0];
|
|
116930
|
+
if (selectedEntry.skills !== undefined || selectedEntry.commands !== undefined)
|
|
116931
|
+
refuse6("candidate marketplace entry declares native skill or command payloads");
|
|
116932
|
+
if (selectedEntry.dependencies !== undefined && (!Array.isArray(selectedEntry.dependencies) || selectedEntry.dependencies.length !== 0))
|
|
116933
|
+
refuse6("selected marketplace plugin declares unsupported native dependencies");
|
|
116934
|
+
if (manifest.skills !== undefined || manifest.commands !== undefined)
|
|
116935
|
+
refuse6("candidate plugin declares native skill or command payloads; project those through the Skills plugin admission flow first");
|
|
116936
|
+
const manifestVersion = manifest.version;
|
|
116937
|
+
if (typeof manifestVersion !== "string" || !manifestVersion || typeof selectedEntry.version !== "string" || selectedEntry.version !== manifestVersion)
|
|
116938
|
+
refuse6("candidate manifest version differs from the selected marketplace entry version");
|
|
116939
|
+
const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
116940
|
+
const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
116941
|
+
if (manifestVersion.length > 256 || /[\x00-\x1f\x7f/\\]/.test(manifestVersion) || !sanitizedVersion)
|
|
116942
|
+
refuse6("candidate version is not a supported native cache component");
|
|
116943
|
+
const sanitizedCachePath = [sanitizeComponent(candidate2.marketplace), sanitizeComponent(selected.plugin), sanitizedVersion].join("/");
|
|
116944
|
+
if (sanitizedCachePaths.has(sanitizedCachePath))
|
|
116945
|
+
refuse6("selected plugins collide after native cache path sanitization");
|
|
116946
|
+
sanitizedCachePaths.add(sanitizedCachePath);
|
|
116947
|
+
const sourcePath = selectedEntry.source;
|
|
116948
|
+
const sourceRelativePath = relative12(candidate2.root, pluginRoot2).split(sep15).join("/");
|
|
116949
|
+
const expectedSourcePath = sourceRelativePath === "." ? "./" : `./${sourceRelativePath}`;
|
|
116950
|
+
if (typeof sourcePath !== "string" || isAbsolute34(sourcePath) || sourcePath !== expectedSourcePath || resolve33(dirname38(dirname38(candidate2.catalogPath)), sourcePath) !== pluginRoot2)
|
|
116951
|
+
refuse6("marketplace source does not resolve to the frozen plugin closure; external source mapping is not supported by this schema");
|
|
116952
|
+
const entry = captureClaudeMarketplaceEntry(candidate2.catalogPath, candidate2.marketplace, selected.plugin);
|
|
116953
|
+
if (entry.sha256.length !== 64)
|
|
116954
|
+
refuse6("candidate marketplace entry is invalid");
|
|
116955
|
+
assertNoUnprojectedSkillPayload(candidate2.root, candidate2.catalogPath, candidate2.marketplace, selected.plugin, manifest);
|
|
116956
|
+
selectedPlugins.push({ plugin: selected.plugin, pluginId: `${selected.plugin}@${candidate2.marketplace}`, manifestSha256: selected.manifestSha256, optionsPatch: selected.optionsPatch, pluginRoot: pluginRoot2, manifest, selectedEntry });
|
|
116957
|
+
}
|
|
116958
|
+
if (candidate2.releaseManifest !== undefined) {
|
|
116959
|
+
keys3(candidate2.releaseManifest, ["path", "sha256"], "release manifest witness");
|
|
116960
|
+
abs(candidate2.releaseManifest.path, "release manifest path");
|
|
116961
|
+
if (!SHA256.test(candidate2.releaseManifest.sha256) || sha7(safeFile(candidate2.releaseManifest.path, "release manifest")) !== candidate2.releaseManifest.sha256)
|
|
116962
|
+
refuse6("complete release manifest bytes changed or lack a valid digest");
|
|
116963
|
+
}
|
|
116964
|
+
if (!request2.preimages || !object11(request2.preimages))
|
|
116965
|
+
refuse6("current Claude preimages are required");
|
|
116966
|
+
keys3(request2.preimages, ["settings", "userSettings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"], "current Claude preimages");
|
|
116967
|
+
for (const name of ["settings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"])
|
|
116968
|
+
keys3(request2.preimages[name], ["path", "sha256"], `${name} preimage`);
|
|
116969
|
+
const expectedPaths = {
|
|
116970
|
+
settings: request2.scope.kind === "project" ? canonical8(join68(request2.scope.projectPath, ".claude/settings.json")) : canonical8(join68(request2.native.home, ".claude/settings.json")),
|
|
116971
|
+
userSettings: canonical8(join68(request2.native.home, ".claude/settings.json")),
|
|
116972
|
+
projectSettings: canonical8(join68(request2.scope.projectPath, ".claude/settings.json")),
|
|
116973
|
+
projectSettingsLocal: canonical8(join68(request2.scope.projectPath, ".claude/settings.local.json")),
|
|
116974
|
+
marketplaces: canonical8(join68(request2.native.home, ".claude/plugins/known_marketplaces.json")),
|
|
116975
|
+
installedPlugins: canonical8(join68(request2.native.home, ".claude/plugins/installed_plugins.json"))
|
|
116976
|
+
};
|
|
116977
|
+
for (const name of Object.keys(expectedPaths)) {
|
|
116978
|
+
const witness = request2.preimages[name];
|
|
116979
|
+
if (witness.path !== expectedPaths[name])
|
|
116980
|
+
refuse6(`${name} preimage path is not the selected Claude path`);
|
|
116981
|
+
readWitness(witness.path, witness.sha256, name);
|
|
116982
|
+
}
|
|
116983
|
+
if (!request2.review || request2.review.pluginHooks !== "reviewed-no-skill-injection")
|
|
116984
|
+
refuse6("an explicit no-skill-injection review is required");
|
|
116985
|
+
if (request2.skills.discoveryInputs?.version !== 1 || !Array.isArray(request2.skills.discoveryInputs.agents) || request2.skills.discoveryInputs.agents.length !== 1 || request2.skills.discoveryInputs.agents[0]?.agent !== "claude" || request2.skills.discoveryInputs.agents[0]?.pluginHooks !== "reviewed-no-skill-injection")
|
|
116986
|
+
refuse6("one explicit reviewed Claude discovery binding is required");
|
|
116987
|
+
const reviewedPaths = new Set(request2.skills.discoveryInputs.agents[0].sources.map((source) => source.path));
|
|
116988
|
+
for (const path of new Set(Object.values(expectedPaths)))
|
|
116989
|
+
if (!reviewedPaths.has(path))
|
|
116990
|
+
refuse6("Skills discovery review does not bind every current user/project Claude settings and registration source");
|
|
116991
|
+
const binding = resolveAgentDiscovery({ home: request2.native.home, agent: "claude", canonical: canonical8, reviewed: request2.skills.discoveryInputs });
|
|
116992
|
+
verifyAgentDiscovery(binding);
|
|
116993
|
+
if (sha7(JSON.stringify(binding)) !== request2.skills.discoverySha256)
|
|
116994
|
+
refuse6("Skills discovery preimage differs from its reviewed binding");
|
|
116995
|
+
assertProjectDiscovery("claude", [request2.scope.projectPath], request2.native.home, canonical8, binding);
|
|
116996
|
+
const stored = snapshot2.value.bridge?.discovery?.claude;
|
|
116997
|
+
if (!stored || !isDeepStrictEqual18(stored, binding) || snapshot2.value.bridge?.home !== request2.native.home)
|
|
116998
|
+
refuse6("Skills policy does not bind the reviewed current Claude discovery");
|
|
116999
|
+
const delta = request2.delta;
|
|
117000
|
+
keys3(delta, ["settingsAfter", "knownMarketplacesAfter", "installedPluginsAfter"], "complete semantic delta");
|
|
117001
|
+
for (const [key, value] of Object.entries(delta))
|
|
117002
|
+
if (!object11(value))
|
|
117003
|
+
refuse6(`${key} must be a complete JSON object`);
|
|
117004
|
+
const settings = jsonFile(expectedPaths.settings, "Claude settings");
|
|
117005
|
+
if (settings && settings.enabledPlugins !== undefined && !object11(settings.enabledPlugins))
|
|
117006
|
+
refuse6("current settings enabledPlugins is not an object");
|
|
117007
|
+
if (settings?.pluginConfigs !== undefined && !object11(settings.pluginConfigs))
|
|
117008
|
+
refuse6("current settings pluginConfigs is not an object");
|
|
117009
|
+
const marketplaces = jsonFile(expectedPaths.marketplaces, "Claude known marketplaces");
|
|
117010
|
+
const registrations = jsonFile(expectedPaths.installedPlugins, "Claude installed plugins");
|
|
117011
|
+
if (registrations && (registrations.version !== 2 || !object11(registrations.plugins)))
|
|
117012
|
+
refuse6("current installed plugin registry has an unsupported shape");
|
|
117013
|
+
const settingsAfter = settings ? structuredClone(settings) : {};
|
|
117014
|
+
if (settingsAfter.enabledPlugins === undefined)
|
|
117015
|
+
settingsAfter.enabledPlugins = {};
|
|
117016
|
+
if (!object11(settingsAfter.enabledPlugins))
|
|
117017
|
+
refuse6("target settings enabledPlugins is not an object");
|
|
117018
|
+
if (settingsAfter.pluginConfigs === undefined)
|
|
117019
|
+
settingsAfter.pluginConfigs = {};
|
|
117020
|
+
if (!object11(settingsAfter.pluginConfigs))
|
|
117021
|
+
refuse6("target settings pluginConfigs is not an object");
|
|
117022
|
+
const targetRegistrations = new Map;
|
|
117023
|
+
const selectedPluginIds = [];
|
|
117024
|
+
const selectedOptionPaths = [];
|
|
117025
|
+
for (const selected of selectedPlugins) {
|
|
117026
|
+
const { pluginId: pluginId2, plugin, manifest, optionsPatch } = selected;
|
|
117027
|
+
if (Array.isArray(settings?.enabledPlugins?.[pluginId2]))
|
|
117028
|
+
refuse6("selected enabledPlugins value is an array; this schema does not model that native shape");
|
|
117029
|
+
selectedPluginIds.push(pluginId2);
|
|
117030
|
+
settingsAfter.enabledPlugins[pluginId2] = true;
|
|
117031
|
+
const currentConfig = settings?.pluginConfigs?.[pluginId2];
|
|
117032
|
+
if (currentConfig !== undefined && !object11(currentConfig))
|
|
117033
|
+
refuse6("selected pluginConfigs entry is malformed");
|
|
117034
|
+
const currentOptions = currentConfig?.options;
|
|
117035
|
+
if (currentOptions !== undefined && !object11(currentOptions))
|
|
117036
|
+
refuse6("selected pluginConfigs options are malformed");
|
|
117037
|
+
const nextConfig = currentConfig === undefined ? {} : structuredClone(currentConfig);
|
|
117038
|
+
nextConfig.options = { ...currentOptions ?? {}, ...optionsPatch, enabled: true };
|
|
117039
|
+
settingsAfter.pluginConfigs[pluginId2] = nextConfig;
|
|
117040
|
+
selectedOptionPaths.push(`pluginConfigs.${pluginId2}.options`);
|
|
117041
|
+
const hasCurrentPluginKey = !!registrations && Object.prototype.hasOwnProperty.call(registrations.plugins, pluginId2);
|
|
117042
|
+
const currentRows = hasCurrentPluginKey ? registrations.plugins[pluginId2] : [];
|
|
117043
|
+
if (!Array.isArray(currentRows))
|
|
117044
|
+
refuse6(`current selected plugin ${pluginId2} registrations are malformed`);
|
|
117045
|
+
for (const row2 of currentRows) {
|
|
117046
|
+
if (!object11(row2) || !["user", "project", "local"].includes(row2.scope) || typeof row2.installPath !== "string")
|
|
117047
|
+
refuse6(`current selected plugin ${pluginId2} registration row is malformed`);
|
|
117048
|
+
abs(row2.installPath, "registered plugin install path");
|
|
117049
|
+
if (row2.version !== undefined && (typeof row2.version !== "string" || !row2.version))
|
|
117050
|
+
refuse6("current selected plugin registration version is malformed");
|
|
117051
|
+
if (row2.scope === "project" || row2.scope === "local")
|
|
117052
|
+
abs(row2.projectPath, "registered plugin project path");
|
|
117053
|
+
else if (row2.projectPath !== undefined)
|
|
117054
|
+
refuse6("user plugin registration has a project path");
|
|
117055
|
+
}
|
|
117056
|
+
const isTargetScopeRow = (row2) => row2?.scope === request2.scope.kind && (request2.scope.kind === "user" ? row2.projectPath === undefined : row2.projectPath === request2.scope.projectPath);
|
|
117057
|
+
const targetIndexes = currentRows.map((row2, index) => isTargetScopeRow(row2) ? index : -1).filter((index) => index >= 0);
|
|
117058
|
+
if (targetIndexes.length > 1)
|
|
117059
|
+
refuse6(`current selected-scope registrations for ${pluginId2} are ambiguous`);
|
|
117060
|
+
const existingTarget = targetIndexes.length === 1 ? currentRows[targetIndexes[0]] : undefined;
|
|
117061
|
+
if (existingTarget !== undefined && Object.keys(existingTarget).some((key) => !["scope", "projectPath", "installPath", "version", "installedAt", "lastUpdated"].includes(key)))
|
|
117062
|
+
refuse6("current selected-scope plugin registration has unsupported fields");
|
|
117063
|
+
for (const key of ["installedAt", "lastUpdated"])
|
|
117064
|
+
if (existingTarget?.[key] !== undefined && !validUtcTimestamp(existingTarget[key]))
|
|
117065
|
+
refuse6(`current selected-scope plugin ${key} field is invalid`);
|
|
117066
|
+
const manifestVersion = manifest.version;
|
|
117067
|
+
const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
117068
|
+
const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
117069
|
+
const expectedInstallPath = join68(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
|
|
117070
|
+
if (expectedInstallPath.length > 4096)
|
|
117071
|
+
refuse6("candidate native cache path exceeds its bound");
|
|
117072
|
+
const targetRegistration = {
|
|
117073
|
+
scope: request2.scope.kind,
|
|
117074
|
+
...request2.scope.kind === "project" ? { projectPath: request2.scope.projectPath } : {},
|
|
117075
|
+
installPath: expectedInstallPath,
|
|
117076
|
+
version: manifestVersion,
|
|
117077
|
+
...existingTarget ? {
|
|
117078
|
+
...existingTarget.installedAt === undefined ? {} : { installedAt: existingTarget.installedAt },
|
|
117079
|
+
lastUpdated: existingTarget.lastUpdated === undefined ? GENERATED_PLUGIN_UPDATE_TIME : preserveOrGenerateTimestamp(existingTarget.lastUpdated, "installed_plugins.selected-row.lastUpdated")
|
|
117080
|
+
} : { installedAt: GENERATED_FRESH_PLUGIN_INSTALLED_AT, lastUpdated: GENERATED_PLUGIN_UPDATE_TIME }
|
|
117081
|
+
};
|
|
117082
|
+
targetRegistrations.set(pluginId2, targetRegistration);
|
|
117083
|
+
}
|
|
117084
|
+
if (candidate2.functionHooksEnv === "1") {
|
|
117085
|
+
const env3 = settingsAfter.env === undefined ? {} : settingsAfter.env;
|
|
117086
|
+
if (!object11(env3))
|
|
117087
|
+
refuse6("target settings env is not an object");
|
|
117088
|
+
settingsAfter.env = { ...env3, CLAUDE_CODE_ENABLE_FUNCTION_HOOKS: "1" };
|
|
117089
|
+
}
|
|
117090
|
+
if (!isDeepStrictEqual18(delta.settingsAfter, settingsAfter))
|
|
117091
|
+
refuse6("complete settings document differs from the exact selected plugin/options/environment operations");
|
|
117092
|
+
const marketplaceRowBefore = marketplaces?.[candidate2.marketplace];
|
|
117093
|
+
if (marketplaceRowBefore !== undefined && !object11(marketplaceRowBefore))
|
|
117094
|
+
refuse6("current marketplace row has an unsupported shape");
|
|
117095
|
+
if (marketplaceRowBefore !== undefined && Object.keys(marketplaceRowBefore).some((key) => !["source", "installLocation", "autoUpdate", "lastUpdated"].includes(key)))
|
|
117096
|
+
refuse6("current marketplace row has unsupported fields");
|
|
117097
|
+
if (marketplaceRowBefore?.autoUpdate !== undefined && typeof marketplaceRowBefore.autoUpdate !== "boolean")
|
|
117098
|
+
refuse6("current marketplace autoUpdate field is invalid");
|
|
117099
|
+
if (marketplaceRowBefore?.lastUpdated !== undefined && !validUtcTimestamp(marketplaceRowBefore.lastUpdated))
|
|
117100
|
+
refuse6("current marketplace update timestamp is invalid");
|
|
117101
|
+
const targetMarketplaceLocation = candidate2.root;
|
|
117102
|
+
const sameMarketplaceSource = isDeepStrictEqual18(marketplaceRowBefore?.source, { source: "directory", path: candidate2.root }) && marketplaceRowBefore?.installLocation === targetMarketplaceLocation;
|
|
117103
|
+
const afterMarketplace = {
|
|
117104
|
+
source: { source: "directory", path: candidate2.root },
|
|
117105
|
+
installLocation: targetMarketplaceLocation,
|
|
117106
|
+
autoUpdate: false,
|
|
117107
|
+
lastUpdated: sameMarketplaceSource && marketplaceRowBefore?.lastUpdated !== undefined ? preserveOrGenerateTimestamp(marketplaceRowBefore.lastUpdated, "known-marketplaces.lastUpdated") : GENERATED_MARKETPLACE_TIME
|
|
117108
|
+
};
|
|
117109
|
+
const marketplacesAfter = marketplaces ? structuredClone(marketplaces) : {};
|
|
117110
|
+
marketplacesAfter[candidate2.marketplace] = afterMarketplace;
|
|
117111
|
+
if (!isDeepStrictEqual18(delta.knownMarketplacesAfter, marketplacesAfter))
|
|
117112
|
+
refuse6("complete marketplace document differs from the exact selected-directory marketplace operation");
|
|
117113
|
+
const installedAfter = registrations ? structuredClone(registrations) : { version: 2, plugins: {} };
|
|
117114
|
+
if (installedAfter.plugins === undefined)
|
|
117115
|
+
installedAfter.plugins = {};
|
|
117116
|
+
if (!object11(installedAfter.plugins))
|
|
117117
|
+
refuse6("current installed plugin registry has an unsupported shape");
|
|
117118
|
+
for (const [pluginId2, targetRegistration] of targetRegistrations) {
|
|
117119
|
+
const rows = Object.prototype.hasOwnProperty.call(installedAfter.plugins, pluginId2) ? installedAfter.plugins[pluginId2] : [];
|
|
117120
|
+
if (!Array.isArray(rows))
|
|
117121
|
+
refuse6(`current selected plugin ${pluginId2} registrations are malformed`);
|
|
117122
|
+
const isTargetScopeRow = (row2) => row2?.scope === request2.scope.kind && (request2.scope.kind === "user" ? row2.projectPath === undefined : row2.projectPath === request2.scope.projectPath);
|
|
117123
|
+
const index = rows.findIndex(isTargetScopeRow);
|
|
117124
|
+
const nextRows = [...rows];
|
|
117125
|
+
if (index < 0)
|
|
117126
|
+
nextRows.push(targetRegistration);
|
|
117127
|
+
else
|
|
117128
|
+
nextRows[index] = targetRegistration;
|
|
117129
|
+
installedAfter.plugins[pluginId2] = nextRows;
|
|
117130
|
+
}
|
|
117131
|
+
if (!isDeepStrictEqual18(delta.installedPluginsAfter, installedAfter))
|
|
117132
|
+
refuse6("complete installed plugin document changes an unrelated registration or has target rows not derived from every selected candidate");
|
|
117133
|
+
const semanticDocumentsSha256 = sha7(canonicalJson3({ settings: settingsAfter, knownMarketplaces: marketplacesAfter, installedPlugins: installedAfter }));
|
|
117134
|
+
const exactPreimages = Object.keys(expectedPaths).map((name) => ({ name, path: expectedPaths[name], sha256: request2.preimages[name].sha256 }));
|
|
117135
|
+
const expectedPostStateSha256 = sha7(canonicalJson3({ paths: expectedPaths, targetSettingsPath: expectedPaths.settings, semanticDocumentsSha256, currentPreimages: exactPreimages }));
|
|
117136
|
+
const operationSha256 = sha7(canonicalJson3({
|
|
117137
|
+
scope: request2.scope,
|
|
117138
|
+
native: request2.native,
|
|
117139
|
+
candidate: request2.candidate,
|
|
117140
|
+
paths: expectedPaths,
|
|
117141
|
+
preimages: exactPreimages,
|
|
117142
|
+
skills: { dataDir: request2.skills.dataDir, policySha256: request2.skills.policySha256, discoverySha256: request2.skills.discoverySha256 },
|
|
117143
|
+
review: request2.review,
|
|
117144
|
+
semanticDocumentsSha256
|
|
117145
|
+
}));
|
|
117146
|
+
return {
|
|
117147
|
+
schema: CLAUDE_PROSPECTIVE_REVIEW_SCHEMA,
|
|
117148
|
+
accepted: true,
|
|
117149
|
+
applied: false,
|
|
117150
|
+
scope: request2.scope,
|
|
117151
|
+
pluginIds: selectedPluginIds,
|
|
117152
|
+
native: { executablePath: request2.native.executable.path, executableTarget: request2.native.executable.target, executableVersion: request2.native.executable.version, executableSha256: request2.native.executable.sha256, configRoot: request2.native.configRoot, pluginRoot: request2.native.pluginRoot },
|
|
117153
|
+
candidate: { marketplace: candidate2.marketplace, plugins: selectedPlugins.map((item) => ({ plugin: item.plugin, pluginId: item.pluginId, manifestSha256: item.manifestSha256, optionsPatchSha256: sha7(canonicalJson3(item.optionsPatch)) })), closureSha256: candidate2.closureSha256, catalogSha256: candidate2.catalogSha256, ...candidate2.releaseManifest ? { releaseManifest: candidate2.releaseManifest } : {} },
|
|
117154
|
+
targetSettingsPath: expectedPaths.settings,
|
|
117155
|
+
skillsDiscoverySha256: request2.skills.discoverySha256,
|
|
117156
|
+
requestFileSha256,
|
|
117157
|
+
semanticDocumentsSha256,
|
|
117158
|
+
operationSha256,
|
|
117159
|
+
expectedPostStateSha256,
|
|
117160
|
+
preimages: [
|
|
117161
|
+
{ name: "settings", path: expectedPaths.settings, sha256: request2.preimages.settings.sha256 },
|
|
117162
|
+
{ name: "user-settings", path: expectedPaths.userSettings, sha256: request2.preimages.userSettings.sha256 },
|
|
117163
|
+
{ name: "project-settings", path: expectedPaths.projectSettings, sha256: request2.preimages.projectSettings.sha256 },
|
|
117164
|
+
{ name: "project-settings-local", path: expectedPaths.projectSettingsLocal, sha256: request2.preimages.projectSettingsLocal.sha256 },
|
|
117165
|
+
{ name: "known-marketplaces", path: expectedPaths.marketplaces, sha256: request2.preimages.marketplaces.sha256 },
|
|
117166
|
+
{ name: "installed-plugins", path: expectedPaths.installedPlugins, sha256: request2.preimages.installedPlugins.sha256 },
|
|
117167
|
+
{ name: "skills-policy", path: join68(request2.skills.dataDir, "agent-policy.json"), sha256: request2.skills.policySha256 }
|
|
117168
|
+
],
|
|
117169
|
+
ownedDelta: { settingsEnabledPlugins: selectedPluginIds, selectedPluginOptions: selectedOptionPaths, functionHooksEnv: candidate2.functionHooksEnv ?? null, knownMarketplace: candidate2.marketplace, installedPlugins: selectedPluginIds, unrelatedGraph: "preserved-by-complete-semantic-documents" },
|
|
117170
|
+
identityPrecondition: "Harnesses must resolve the installed Claude home/config/plugin roots and executable path/version/hash plus the installed Skills data directory through their package-owned consumers, then compare every value before trusting this receipt; this validator checks supplied paths and current bytes only and does not establish actual consumer identity",
|
|
117171
|
+
reviewPrecondition: "The caller's no-skill-injection assessment is bound to the raw request hash; structural checks are not semantic hook review, and Harnesses must retain its existing review record and verify the exact candidate binding",
|
|
117172
|
+
limitation: "validation-only; native installer atomicity and crash recovery are not established"
|
|
117173
|
+
};
|
|
117174
|
+
}
|
|
117175
|
+
var CLAUDE_PROSPECTIVE_REVIEW_SCHEMA = "skills.claude-plugin-prospective-review/v1", SHA256, MAX_CANDIDATE_FILES = 16384, MAX_CANDIDATE_BYTES, sha7 = (data) => createHash41("sha256").update(data).digest("hex"), GENERATED_MARKETPLACE_TIME, GENERATED_FRESH_PLUGIN_INSTALLED_AT, GENERATED_PLUGIN_UPDATE_TIME;
|
|
117176
|
+
var init_claude_prospective_review = __esm(() => {
|
|
117177
|
+
init_agent_discovery();
|
|
117178
|
+
init_agent_integration();
|
|
117179
|
+
init_claude_marketplace_entry_witness();
|
|
117180
|
+
init_claude_plugin_manifest_witness();
|
|
117181
|
+
init_managed_policy();
|
|
117182
|
+
init_codex_native_skill_policy();
|
|
117183
|
+
SHA256 = /^[a-f0-9]{64}$/;
|
|
117184
|
+
MAX_CANDIDATE_BYTES = 256 * 1024 * 1024;
|
|
117185
|
+
GENERATED_MARKETPLACE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "known-marketplaces.lastUpdated" });
|
|
117186
|
+
GENERATED_FRESH_PLUGIN_INSTALLED_AT = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.fresh-row.installedAt" });
|
|
117187
|
+
GENERATED_PLUGIN_UPDATE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.selected-row.lastUpdated" });
|
|
117188
|
+
});
|
|
117189
|
+
|
|
116527
117190
|
// src/lib/self-spawn.ts
|
|
116528
|
-
import { realpathSync as
|
|
116529
|
-
import { dirname as
|
|
117191
|
+
import { realpathSync as realpathSync22 } from "fs";
|
|
117192
|
+
import { dirname as dirname39, isAbsolute as isAbsolute35, sep as sep16 } from "path";
|
|
116530
117193
|
function selfSpawnRoot(entry) {
|
|
116531
117194
|
const index = entry.indexOf(INSTALLED_PACKAGE);
|
|
116532
|
-
const root2 = index > 0 ? entry.slice(0, index) :
|
|
117195
|
+
const root2 = index > 0 ? entry.slice(0, index) : dirname39(entry);
|
|
116533
117196
|
try {
|
|
116534
|
-
return
|
|
117197
|
+
return realpathSync22(root2);
|
|
116535
117198
|
} catch {
|
|
116536
117199
|
return root2;
|
|
116537
117200
|
}
|
|
@@ -116539,7 +117202,7 @@ function selfSpawnRoot(entry) {
|
|
|
116539
117202
|
function selfSpawnCommand(args2, options = {}) {
|
|
116540
117203
|
const runtime = options.runtime ?? process.execPath;
|
|
116541
117204
|
const entry = options.entry ?? process.argv[1];
|
|
116542
|
-
if (!runtime || !
|
|
117205
|
+
if (!runtime || !isAbsolute35(runtime) || !entry || !isAbsolute35(entry))
|
|
116543
117206
|
throw new Error("SKILLS_SELF_SPAWN_ENTRY_UNRESOLVED");
|
|
116544
117207
|
const root2 = selfSpawnRoot(entry);
|
|
116545
117208
|
const env3 = pinnedLauncherEnvironment(options.env ?? process.env);
|
|
@@ -116549,7 +117212,7 @@ function selfSpawnCommand(args2, options = {}) {
|
|
|
116549
117212
|
var INSTALLED_PACKAGE;
|
|
116550
117213
|
var init_self_spawn = __esm(() => {
|
|
116551
117214
|
init_runtime_launcher();
|
|
116552
|
-
INSTALLED_PACKAGE = `${
|
|
117215
|
+
INSTALLED_PACKAGE = `${sep16}node_modules${sep16}@hasna${sep16}skills${sep16}`;
|
|
116553
117216
|
});
|
|
116554
117217
|
|
|
116555
117218
|
// src/cli/commands/agent-integration.ts
|
|
@@ -116557,10 +117220,10 @@ var exports_agent_integration = {};
|
|
|
116557
117220
|
__export(exports_agent_integration, {
|
|
116558
117221
|
registerAgentIntegration: () => registerAgentIntegration
|
|
116559
117222
|
});
|
|
116560
|
-
import { existsSync as existsSync36, lstatSync as
|
|
116561
|
-
import { createHash as
|
|
117223
|
+
import { existsSync as existsSync36, lstatSync as lstatSync38, readFileSync as readFileSync46, readSync as readSync18, writeFileSync as writeFileSync27 } from "fs";
|
|
117224
|
+
import { createHash as createHash42 } from "crypto";
|
|
116562
117225
|
import { homedir as homedir13 } from "os";
|
|
116563
|
-
import { isAbsolute as
|
|
117226
|
+
import { isAbsolute as isAbsolute36 } from "path";
|
|
116564
117227
|
function pinnedHookProfile(input3, configuredProfile) {
|
|
116565
117228
|
try {
|
|
116566
117229
|
const parsed = parseSkillContextInput(JSON.stringify(input3));
|
|
@@ -116656,7 +117319,7 @@ function registerAgentIntegration(parent) {
|
|
|
116656
117319
|
});
|
|
116657
117320
|
hook.command("native-catalog").requiredOption("--cwd <path>", "Absolute project directory observed by native Codex skills/list").requiredOption("--output <file>", "New private file for the projected native catalog").option("--codex-command <path>", "Installed Codex executable used by the native client", "codex").option("--json", "Output bounded capture metadata as JSON", false).description("Capture a reviewed Codex native skill catalog for exact hook enrollment").action(async (options) => {
|
|
116658
117321
|
try {
|
|
116659
|
-
if (!
|
|
117322
|
+
if (!isAbsolute36(options.cwd) || !isAbsolute36(options.output))
|
|
116660
117323
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_ABSOLUTE_PATH_REQUIRED");
|
|
116661
117324
|
if (existsSync36(options.output))
|
|
116662
117325
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
@@ -116675,8 +117338,8 @@ function registerAgentIntegration(parent) {
|
|
|
116675
117338
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
116676
117339
|
throw error61;
|
|
116677
117340
|
}
|
|
116678
|
-
const written =
|
|
116679
|
-
if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 ||
|
|
117341
|
+
const written = lstatSync38(options.output);
|
|
117342
|
+
if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 || readFileSync46(options.output, "utf8") !== bytes)
|
|
116680
117343
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_READBACK_FAILED");
|
|
116681
117344
|
const receipt = {
|
|
116682
117345
|
version: catalog.version,
|
|
@@ -116684,7 +117347,7 @@ function registerAgentIntegration(parent) {
|
|
|
116684
117347
|
skillCount: catalog.skills.length,
|
|
116685
117348
|
output: options.output,
|
|
116686
117349
|
bytes: Buffer.byteLength(bytes),
|
|
116687
|
-
sha256:
|
|
117350
|
+
sha256: createHash42("sha256").update(bytes).digest("hex")
|
|
116688
117351
|
};
|
|
116689
117352
|
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
|
|
116690
117353
|
} catch (error61) {
|
|
@@ -116707,6 +117370,22 @@ function registerAgentIntegration(parent) {
|
|
|
116707
117370
|
throw new Error("Unsupported witness kind; select sumi-settings-v1, claude-settings-v4, claude-settings-v3, claude-settings-v2, codex-settings-v4, codex-settings-v3, codex-settings-v2, codex-settings-v1, claude-marketplace-registry-v2 or claude-marketplace-entry-v1");
|
|
116708
117371
|
await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
|
|
116709
117372
|
});
|
|
117373
|
+
hook.command("review-claude-candidate").requiredOption("--request <file>", "Frozen skills.claude-plugin-prospective-review/v1 JSON request").option("--json", "Return the validation-only receipt as JSON", false).description("Validate a prospective Claude plugin candidate and current preimages without changing native state; Harnesses must compare native and Skills paths against installed package-owned resolvers before trusting the receipt").action(async (options) => {
|
|
117374
|
+
try {
|
|
117375
|
+
if (!isAbsolute36(options.request))
|
|
117376
|
+
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: --request must be an absolute path");
|
|
117377
|
+
const stat3 = lstatSync38(options.request);
|
|
117378
|
+
if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > 1024 * 1024)
|
|
117379
|
+
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: request must be a regular file no larger than 1 MiB");
|
|
117380
|
+
const requestBytes = readFileSync46(options.request);
|
|
117381
|
+
const request2 = JSON.parse(requestBytes.toString("utf8"));
|
|
117382
|
+
const receipt = reviewClaudeProspectiveCandidate(request2, createHash42("sha256").update(requestBytes).digest("hex"));
|
|
117383
|
+
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Claude plugin candidate review accepted for ${receipt.pluginIds.length} selected plugin(s); native state was not changed.`);
|
|
117384
|
+
} catch (error61) {
|
|
117385
|
+
console.error(error61 instanceof Error && error61.message.startsWith("CLAUDE_CANDIDATE_REVIEW_REFUSED:") ? error61.message : "CLAUDE_CANDIDATE_REVIEW_REFUSED: request or current preimage validation failed");
|
|
117386
|
+
process.exitCode = 1;
|
|
117387
|
+
}
|
|
117388
|
+
});
|
|
116710
117389
|
hook.command("rebind-settings").requiredOption("--agent <agent>", "claude, codex or sumi").requiredOption("--reviewed-preimage <path>", "Exact preserved legacy settings.json, config.toml or sumi.json").requiredOption("--expected-policy-sha256 <sha256>", "Exact current managed policy bytes").requiredOption("--expected-settings-sha256 <sha256>", "Exact current native settings bytes").option("--codex-witness-version <version>", "Explicit target: 2 (legacy default) 3 (service tier and model-advertised effort), or 4 (native availability UI counts)").option("--claude-witness-version <version>", "Explicit target: 3 (default) or 4 (also permits a built-in top-level theme)").option("--apply", "Apply the explicit semantic witness upgrade with preservation and readback", false).option("--json", "Return the metadata-only plan or receipt", false).description("Explicitly migrate a legacy native settings witness after proving only known preferences changed").action(async (options) => {
|
|
116711
117390
|
try {
|
|
116712
117391
|
if (!["claude", "codex", "sumi"].includes(options.agent))
|
|
@@ -116742,8 +117421,8 @@ function registerAgentIntegration(parent) {
|
|
|
116742
117421
|
});
|
|
116743
117422
|
hook.command("install").option("--agent <agent>", `Agent to configure: ${INTEGRATION_AGENTS.join(", ")}, all`, "all").option("--command <path>", "Skills executable used by the hook (preserves existing binding; new agents use skills)").option("--selection-profile <id>", "Selection profile (preserves existing binding; new agents use default)").option("--include-vendor", "Retained for compatibility; vendor system skills are always inventoried and disabled", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes for unsupported registrations").option("--codex-skill-denials <file>", "Explicit reviewed remote skill denials: array of exact name, path and document sha256; requires fresh catalog and discovery inputs").option("--codex-native-catalog <file>", "Reviewed supported Codex skill and installed-plugin receipt for exact qualified-name disables").option("--reviewed-cache-alias <path>", "Exact skill-containing vendor cache alias reviewed for this hook plan").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Apply the plan, preserving prior configuration in private backups", false).option("--json", "Output a receipt as JSON", false).description("Plan or install one Skills CLI bridge plus native prompt hooks").action(async (options) => {
|
|
116744
117423
|
try {
|
|
116745
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
116746
|
-
const plan = planAgentIntegration({ projectDir: process.cwd(), agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(
|
|
117424
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync46(options.discoveryInputs, "utf8")) : undefined;
|
|
117425
|
+
const plan = planAgentIntegration({ projectDir: process.cwd(), agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(readFileSync46(options.codexSkillDenials, "utf8")) : undefined, codexNativeCatalog: options.codexNativeCatalog ? JSON.parse(readFileSync46(options.codexNativeCatalog, "utf8")) : undefined });
|
|
116747
117426
|
const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
|
|
116748
117427
|
const receipt = { codexPluginSkillReview: plan.codexPluginSkillReview, codexPluginSkills: plan.changes.filter((change) => change.path.endsWith("agent-policy.json")).map((change) => JSON.parse(change.after).bridge?.codexPluginSkills ?? []).flat(), applied: options.apply, planned: plan.changes.map((change) => change.path), ...result2, rootAliases: plan.rootAliases ?? [], discovery: plan.discoveryAfter, nativeSkills: plan.nativeSkills.map((entry) => ({ agent: entry.agent, path: entry.path, managed: entry.managed, vendor: entry.vendor, system: entry.system === true, bridge: entry.bridge === true })), requiresNativeRetirement: plan.nativeSkills.some((entry) => !entry.bridge && !entry.system) };
|
|
116749
117428
|
if (options.json)
|
|
@@ -116802,7 +117481,7 @@ function registerAgentIntegration(parent) {
|
|
|
116802
117481
|
try {
|
|
116803
117482
|
if (agents(options.agent).length !== 1)
|
|
116804
117483
|
throw new Error("A hook invocation requires one agent");
|
|
116805
|
-
const inputBytes =
|
|
117484
|
+
const inputBytes = readFileSync46(0), inputText = inputBytes.toString("utf8");
|
|
116806
117485
|
if (inputText.length > 1024 * 1024)
|
|
116807
117486
|
throw new Error("Hook input is too large");
|
|
116808
117487
|
let input3 = JSON.parse(inputText);
|
|
@@ -116815,12 +117494,12 @@ function registerAgentIntegration(parent) {
|
|
|
116815
117494
|
input3.hook_event_name = event;
|
|
116816
117495
|
const projects = [process.cwd()];
|
|
116817
117496
|
if (input3.cwd !== undefined) {
|
|
116818
|
-
if (typeof input3.cwd !== "string" || !
|
|
117497
|
+
if (typeof input3.cwd !== "string" || !isAbsolute36(input3.cwd) || input3.cwd.includes("\x00"))
|
|
116819
117498
|
throw new Error("Invalid native hook working directory");
|
|
116820
117499
|
projects.push(input3.cwd);
|
|
116821
117500
|
}
|
|
116822
117501
|
if (options.agent === "cursor") {
|
|
116823
|
-
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !
|
|
117502
|
+
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !isAbsolute36(path) || path.includes("\x00")))
|
|
116824
117503
|
throw new Error("Invalid Cursor workspace roots");
|
|
116825
117504
|
projects.push(...input3.workspace_roots);
|
|
116826
117505
|
input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
|
|
@@ -116918,7 +117597,7 @@ function registerAgentIntegration(parent) {
|
|
|
116918
117597
|
try {
|
|
116919
117598
|
if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
|
|
116920
117599
|
throw new Error(`Unsupported migration agent: ${options.agent}`);
|
|
116921
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
117600
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync46(options.discoveryInputs, "utf8")) : undefined;
|
|
116922
117601
|
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
116923
117602
|
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
116924
117603
|
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
@@ -116956,6 +117635,7 @@ var init_agent_integration2 = __esm(() => {
|
|
|
116956
117635
|
init_codex_settings_witness();
|
|
116957
117636
|
init_claude_marketplace_registry();
|
|
116958
117637
|
init_claude_marketplace_entry_witness();
|
|
117638
|
+
init_claude_prospective_review();
|
|
116959
117639
|
init_codex_native_skill_catalog();
|
|
116960
117640
|
init_self_spawn();
|
|
116961
117641
|
RECOVERABLE_CONTEXT_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
|
|
@@ -117231,28 +117911,28 @@ var init_grants = __esm(() => {
|
|
|
117231
117911
|
});
|
|
117232
117912
|
|
|
117233
117913
|
// src/lib/station-hydrate.ts
|
|
117234
|
-
import { createHash as
|
|
117914
|
+
import { createHash as createHash43 } from "crypto";
|
|
117235
117915
|
import {
|
|
117236
117916
|
copyFileSync as copyFileSync3,
|
|
117237
117917
|
mkdirSync as mkdirSync29,
|
|
117238
|
-
readdirSync as
|
|
117239
|
-
readFileSync as
|
|
117918
|
+
readdirSync as readdirSync25,
|
|
117919
|
+
readFileSync as readFileSync47,
|
|
117240
117920
|
statSync as statSync23,
|
|
117241
117921
|
writeFileSync as writeFileSync30
|
|
117242
117922
|
} from "fs";
|
|
117243
|
-
import { dirname as
|
|
117923
|
+
import { dirname as dirname40, join as join69, resolve as resolve34, sep as sep17 } from "path";
|
|
117244
117924
|
function fail6(code2, message, detail = []) {
|
|
117245
117925
|
throw new StationSnapshotError(code2, message, detail);
|
|
117246
117926
|
}
|
|
117247
117927
|
function snapshotRootFor(repoRoot, stationId) {
|
|
117248
|
-
return
|
|
117928
|
+
return join69(repoRoot, "resources", stationId, "skills");
|
|
117249
117929
|
}
|
|
117250
117930
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
117251
117931
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
117252
|
-
const manifestPath =
|
|
117932
|
+
const manifestPath = join69(snapshotRoot, "sync-manifest.json");
|
|
117253
117933
|
let manifest;
|
|
117254
117934
|
try {
|
|
117255
|
-
manifest = JSON.parse(
|
|
117935
|
+
manifest = JSON.parse(readFileSync47(manifestPath, "utf8"));
|
|
117256
117936
|
} catch (error61) {
|
|
117257
117937
|
fail6("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
117258
117938
|
}
|
|
@@ -117276,10 +117956,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117276
117956
|
const hashMismatches = [];
|
|
117277
117957
|
const skippedByRule = [];
|
|
117278
117958
|
for (const agent of SYNC_AGENTS) {
|
|
117279
|
-
const agentRoot =
|
|
117959
|
+
const agentRoot = join69(snapshotRoot, "agent-homes", agent);
|
|
117280
117960
|
let identEntries;
|
|
117281
117961
|
try {
|
|
117282
|
-
identEntries =
|
|
117962
|
+
identEntries = readdirSync25(agentRoot, { withFileTypes: true });
|
|
117283
117963
|
} catch {
|
|
117284
117964
|
continue;
|
|
117285
117965
|
}
|
|
@@ -117287,10 +117967,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117287
117967
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
117288
117968
|
continue;
|
|
117289
117969
|
}
|
|
117290
|
-
const identRoot =
|
|
117970
|
+
const identRoot = join69(agentRoot, identEntry.name);
|
|
117291
117971
|
const entries = walkEntries(identRoot);
|
|
117292
117972
|
for (const entry of entries) {
|
|
117293
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
117973
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep17)];
|
|
117294
117974
|
if (entry.kind === "symlink") {
|
|
117295
117975
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
117296
117976
|
continue;
|
|
@@ -117314,8 +117994,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117314
117994
|
});
|
|
117315
117995
|
continue;
|
|
117316
117996
|
}
|
|
117317
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
117318
|
-
const homeRelative = relativeParts.join(
|
|
117997
|
+
const withinIdent = relativeParts.slice(1).join(sep17);
|
|
117998
|
+
const homeRelative = relativeParts.join(sep17);
|
|
117319
117999
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
117320
118000
|
skippedByRule.push({
|
|
117321
118001
|
ident: identEntry.name,
|
|
@@ -117388,7 +118068,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117388
118068
|
for (const copy of copies) {
|
|
117389
118069
|
let isStub = false;
|
|
117390
118070
|
try {
|
|
117391
|
-
isStub = isPointerSkillMd(
|
|
118071
|
+
isStub = isPointerSkillMd(readFileSync47(copy.fullPath, "utf8"));
|
|
117392
118072
|
} catch {
|
|
117393
118073
|
isStub = false;
|
|
117394
118074
|
}
|
|
@@ -117434,12 +118114,12 @@ function skillSha256(skill) {
|
|
|
117434
118114
|
return sha256File(skill.files[0].winner.fullPath);
|
|
117435
118115
|
}
|
|
117436
118116
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
117437
|
-
return
|
|
118117
|
+
return createHash43("sha256").update(joined.sort().join(`
|
|
117438
118118
|
`)).digest("hex");
|
|
117439
118119
|
}
|
|
117440
118120
|
function writeStationHydration(options) {
|
|
117441
|
-
const repoRoot =
|
|
117442
|
-
const cacheRoot =
|
|
118121
|
+
const repoRoot = resolve34(options.repoRoot ?? process.cwd());
|
|
118122
|
+
const cacheRoot = resolve34(options.cacheRoot ?? resolveCorpusRoot());
|
|
117443
118123
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
117444
118124
|
const resultSkills = plan.winners.map((skill) => ({
|
|
117445
118125
|
ident: skill.ident,
|
|
@@ -117472,7 +118152,7 @@ function writeStationHydration(options) {
|
|
|
117472
118152
|
const toWrite = [];
|
|
117473
118153
|
for (const skill of plan.winners) {
|
|
117474
118154
|
for (const file2 of skill.files) {
|
|
117475
|
-
const destination =
|
|
118155
|
+
const destination = join69(cacheRoot, skill.ident, file2.withinIdent);
|
|
117476
118156
|
const digest4 = sha256File(file2.winner.fullPath);
|
|
117477
118157
|
let existingDigest = null;
|
|
117478
118158
|
try {
|
|
@@ -117493,7 +118173,7 @@ function writeStationHydration(options) {
|
|
|
117493
118173
|
}
|
|
117494
118174
|
let written = 0;
|
|
117495
118175
|
for (const entry of toWrite) {
|
|
117496
|
-
mkdirSync29(
|
|
118176
|
+
mkdirSync29(dirname40(entry.destination), { recursive: true });
|
|
117497
118177
|
copyFileSync3(entry.fullPath, entry.destination);
|
|
117498
118178
|
written += 1;
|
|
117499
118179
|
}
|
|
@@ -117514,8 +118194,8 @@ function writeStationHydration(options) {
|
|
|
117514
118194
|
},
|
|
117515
118195
|
skills: resultSkills
|
|
117516
118196
|
};
|
|
117517
|
-
const hydrationManifestPath =
|
|
117518
|
-
mkdirSync29(
|
|
118197
|
+
const hydrationManifestPath = join69(dirname40(cacheRoot), `hydration-${options.stationId}.json`);
|
|
118198
|
+
mkdirSync29(dirname40(hydrationManifestPath), { recursive: true });
|
|
117519
118199
|
writeFileSync30(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
117520
118200
|
`);
|
|
117521
118201
|
return {
|
|
@@ -117591,15 +118271,15 @@ var init_hydrate = __esm(() => {
|
|
|
117591
118271
|
});
|
|
117592
118272
|
|
|
117593
118273
|
// src/lib/prepare-skill.ts
|
|
117594
|
-
import { createHash as
|
|
117595
|
-
import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as
|
|
118274
|
+
import { createHash as createHash44, randomUUID as randomUUID13 } from "crypto";
|
|
118275
|
+
import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as lstatSync39, mkdirSync as mkdirSync30, mkdtempSync as mkdtempSync6, readFileSync as readFileSync48, readdirSync as readdirSync26, renameSync as renameSync15, rmSync as rmSync9, writeFileSync as writeFileSync31 } from "fs";
|
|
117596
118276
|
import { tmpdir as tmpdir2 } from "os";
|
|
117597
|
-
import { dirname as
|
|
118277
|
+
import { dirname as dirname41, isAbsolute as isAbsolute37, join as join70, relative as relative13, resolve as resolve35, sep as sep18 } from "path";
|
|
117598
118278
|
function prepareSkill(name, options) {
|
|
117599
118279
|
const normalized = normalizePortableSkillName(name);
|
|
117600
118280
|
if (!new RegExp(SEMVER_PATTERN).test(options.version))
|
|
117601
118281
|
throw new Error("--version must be a semantic version, for example 0.2.0.");
|
|
117602
|
-
const path =
|
|
118282
|
+
const path = join70(getPortableSkillsRootReadOnly(options), normalized);
|
|
117603
118283
|
assertPortableAuthoringPath(path, options);
|
|
117604
118284
|
const before2 = snapshot2(path);
|
|
117605
118285
|
const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
|
|
@@ -117634,16 +118314,16 @@ function prepareSkill(name, options) {
|
|
|
117634
118314
|
throw new Error("skill.json provenance must be an object.");
|
|
117635
118315
|
const provenance = manifest.provenance;
|
|
117636
118316
|
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
117637
|
-
const staging = mkdtempSync6(
|
|
118317
|
+
const staging = mkdtempSync6(join70(tmpdir2(), "skills-prepare-"));
|
|
117638
118318
|
try {
|
|
117639
118319
|
for (const directory2 of before2.directories)
|
|
117640
|
-
mkdirSync30(
|
|
118320
|
+
mkdirSync30(join70(staging, directory2), { recursive: true });
|
|
117641
118321
|
for (const file2 of before2.files) {
|
|
117642
|
-
const destination =
|
|
117643
|
-
mkdirSync30(
|
|
118322
|
+
const destination = join70(staging, file2.path);
|
|
118323
|
+
mkdirSync30(dirname41(destination), { recursive: true });
|
|
117644
118324
|
writeFileSync31(destination, file2.bytes, { mode: file2.mode });
|
|
117645
118325
|
}
|
|
117646
|
-
const stagedManifest =
|
|
118326
|
+
const stagedManifest = join70(staging, "skill.json");
|
|
117647
118327
|
writeFileSync31(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
|
|
117648
118328
|
`);
|
|
117649
118329
|
const contentHash = computeContentHash(staging);
|
|
@@ -117666,11 +118346,11 @@ ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
|
|
|
117666
118346
|
if (snapshot2(path).identity !== before2.identity)
|
|
117667
118347
|
throw new Error("Skill changed during preparation; review the draft and run prepare again.");
|
|
117668
118348
|
if (changed3 && !options.dryRun) {
|
|
117669
|
-
const temporary =
|
|
118349
|
+
const temporary = join70(path, `.skill-prepare-${randomUUID13()}.tmp`);
|
|
117670
118350
|
try {
|
|
117671
118351
|
writeFileSync31(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
|
|
117672
118352
|
chmodSync5(temporary, manifestFile.mode);
|
|
117673
|
-
renameSync15(temporary,
|
|
118353
|
+
renameSync15(temporary, join70(path, "skill.json"));
|
|
117674
118354
|
} finally {
|
|
117675
118355
|
rmSync9(temporary, { force: true });
|
|
117676
118356
|
}
|
|
@@ -117700,8 +118380,8 @@ function validateRuntimeEntrypoint(candidate2, root2) {
|
|
|
117700
118380
|
const entry = candidate2.runtime.entrypoint;
|
|
117701
118381
|
if (typeof entry !== "string")
|
|
117702
118382
|
return;
|
|
117703
|
-
const target =
|
|
117704
|
-
if (
|
|
118383
|
+
const target = resolve35(root2, entry), rel = relative13(root2, target);
|
|
118384
|
+
if (isAbsolute37(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep18}`) || isAbsolute37(rel) || !existsSync37(target) || !lstatSync39(target).isFile()) {
|
|
117705
118385
|
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
117706
118386
|
}
|
|
117707
118387
|
}
|
|
@@ -117786,20 +118466,20 @@ function compareVersions(left, right) {
|
|
|
117786
118466
|
return 0;
|
|
117787
118467
|
}
|
|
117788
118468
|
function snapshot2(root2) {
|
|
117789
|
-
const rootStat =
|
|
118469
|
+
const rootStat = lstatSync39(root2);
|
|
117790
118470
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
117791
118471
|
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
117792
118472
|
const files = [], directories = [];
|
|
117793
|
-
const hash6 =
|
|
118473
|
+
const hash6 = createHash44("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
117794
118474
|
let count = 0, bytes = 0;
|
|
117795
|
-
const walk2 = (
|
|
117796
|
-
for (const name of
|
|
117797
|
-
if (IGNORED.has(name.toLowerCase()) || !
|
|
118475
|
+
const walk2 = (relative14) => {
|
|
118476
|
+
for (const name of readdirSync26(join70(root2, relative14)).sort()) {
|
|
118477
|
+
if (IGNORED.has(name.toLowerCase()) || !relative14 && ROOT_IGNORED.has(name.toLowerCase()))
|
|
117798
118478
|
continue;
|
|
117799
|
-
const path =
|
|
118479
|
+
const path = relative14 ? `${relative14}/${name}` : name;
|
|
117800
118480
|
if (++count > 20000)
|
|
117801
118481
|
throw new Error("Skill exceeds the preparation entry limit (20000).");
|
|
117802
|
-
const absolute =
|
|
118482
|
+
const absolute = join70(root2, path), stat3 = lstatSync39(absolute);
|
|
117803
118483
|
if (stat3.isSymbolicLink())
|
|
117804
118484
|
throw new Error(`Skill contains a symlink: ${path}. No files were changed.`);
|
|
117805
118485
|
if (stat3.isDirectory() && name === "node_modules")
|
|
@@ -117812,7 +118492,7 @@ function snapshot2(root2) {
|
|
|
117812
118492
|
bytes += stat3.size;
|
|
117813
118493
|
if (bytes > 50000000)
|
|
117814
118494
|
throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
|
|
117815
|
-
const content =
|
|
118495
|
+
const content = readFileSync48(absolute);
|
|
117816
118496
|
if (content.length !== stat3.size)
|
|
117817
118497
|
throw new Error("Skill changed during preparation.");
|
|
117818
118498
|
files.push({ path, bytes: content, mode: stat3.mode & 511 });
|
|
@@ -118215,7 +118895,7 @@ var init_schedule = __esm(() => {
|
|
|
118215
118895
|
|
|
118216
118896
|
// src/lib/registry-sync.ts
|
|
118217
118897
|
import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync32 } from "fs";
|
|
118218
|
-
import { dirname as
|
|
118898
|
+
import { dirname as dirname42, relative as relative14 } from "path";
|
|
118219
118899
|
function createRegistrySyncArtifact(options = {}) {
|
|
118220
118900
|
const profile = options.profile ?? "all";
|
|
118221
118901
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -118227,7 +118907,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
118227
118907
|
const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
118228
118908
|
const skills = registry2.map((skill) => {
|
|
118229
118909
|
const skillPath = getSkillPath(skill.name);
|
|
118230
|
-
const directory2 =
|
|
118910
|
+
const directory2 = relative14(process.cwd(), skillPath) || skillPath;
|
|
118231
118911
|
const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
|
|
118232
118912
|
const docs = includeDocs ? buildDocs(skill.name) : undefined;
|
|
118233
118913
|
return {
|
|
@@ -118272,7 +118952,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
118272
118952
|
};
|
|
118273
118953
|
}
|
|
118274
118954
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
118275
|
-
mkdirSync31(
|
|
118955
|
+
mkdirSync31(dirname42(path), { recursive: true });
|
|
118276
118956
|
writeFileSync32(path, `${JSON.stringify(artifact, null, 2)}
|
|
118277
118957
|
`);
|
|
118278
118958
|
}
|
|
@@ -118292,8 +118972,8 @@ var init_registry_sync = __esm(() => {
|
|
|
118292
118972
|
});
|
|
118293
118973
|
|
|
118294
118974
|
// src/lib/review-export.ts
|
|
118295
|
-
import { constants as constants25, closeSync as closeSync27, existsSync as existsSync38, fstatSync as fstatSync27, fsyncSync as fsyncSync9, lstatSync as
|
|
118296
|
-
import { basename as
|
|
118975
|
+
import { constants as constants25, closeSync as closeSync27, existsSync as existsSync38, fstatSync as fstatSync27, fsyncSync as fsyncSync9, lstatSync as lstatSync40, openSync as openSync27, realpathSync as realpathSync23, statSync as statSync24, writeFileSync as writeFileSync33 } from "fs";
|
|
118976
|
+
import { basename as basename19, dirname as dirname43, isAbsolute as isAbsolute38, join as join71, parse as parse13, resolve as resolve36, sep as sep19 } from "path";
|
|
118297
118977
|
function exactVersion(spec) {
|
|
118298
118978
|
const at2 = spec.indexOf("@");
|
|
118299
118979
|
const slug2 = spec.slice(0, at2);
|
|
@@ -118304,23 +118984,23 @@ function exactVersion(spec) {
|
|
|
118304
118984
|
return { slug: slug2, version: version2 };
|
|
118305
118985
|
}
|
|
118306
118986
|
function assertPrivateDestination(output2) {
|
|
118307
|
-
if (!
|
|
118987
|
+
if (!isAbsolute38(output2) || resolve36(output2) !== output2 || !basename19(output2).endsWith(".tar.gz")) {
|
|
118308
118988
|
throw new Error("Review output must be an absolute, normalized .tar.gz path.");
|
|
118309
118989
|
}
|
|
118310
|
-
const parent =
|
|
118311
|
-
if (
|
|
118990
|
+
const parent = dirname43(output2);
|
|
118991
|
+
if (realpathSync23(parent) !== parent || lstatSync40(parent).isSymbolicLink()) {
|
|
118312
118992
|
throw new Error("Review output parent must be a real directory, not a symlink.");
|
|
118313
118993
|
}
|
|
118314
118994
|
const parentStat = statSync24(parent);
|
|
118315
118995
|
if (!parentStat.isDirectory() || (parentStat.mode & 63) !== 0 || process.getuid && parentStat.uid !== process.getuid()) {
|
|
118316
118996
|
throw new Error("Review output parent must be an owner-only directory (mode 0700).");
|
|
118317
118997
|
}
|
|
118318
|
-
const segments = parent.toLowerCase().split(
|
|
118998
|
+
const segments = parent.toLowerCase().split(sep19);
|
|
118319
118999
|
if (segments.some((segment) => NATIVE_CONTAINERS.has(segment)) || segments.some((segment, index) => segment === ".config" && segments[index + 1] === "opencode") || segments.some((segment, index) => segment === ".hasna" && segments[index + 1] === "skills")) {
|
|
118320
119000
|
throw new Error("Review output cannot be placed in agent-native discovery or Skills installation paths.");
|
|
118321
119001
|
}
|
|
118322
|
-
for (let current = parent;; current =
|
|
118323
|
-
if (existsSync38(
|
|
119002
|
+
for (let current = parent;; current = dirname43(current)) {
|
|
119003
|
+
if (existsSync38(join71(current, ".git"))) {
|
|
118324
119004
|
throw new Error("Review output cannot be placed inside a Git worktree.");
|
|
118325
119005
|
}
|
|
118326
119006
|
if (current === parse13(current).root)
|
|
@@ -118396,12 +119076,12 @@ function registerRegistry(parent) {
|
|
|
118396
119076
|
async function writeJson2(value, space) {
|
|
118397
119077
|
const text4 = `${JSON.stringify(value, null, space)}
|
|
118398
119078
|
`;
|
|
118399
|
-
await new Promise((
|
|
119079
|
+
await new Promise((resolve37, reject2) => {
|
|
118400
119080
|
process.stdout.write(text4, (error61) => {
|
|
118401
119081
|
if (error61)
|
|
118402
119082
|
reject2(error61);
|
|
118403
119083
|
else
|
|
118404
|
-
|
|
119084
|
+
resolve37();
|
|
118405
119085
|
});
|
|
118406
119086
|
});
|
|
118407
119087
|
}
|
|
@@ -118605,9 +119285,9 @@ __export(exports_publish, {
|
|
|
118605
119285
|
PushSkillError: () => PushSkillError
|
|
118606
119286
|
});
|
|
118607
119287
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
118608
|
-
import { existsSync as existsSync39, readFileSync as
|
|
119288
|
+
import { existsSync as existsSync39, readFileSync as readFileSync49 } from "fs";
|
|
118609
119289
|
import { hostname as hostname5 } from "os";
|
|
118610
|
-
import { join as
|
|
119290
|
+
import { join as join72 } from "path";
|
|
118611
119291
|
function registerPublish(parent) {
|
|
118612
119292
|
parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--force-new-version", "If name@version already exists with different content, publish as the next patch version", false).option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
|
|
118613
119293
|
try {
|
|
@@ -118659,8 +119339,8 @@ async function pushSkill(name, options = {}) {
|
|
|
118659
119339
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
118660
119340
|
await verifyPackedCanonicalHash(skill.name, packed);
|
|
118661
119341
|
const versionManifest = buildVersionManifest(skill.path, packed);
|
|
118662
|
-
const skillMdPath =
|
|
118663
|
-
const skillMd = existsSync39(skillMdPath) ?
|
|
119342
|
+
const skillMdPath = join72(skill.path, "SKILL.md");
|
|
119343
|
+
const skillMd = existsSync39(skillMdPath) ? readFileSync49(skillMdPath, "utf-8") : undefined;
|
|
118664
119344
|
const base3 = {
|
|
118665
119345
|
slug: skill.name,
|
|
118666
119346
|
path: skill.path,
|
|
@@ -118898,7 +119578,7 @@ var exports_private_publications = {};
|
|
|
118898
119578
|
__export(exports_private_publications, {
|
|
118899
119579
|
registerPrivatePublications: () => registerPrivatePublications
|
|
118900
119580
|
});
|
|
118901
|
-
import { resolve as
|
|
119581
|
+
import { resolve as resolve37 } from "path";
|
|
118902
119582
|
function verifyOptions(options, action) {
|
|
118903
119583
|
if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
|
|
118904
119584
|
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
|
|
@@ -118943,9 +119623,9 @@ function registerPrivatePublications(parent) {
|
|
|
118943
119623
|
if (code2 === null)
|
|
118944
119624
|
return;
|
|
118945
119625
|
const context2 = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
|
|
118946
|
-
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 =
|
|
119626
|
+
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 = resolve37(options.recoveryDir);
|
|
118947
119627
|
if (action === "publish")
|
|
118948
|
-
await preparePrivatePublication(client2,
|
|
119628
|
+
await preparePrivatePublication(client2, resolve37(args2[0]), directory2, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
|
|
118949
119629
|
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client2, directory2, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client2, directory2, action === "cancel");
|
|
118950
119630
|
print2(result2, options.json);
|
|
118951
119631
|
} catch (error61) {
|
|
@@ -119047,7 +119727,7 @@ __export(exports_auth, {
|
|
|
119047
119727
|
import { createInterface as createInterface3 } from "readline";
|
|
119048
119728
|
function prompt(question) {
|
|
119049
119729
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
119050
|
-
return new Promise((
|
|
119730
|
+
return new Promise((resolve38) => {
|
|
119051
119731
|
let settled = false;
|
|
119052
119732
|
const finish = (answer) => {
|
|
119053
119733
|
if (settled)
|
|
@@ -119056,7 +119736,7 @@ function prompt(question) {
|
|
|
119056
119736
|
rl.close();
|
|
119057
119737
|
if (answer === null)
|
|
119058
119738
|
process.exitCode = 130;
|
|
119059
|
-
|
|
119739
|
+
resolve38(answer);
|
|
119060
119740
|
};
|
|
119061
119741
|
rl.once("SIGINT", () => finish(null));
|
|
119062
119742
|
rl.once("close", () => finish(null));
|
|
@@ -119234,7 +119914,7 @@ Skipped. Run it any time: skills setup agents`));
|
|
|
119234
119914
|
function promptHidden(question) {
|
|
119235
119915
|
const stdin = process.stdin;
|
|
119236
119916
|
process.stdout.write(question);
|
|
119237
|
-
return new Promise((
|
|
119917
|
+
return new Promise((resolve38) => {
|
|
119238
119918
|
let value = "";
|
|
119239
119919
|
const done = (answer) => {
|
|
119240
119920
|
stdin.off("data", onData);
|
|
@@ -119244,7 +119924,7 @@ function promptHidden(question) {
|
|
|
119244
119924
|
`);
|
|
119245
119925
|
if (answer === null)
|
|
119246
119926
|
process.exitCode = 130;
|
|
119247
|
-
|
|
119927
|
+
resolve38(answer);
|
|
119248
119928
|
};
|
|
119249
119929
|
const onData = (chunk2) => {
|
|
119250
119930
|
for (const character of chunk2.toString()) {
|
|
@@ -119636,8 +120316,8 @@ function registerAccountCommands(parent) {
|
|
|
119636
120316
|
}
|
|
119637
120317
|
function registerAuth(parent) {
|
|
119638
120318
|
const auth = parent.command("auth").description("Manage account authentication");
|
|
119639
|
-
const
|
|
119640
|
-
|
|
120319
|
+
const keys4 = auth.command("keys").description("Manage API keys on the configured instance");
|
|
120320
|
+
keys4.command("list").option("--json", "Output as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").action(async (options) => {
|
|
119641
120321
|
try {
|
|
119642
120322
|
const target = await captureProfileWorkspace("List API keys");
|
|
119643
120323
|
target.unchanged();
|
|
@@ -119646,7 +120326,7 @@ function registerAuth(parent) {
|
|
|
119646
120326
|
writeCommandError(error61, "Failed to list API keys", options.json);
|
|
119647
120327
|
}
|
|
119648
120328
|
});
|
|
119649
|
-
|
|
120329
|
+
keys4.command("create").argument("<name>").option("--scope <scope>", "Limit key scope (repeatable)", (value, all) => [...all, value], []).option("--json", "Output the newly created key as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").description("Create a key; the returned secret is shown once and must be stored securely").action(async (name, options) => {
|
|
119650
120330
|
try {
|
|
119651
120331
|
const target = await captureProfileWorkspace("Create API key");
|
|
119652
120332
|
const client2 = new RemoteSkillsAuthClient(target.origin);
|
|
@@ -119657,7 +120337,7 @@ function registerAuth(parent) {
|
|
|
119657
120337
|
writeCommandError(error61, "Failed to create API key", options.json);
|
|
119658
120338
|
}
|
|
119659
120339
|
});
|
|
119660
|
-
|
|
120340
|
+
keys4.command("revoke").argument("<key-id>").option("--json", "Output as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").action(async (id3, options) => {
|
|
119661
120341
|
try {
|
|
119662
120342
|
const target = await captureProfileWorkspace("Revoke API key");
|
|
119663
120343
|
target.unchanged();
|
|
@@ -119666,7 +120346,7 @@ function registerAuth(parent) {
|
|
|
119666
120346
|
writeCommandError(error61, "Failed to revoke API key", options.json);
|
|
119667
120347
|
}
|
|
119668
120348
|
});
|
|
119669
|
-
|
|
120349
|
+
keys4.command("add-publish-scope").argument("<key-id>").requiredOption("--expected-scopes <csv>", "Current comma-separated scopes; stale values are refused").option("--json", "Output as JSON", false).description("Add skills:publish to one existing key without minting or rotating it").action(async (id3, options) => {
|
|
119670
120350
|
try {
|
|
119671
120351
|
const connection = await resolveSkillsConnection();
|
|
119672
120352
|
if (!connection)
|
|
@@ -119800,7 +120480,7 @@ function promptInvitationProof(kind) {
|
|
|
119800
120480
|
const label = kind === "token" ? "invitation token" : "recovery code";
|
|
119801
120481
|
const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
|
|
119802
120482
|
const { stdin, stderr: output2 } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
|
|
119803
|
-
return new Promise((
|
|
120483
|
+
return new Promise((resolve38) => {
|
|
119804
120484
|
let value = "", settled = false, overflow = false;
|
|
119805
120485
|
const finish = (answer) => {
|
|
119806
120486
|
if (settled)
|
|
@@ -119817,7 +120497,7 @@ function promptInvitationProof(kind) {
|
|
|
119817
120497
|
`);
|
|
119818
120498
|
if (answer === null)
|
|
119819
120499
|
process.exitCode = 130;
|
|
119820
|
-
|
|
120500
|
+
resolve38(answer);
|
|
119821
120501
|
};
|
|
119822
120502
|
const cancel = () => finish(null);
|
|
119823
120503
|
const keypress = (text4, key) => {
|
|
@@ -120389,8 +121069,8 @@ var init_storage2 = __esm(() => {
|
|
|
120389
121069
|
});
|
|
120390
121070
|
|
|
120391
121071
|
// src/lib/registry-reconcile.ts
|
|
120392
|
-
import { existsSync as existsSync40, readFileSync as
|
|
120393
|
-
import { join as
|
|
121072
|
+
import { existsSync as existsSync40, readFileSync as readFileSync50, statSync as statSync25, writeFileSync as writeFileSync34 } from "fs";
|
|
121073
|
+
import { join as join73 } from "path";
|
|
120394
121074
|
function isDirectory2(path) {
|
|
120395
121075
|
try {
|
|
120396
121076
|
return statSync25(path).isDirectory();
|
|
@@ -120401,15 +121081,15 @@ function isDirectory2(path) {
|
|
|
120401
121081
|
function migrationNeeded(options) {
|
|
120402
121082
|
if (options.rootDir)
|
|
120403
121083
|
return false;
|
|
120404
|
-
const appDir = options.homeDir ?
|
|
120405
|
-
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(
|
|
121084
|
+
const appDir = options.homeDir ? join73(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
121085
|
+
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join73(appDir, SKILLS_CACHE_DIRNAME)));
|
|
120406
121086
|
}
|
|
120407
121087
|
function readBaseline(skillDir) {
|
|
120408
|
-
const markerPath =
|
|
121088
|
+
const markerPath = join73(skillDir, PULL_MARKER_FILE);
|
|
120409
121089
|
if (!existsSync40(markerPath))
|
|
120410
121090
|
return;
|
|
120411
121091
|
try {
|
|
120412
|
-
const marker = JSON.parse(
|
|
121092
|
+
const marker = JSON.parse(readFileSync50(markerPath, "utf-8"));
|
|
120413
121093
|
if (!isSkillsOwnershipMarker(marker))
|
|
120414
121094
|
return;
|
|
120415
121095
|
return {
|
|
@@ -120421,11 +121101,11 @@ function readBaseline(skillDir) {
|
|
|
120421
121101
|
}
|
|
120422
121102
|
}
|
|
120423
121103
|
function readCursor(root2) {
|
|
120424
|
-
const path =
|
|
121104
|
+
const path = join73(root2, SYNC_CURSOR_FILE);
|
|
120425
121105
|
if (!existsSync40(path))
|
|
120426
121106
|
return { runCount: 0 };
|
|
120427
121107
|
try {
|
|
120428
|
-
const cursor2 = JSON.parse(
|
|
121108
|
+
const cursor2 = JSON.parse(readFileSync50(path, "utf-8"));
|
|
120429
121109
|
return { runCount: typeof cursor2.runCount === "number" ? cursor2.runCount : 0 };
|
|
120430
121110
|
} catch {
|
|
120431
121111
|
return { runCount: 0 };
|
|
@@ -120434,12 +121114,12 @@ function readCursor(root2) {
|
|
|
120434
121114
|
function resolveCorpusRootReadOnly(options) {
|
|
120435
121115
|
if (options.rootDir)
|
|
120436
121116
|
return { root: options.rootDir, migrationPending: false };
|
|
120437
|
-
const appDir = options.homeDir ?
|
|
120438
|
-
const cache3 =
|
|
121117
|
+
const appDir = options.homeDir ? join73(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
121118
|
+
const cache3 = join73(appDir, SKILLS_CACHE_DIRNAME);
|
|
120439
121119
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
120440
121120
|
return { root: cache3, migrationPending: false };
|
|
120441
121121
|
}
|
|
120442
|
-
return { root:
|
|
121122
|
+
return { root: join73(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
120443
121123
|
}
|
|
120444
121124
|
function remoteRowToSkill(record12) {
|
|
120445
121125
|
const slug2 = typeof record12.slug === "string" ? record12.slug : typeof record12.name === "string" ? record12.name : undefined;
|
|
@@ -120573,7 +121253,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
120573
121253
|
for (const slug2 of allSlugs) {
|
|
120574
121254
|
const local = locals.get(slug2);
|
|
120575
121255
|
const remote = remotes.get(slug2);
|
|
120576
|
-
const baseline = local ? readBaseline(
|
|
121256
|
+
const baseline = local ? readBaseline(join73(root2, slug2)) : undefined;
|
|
120577
121257
|
const { state, reason: reason2 } = classifySkill(local, remote, baseline);
|
|
120578
121258
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
120579
121259
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -120632,7 +121312,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
120632
121312
|
try {
|
|
120633
121313
|
await pushSkill(slug2, { rootDir: root2, client: client2 });
|
|
120634
121314
|
const pushed = locals.get(slug2);
|
|
120635
|
-
writePullMarker(
|
|
121315
|
+
writePullMarker(join73(root2, slug2), {
|
|
120636
121316
|
skill: slug2,
|
|
120637
121317
|
...pushed?.version ? { version: pushed.version } : {},
|
|
120638
121318
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -120704,7 +121384,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
120704
121384
|
runCount: readCursor(root2).runCount + 1,
|
|
120705
121385
|
summary
|
|
120706
121386
|
};
|
|
120707
|
-
writeFileSync34(
|
|
121387
|
+
writeFileSync34(join73(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
|
|
120708
121388
|
`);
|
|
120709
121389
|
return {
|
|
120710
121390
|
corpusRoot: root2,
|