@delorenj/skillex 0.1.2 → 0.1.3
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/dist/cli.js +176 -84
- package/dist/core/profile-policy.d.ts +4 -0
- package/dist/core/profile-types.d.ts +1 -0
- package/dist/index.js +170 -82
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12018,10 +12018,94 @@ async function migrate(input = {}) {
|
|
|
12018
12018
|
|
|
12019
12019
|
// src/core/profile-sync.ts
|
|
12020
12020
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
12021
|
-
import { constants as
|
|
12022
|
-
import { mkdir as mkdir10, open as
|
|
12023
|
-
import { join as
|
|
12021
|
+
import { constants as constants11 } from "node:fs";
|
|
12022
|
+
import { mkdir as mkdir10, open as open14, readdir as readdir21, realpath as realpath22, rename as rename8, symlink as symlink6, unlink as unlink9 } from "node:fs/promises";
|
|
12023
|
+
import { join as join32, resolve as resolve17 } from "node:path";
|
|
12024
12024
|
import { isDeepStrictEqual as isDeepStrictEqual6 } from "node:util";
|
|
12025
|
+
|
|
12026
|
+
// src/core/profile-policy.ts
|
|
12027
|
+
import { constants as constants10 } from "node:fs";
|
|
12028
|
+
import { open as open13, readFile as readFile12 } from "node:fs/promises";
|
|
12029
|
+
import { join as join31 } from "node:path";
|
|
12030
|
+
import { parseDocument as parseDocument3 } from "yaml";
|
|
12031
|
+
var metadataFiles = /* @__PURE__ */ new Set([
|
|
12032
|
+
".usage.json",
|
|
12033
|
+
".usage.json.lock",
|
|
12034
|
+
".curator_state",
|
|
12035
|
+
".curator_suppressed",
|
|
12036
|
+
".sync_state"
|
|
12037
|
+
]);
|
|
12038
|
+
var metadataDirectories = /* @__PURE__ */ new Set([".curator_backups"]);
|
|
12039
|
+
function hermesMetadata(item2) {
|
|
12040
|
+
if (item2.kind === "file") return metadataFiles.has(item2.name);
|
|
12041
|
+
if (item2.kind === "directory") return metadataDirectories.has(item2.name);
|
|
12042
|
+
return false;
|
|
12043
|
+
}
|
|
12044
|
+
function refuse3(path, message) {
|
|
12045
|
+
return fail(
|
|
12046
|
+
"E_PROFILE_SKILLEX_ONLY",
|
|
12047
|
+
message,
|
|
12048
|
+
{
|
|
12049
|
+
path,
|
|
12050
|
+
fix: "Preserve foreign content outside discovery roots; clear skills.external_dirs through the locked config renderer, then preview skillex profile sync NAME --project PATH --skillex-only. Never delete or adopt local payloads implicitly."
|
|
12051
|
+
},
|
|
12052
|
+
ExitCode.REFUSED
|
|
12053
|
+
);
|
|
12054
|
+
}
|
|
12055
|
+
async function strictProfile(profile, options) {
|
|
12056
|
+
if (options.skillexOnly !== void 0 && typeof options.skillexOnly !== "boolean")
|
|
12057
|
+
refuse3(profile.root, "skillexOnly must be boolean.");
|
|
12058
|
+
const marker = await entry2(join31(profile.root, ".skillex-only"));
|
|
12059
|
+
if (marker && !marker.info.isFile())
|
|
12060
|
+
refuse3(marker.path, "The strict policy marker must be a regular file.");
|
|
12061
|
+
return options.skillexOnly === true || marker !== void 0;
|
|
12062
|
+
}
|
|
12063
|
+
async function assertStrictProfile(profile, options, local) {
|
|
12064
|
+
if (!await strictProfile(profile, options)) return false;
|
|
12065
|
+
for (const item2 of local)
|
|
12066
|
+
if (!hermesMetadata(item2))
|
|
12067
|
+
refuse3(item2.path, `Skillex-only profile contains unowned entry ${item2.name}.`);
|
|
12068
|
+
const path = join31(profile.root, "config.yaml");
|
|
12069
|
+
const config = await entry2(path);
|
|
12070
|
+
if (config) {
|
|
12071
|
+
if (!config.info.isFile())
|
|
12072
|
+
refuse3(path, "Strict profile config must be a regular generated file.");
|
|
12073
|
+
const document = parseDocument3(await readFile12(path, "utf8"), { uniqueKeys: true });
|
|
12074
|
+
if (document.errors.length) refuse3(path, "Cannot validate strict skills configuration.");
|
|
12075
|
+
const dirs = document.toJS()?.skills?.external_dirs;
|
|
12076
|
+
if (dirs !== void 0 && (!Array.isArray(dirs) || dirs.length))
|
|
12077
|
+
refuse3(path, "Skillex-only profiles require skills.external_dirs: [].");
|
|
12078
|
+
}
|
|
12079
|
+
const optOut = await entry2(join31(profile.root, ".no-bundled-skills"));
|
|
12080
|
+
if (optOut && !optOut.info.isFile())
|
|
12081
|
+
refuse3(optOut.path, "Bundled opt-out marker must be a regular file.");
|
|
12082
|
+
return true;
|
|
12083
|
+
}
|
|
12084
|
+
async function publishStrictPolicy(profile) {
|
|
12085
|
+
for (const name of [".no-bundled-skills", ".skillex-only"]) {
|
|
12086
|
+
const path = join31(profile.root, name);
|
|
12087
|
+
const existing = await entry2(path);
|
|
12088
|
+
if (existing) {
|
|
12089
|
+
if (!existing.info.isFile()) refuse3(path, "Policy markers must be regular files.");
|
|
12090
|
+
continue;
|
|
12091
|
+
}
|
|
12092
|
+
const handle = await open13(
|
|
12093
|
+
path,
|
|
12094
|
+
constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW,
|
|
12095
|
+
384
|
|
12096
|
+
);
|
|
12097
|
+
try {
|
|
12098
|
+
await handle.writeFile(
|
|
12099
|
+
"Skillex owns this PM skill projection. Use skillex profile sync; no local installs.\n"
|
|
12100
|
+
);
|
|
12101
|
+
await handle.sync();
|
|
12102
|
+
} finally {
|
|
12103
|
+
await handle.close();
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
}
|
|
12107
|
+
|
|
12108
|
+
// src/core/profile-sync.ts
|
|
12025
12109
|
function refused3(code, message, path, fix) {
|
|
12026
12110
|
fail(code, message, { path, fix }, ExitCode.REFUSED);
|
|
12027
12111
|
}
|
|
@@ -12040,14 +12124,14 @@ function receiptOptions(profile, options, scopes = []) {
|
|
|
12040
12124
|
const { home, cwd } = normalized;
|
|
12041
12125
|
const expanded = (value) => resolve17(
|
|
12042
12126
|
cwd,
|
|
12043
|
-
value === "~" ? home : value.startsWith("~/") ?
|
|
12127
|
+
value === "~" ? home : value.startsWith("~/") ? join32(home, value.slice(2)) : value
|
|
12044
12128
|
);
|
|
12045
12129
|
return {
|
|
12046
12130
|
...normalized,
|
|
12047
12131
|
forbiddenRoots: [
|
|
12048
12132
|
.../* @__PURE__ */ new Set([
|
|
12049
12133
|
profile.root,
|
|
12050
|
-
|
|
12134
|
+
join32(home, ".agents"),
|
|
12051
12135
|
...options.project ? [expanded(options.project)] : [],
|
|
12052
12136
|
...options.registryRoot ? [expanded(options.registryRoot)] : [],
|
|
12053
12137
|
...scopes.flatMap((scope) => [
|
|
@@ -12109,7 +12193,7 @@ async function resolveProjection(options) {
|
|
|
12109
12193
|
}
|
|
12110
12194
|
const locations = /* @__PURE__ */ new Map();
|
|
12111
12195
|
for (const scope of scopes) {
|
|
12112
|
-
locations.set(
|
|
12196
|
+
locations.set(join32(scope.registry.root, "all-skills"), "catalog");
|
|
12113
12197
|
if (scope.pack) locations.set(scope.pack.path, "pack");
|
|
12114
12198
|
}
|
|
12115
12199
|
const sources = [];
|
|
@@ -12136,7 +12220,7 @@ async function observations(profile, state) {
|
|
|
12136
12220
|
);
|
|
12137
12221
|
if (root)
|
|
12138
12222
|
for (const name of (await readdir21(profile.skillsRoot)).sort()) {
|
|
12139
|
-
const path =
|
|
12223
|
+
const path = join32(profile.skillsRoot, name);
|
|
12140
12224
|
const actual = await entry2(path);
|
|
12141
12225
|
if (!actual) continue;
|
|
12142
12226
|
entries.set(name, actual);
|
|
@@ -12188,7 +12272,7 @@ async function buildWork(profile, state, options) {
|
|
|
12188
12272
|
for (const scope of projection.scopes) {
|
|
12189
12273
|
for (const protectedRoot of [
|
|
12190
12274
|
scope.registry.root,
|
|
12191
|
-
...scope.scope === "project" ? [scope.root] : [
|
|
12275
|
+
...scope.scope === "project" ? [scope.root] : [join32(scope.root, ".agents")]
|
|
12192
12276
|
])
|
|
12193
12277
|
if (within2(profile.skillsRoot, protectedRoot) || within2(protectedRoot, profile.skillsRoot))
|
|
12194
12278
|
refused3(
|
|
@@ -12208,18 +12292,19 @@ async function buildWork(profile, state, options) {
|
|
|
12208
12292
|
);
|
|
12209
12293
|
state = await readProfileState(profile, receiptOptions(profile, options, projection.scopes));
|
|
12210
12294
|
const observed = await observations(profile, state);
|
|
12295
|
+
await assertStrictProfile(profile, options, observed.local);
|
|
12211
12296
|
const links = { ...state.data.links };
|
|
12212
12297
|
const changes = [];
|
|
12213
12298
|
if (!observed.root) changes.push({ action: "mkdir", path: profile.skillsRoot });
|
|
12214
12299
|
for (const [name, claim2] of Object.entries(links))
|
|
12215
12300
|
if (!owned2(observed.entries.get(name), claim2)) {
|
|
12216
12301
|
delete links[name];
|
|
12217
|
-
changes.push({ action: "release", name, path:
|
|
12302
|
+
changes.push({ action: "release", name, path: join32(profile.skillsRoot, name) });
|
|
12218
12303
|
}
|
|
12219
12304
|
const candidates = [];
|
|
12220
12305
|
const desiredNames = new Set(projection.desired.map((desired) => desired.name));
|
|
12221
12306
|
for (const desired of projection.desired) {
|
|
12222
|
-
const path =
|
|
12307
|
+
const path = join32(profile.skillsRoot, desired.name);
|
|
12223
12308
|
const actual = observed.entries.get(desired.name);
|
|
12224
12309
|
const recorded = claim(links, desired.name);
|
|
12225
12310
|
const state2 = actual ? recorded ? lexicalTarget(path, actual.raw ?? "") === desired.target ? "unchanged" : "update" : "shadowed" : "create";
|
|
@@ -12236,7 +12321,7 @@ async function buildWork(profile, state, options) {
|
|
|
12236
12321
|
}
|
|
12237
12322
|
for (const name of Object.keys(links).sort())
|
|
12238
12323
|
if (!desiredNames.has(name))
|
|
12239
|
-
changes.push({ action: "prune", name, path:
|
|
12324
|
+
changes.push({ action: "prune", name, path: join32(profile.skillsRoot, name) });
|
|
12240
12325
|
const next = {
|
|
12241
12326
|
version: 1,
|
|
12242
12327
|
root: observed.root ? identity(observed.root) : null,
|
|
@@ -12274,6 +12359,7 @@ async function inspectProfile(profile, options = {}) {
|
|
|
12274
12359
|
}
|
|
12275
12360
|
try {
|
|
12276
12361
|
preserved = (await observations(profile, state)).local;
|
|
12362
|
+
await assertStrictProfile(profile, options, preserved);
|
|
12277
12363
|
} catch (error) {
|
|
12278
12364
|
const failure6 = asFailure(error);
|
|
12279
12365
|
findings.push(...failure6.findings);
|
|
@@ -12325,9 +12411,9 @@ async function inspectProfile(profile, options = {}) {
|
|
|
12325
12411
|
}
|
|
12326
12412
|
async function flush3(path, expected) {
|
|
12327
12413
|
await requireIdentity(path, expected);
|
|
12328
|
-
const handle = await
|
|
12414
|
+
const handle = await open14(
|
|
12329
12415
|
path,
|
|
12330
|
-
|
|
12416
|
+
constants11.O_RDONLY | constants11.O_DIRECTORY | constants11.O_NOFOLLOW
|
|
12331
12417
|
);
|
|
12332
12418
|
try {
|
|
12333
12419
|
if (!matches2({ path, info: await handle.stat({ bigint: true }) }, expected))
|
|
@@ -12372,7 +12458,7 @@ async function recoverProfile(profile, state, options, mark, findings) {
|
|
|
12372
12458
|
if (!pending || !root) return state;
|
|
12373
12459
|
checkProfileSignal(options);
|
|
12374
12460
|
await rootGuard(profile, root);
|
|
12375
|
-
const path =
|
|
12461
|
+
const path = join32(profile.skillsRoot, pending.name);
|
|
12376
12462
|
let current = await entry2(path);
|
|
12377
12463
|
const parked = await entry2(pending.parked);
|
|
12378
12464
|
const staged = await entry2(pending.stage);
|
|
@@ -12498,8 +12584,8 @@ async function apply2(work, mark, begin) {
|
|
|
12498
12584
|
}
|
|
12499
12585
|
}
|
|
12500
12586
|
const id = randomUUID9();
|
|
12501
|
-
const stage2 =
|
|
12502
|
-
const parked =
|
|
12587
|
+
const stage2 = join32(profile.skillsRoot, `.skillex-tmp-profile-${id}-new`);
|
|
12588
|
+
const parked = join32(profile.skillsRoot, `.skillex-tmp-profile-${id}-old`);
|
|
12503
12589
|
await absent(stage2);
|
|
12504
12590
|
await absent(parked);
|
|
12505
12591
|
let next;
|
|
@@ -12644,7 +12730,7 @@ async function runProfileSync(name, options) {
|
|
|
12644
12730
|
{ findings, exit: readExit(findings, state.data.pending !== void 0) }
|
|
12645
12731
|
);
|
|
12646
12732
|
}
|
|
12647
|
-
if (work && !work.result.changes.length)
|
|
12733
|
+
if (work && !work.result.changes.length && !await strictProfile(profile, options))
|
|
12648
12734
|
return makeResult(
|
|
12649
12735
|
"profile sync",
|
|
12650
12736
|
{ ...work.result, project: work.projection.project, dryRun: false, applied },
|
|
@@ -12672,6 +12758,8 @@ async function runProfileSync(name, options) {
|
|
|
12672
12758
|
});
|
|
12673
12759
|
}
|
|
12674
12760
|
await assertProfileIdentity(selected);
|
|
12761
|
+
if (await assertStrictProfile(selected, options, (await observations(selected, state)).local))
|
|
12762
|
+
await publishStrictPolicy(selected);
|
|
12675
12763
|
const currentProfile = await discoverProfile(name, options);
|
|
12676
12764
|
if (currentProfile.root !== selected.root || currentProfile.skillsRoot !== selected.skillsRoot)
|
|
12677
12765
|
refused3(
|
|
@@ -12709,21 +12797,21 @@ async function runProfileSync(name, options) {
|
|
|
12709
12797
|
const latest = await readProfileState(profile, receiptOptions(profile, options));
|
|
12710
12798
|
const pending = latest.data.pending;
|
|
12711
12799
|
if (pending) {
|
|
12712
|
-
const current = await entry2(
|
|
12800
|
+
const current = await entry2(join32(profile.skillsRoot, pending.name));
|
|
12713
12801
|
if (pending.next && matches2(current, pending.next) && !applied.some((change2) => change2.path === current?.path))
|
|
12714
12802
|
applied.push({
|
|
12715
12803
|
action: pending.previous ? "update" : "create",
|
|
12716
12804
|
name: pending.name,
|
|
12717
|
-
path:
|
|
12805
|
+
path: join32(profile.skillsRoot, pending.name),
|
|
12718
12806
|
target: pending.next.raw
|
|
12719
12807
|
});
|
|
12720
12808
|
if (!pending.next && !current && pending.previous && matches2(await entry2(pending.parked), pending.previous) && !applied.some(
|
|
12721
|
-
(change2) => change2.path ===
|
|
12809
|
+
(change2) => change2.path === join32(profile.skillsRoot, pending.name)
|
|
12722
12810
|
))
|
|
12723
12811
|
applied.push({
|
|
12724
12812
|
action: "prune",
|
|
12725
12813
|
name: pending.name,
|
|
12726
|
-
path:
|
|
12814
|
+
path: join32(profile.skillsRoot, pending.name)
|
|
12727
12815
|
});
|
|
12728
12816
|
}
|
|
12729
12817
|
state = latest;
|
|
@@ -12865,13 +12953,13 @@ async function syncProfile(name, options) {
|
|
|
12865
12953
|
|
|
12866
12954
|
// src/core/selection-commands.ts
|
|
12867
12955
|
import { realpath as realpath24, stat as stat5 } from "node:fs/promises";
|
|
12868
|
-
import { dirname as dirname21, join as
|
|
12956
|
+
import { dirname as dirname21, join as join34, parse as parse4, resolve as resolve19 } from "node:path";
|
|
12869
12957
|
|
|
12870
12958
|
// src/core/selection-manifest.ts
|
|
12871
12959
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
12872
|
-
import { constants as
|
|
12873
|
-
import { link as link7, lstat as lstat19, mkdir as mkdir11, open as
|
|
12874
|
-
import { dirname as dirname20, join as
|
|
12960
|
+
import { constants as constants12 } from "node:fs";
|
|
12961
|
+
import { link as link7, lstat as lstat19, mkdir as mkdir11, open as open15, realpath as realpath23, rename as rename9, rmdir as rmdir6, unlink as unlink10 } from "node:fs/promises";
|
|
12962
|
+
import { dirname as dirname20, join as join33, resolve as resolve18 } from "node:path";
|
|
12875
12963
|
import { isDeepStrictEqual as isDeepStrictEqual7 } from "node:util";
|
|
12876
12964
|
var SelectionManifestWriteError = class extends SkillexError {
|
|
12877
12965
|
constructor(error, published) {
|
|
@@ -12963,13 +13051,13 @@ async function contextFor3(root) {
|
|
|
12963
13051
|
unsafe2(path, "A scope ancestor is missing or is not a real directory.");
|
|
12964
13052
|
parents2.set(path, info2);
|
|
12965
13053
|
}
|
|
12966
|
-
const agents =
|
|
13054
|
+
const agents = join33(canonical2, ".agents");
|
|
12967
13055
|
const info = await inspect5(agents);
|
|
12968
13056
|
if (info) {
|
|
12969
13057
|
if (!info.isDirectory()) unsafe2(agents, "The scope .agents path must be a real directory.");
|
|
12970
13058
|
parents2.set(agents, info);
|
|
12971
13059
|
}
|
|
12972
|
-
return { root: canonical2, agents, path:
|
|
13060
|
+
return { root: canonical2, agents, path: join33(agents, "skills.json"), parents: parents2 };
|
|
12973
13061
|
}
|
|
12974
13062
|
async function assertParents4(context2) {
|
|
12975
13063
|
for (const [path, expected] of context2.parents) {
|
|
@@ -12986,7 +13074,7 @@ async function readStored2(path) {
|
|
|
12986
13074
|
const first = await inspect5(path);
|
|
12987
13075
|
if (!first) return void 0;
|
|
12988
13076
|
if (!first.isFile()) unsafe2(path, "The selection declaration must be a real regular file.");
|
|
12989
|
-
const handle = await
|
|
13077
|
+
const handle = await open15(path, constants12.O_RDONLY | constants12.O_NOFOLLOW);
|
|
12990
13078
|
try {
|
|
12991
13079
|
const before = await handle.stat({ bigint: true });
|
|
12992
13080
|
if (!before.isFile() || !sameObject2(first, before))
|
|
@@ -13058,9 +13146,9 @@ async function assertPrevious2(context2, previous) {
|
|
|
13058
13146
|
return current;
|
|
13059
13147
|
}
|
|
13060
13148
|
async function flushDirectory2(path, identity4) {
|
|
13061
|
-
const handle = await
|
|
13149
|
+
const handle = await open15(
|
|
13062
13150
|
path,
|
|
13063
|
-
|
|
13151
|
+
constants12.O_RDONLY | constants12.O_DIRECTORY | constants12.O_NOFOLLOW
|
|
13064
13152
|
);
|
|
13065
13153
|
try {
|
|
13066
13154
|
if (!sameObject2(identity4, await handle.stat({ bigint: true })))
|
|
@@ -13128,11 +13216,11 @@ async function writeSelectionManifest(previous, raw) {
|
|
|
13128
13216
|
await flushDirectory2(context2.root, rootIdentity);
|
|
13129
13217
|
}
|
|
13130
13218
|
await assertPrevious2(context2, prior);
|
|
13131
|
-
const path =
|
|
13219
|
+
const path = join33(context2.agents, `.skillex-tmp-${randomUUID10()}`);
|
|
13132
13220
|
const mode = prior.file ? Number(prior.file.identity.mode & 0o7777n) : 420;
|
|
13133
|
-
const file = await
|
|
13221
|
+
const file = await open15(
|
|
13134
13222
|
path,
|
|
13135
|
-
|
|
13223
|
+
constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW,
|
|
13136
13224
|
mode
|
|
13137
13225
|
);
|
|
13138
13226
|
try {
|
|
@@ -13244,7 +13332,7 @@ async function directory(value, cwd, home) {
|
|
|
13244
13332
|
fail("E_PROJECT_ROOT", "A project or working directory must be a nonempty path.", {
|
|
13245
13333
|
fix: "Pass an existing directory as --project or cwd."
|
|
13246
13334
|
});
|
|
13247
|
-
const expanded = value === "~" ? home : value.startsWith("~/") ?
|
|
13335
|
+
const expanded = value === "~" ? home : value.startsWith("~/") ? join34(home, value.slice(2)) : value;
|
|
13248
13336
|
const path = resolve19(cwd, expanded);
|
|
13249
13337
|
const info = await inspectPath(path);
|
|
13250
13338
|
if (!info || !(await stat5(path)).isDirectory())
|
|
@@ -13257,7 +13345,7 @@ async function directory(value, cwd, home) {
|
|
|
13257
13345
|
async function nearestGitRoot(cwd, home) {
|
|
13258
13346
|
let current = cwd;
|
|
13259
13347
|
while (current !== home && current !== parse4(current).root) {
|
|
13260
|
-
if (await inspectPath(
|
|
13348
|
+
if (await inspectPath(join34(current, ".git"))) return current;
|
|
13261
13349
|
current = dirname21(current);
|
|
13262
13350
|
}
|
|
13263
13351
|
return void 0;
|
|
@@ -13424,7 +13512,7 @@ async function prepare3(options, edit) {
|
|
|
13424
13512
|
...options,
|
|
13425
13513
|
home: target2.home,
|
|
13426
13514
|
forbiddenRoots: [
|
|
13427
|
-
|
|
13515
|
+
join34(target2.root, ".agents"),
|
|
13428
13516
|
...target2.scope === "project" ? [target2.root] : []
|
|
13429
13517
|
]
|
|
13430
13518
|
});
|
|
@@ -13579,7 +13667,7 @@ async function setInheritance(enabled, options = {}) {
|
|
|
13579
13667
|
|
|
13580
13668
|
// src/core/sets.ts
|
|
13581
13669
|
import { readdir as readdir22 } from "node:fs/promises";
|
|
13582
|
-
import { join as
|
|
13670
|
+
import { join as join35 } from "node:path";
|
|
13583
13671
|
function validateName(name) {
|
|
13584
13672
|
parseManifest({ sets: [{ name }] }, "set name");
|
|
13585
13673
|
}
|
|
@@ -13596,7 +13684,7 @@ async function listSets(options = {}) {
|
|
|
13596
13684
|
const command = "set list";
|
|
13597
13685
|
try {
|
|
13598
13686
|
const registry = await discoverRegistry(options);
|
|
13599
|
-
const path =
|
|
13687
|
+
const path = join35(registry.root, "sets");
|
|
13600
13688
|
const sets = [];
|
|
13601
13689
|
const failures = [];
|
|
13602
13690
|
if (await inspectPath(path)) {
|
|
@@ -13609,7 +13697,7 @@ async function listSets(options = {}) {
|
|
|
13609
13697
|
try {
|
|
13610
13698
|
sets.push(await readSet(registry.root, entry3.name));
|
|
13611
13699
|
} catch (error) {
|
|
13612
|
-
failures.push(collectedError(error,
|
|
13700
|
+
failures.push(collectedError(error, join35(path, entry3.name)));
|
|
13613
13701
|
}
|
|
13614
13702
|
}
|
|
13615
13703
|
}
|
|
@@ -13631,7 +13719,7 @@ async function showSet(name, options = {}) {
|
|
|
13631
13719
|
async function prepareSet(name, names2, operation, options) {
|
|
13632
13720
|
validateName(name);
|
|
13633
13721
|
const registry = await discoverRegistry(options);
|
|
13634
|
-
const path =
|
|
13722
|
+
const path = join35(registry.root, "sets", name);
|
|
13635
13723
|
const exists = await inspectPath(path);
|
|
13636
13724
|
const previous = operation === "create" && !exists ? { kind: "set", name, path, skills: [] } : await readSet(registry.root, name);
|
|
13637
13725
|
const requested = await requestedSkills(registry.root, names2, path);
|
|
@@ -13647,10 +13735,10 @@ async function prepareSet(name, names2, operation, options) {
|
|
|
13647
13735
|
const plan2 = await newPlan(registry, composition2, options.dryRun === true);
|
|
13648
13736
|
await planDirectory(plan2, path);
|
|
13649
13737
|
for (const skill of previous.skills)
|
|
13650
|
-
await inspectCanonicalLink(plan2,
|
|
13738
|
+
await inspectCanonicalLink(plan2, join35(path, skill.name), skill.path);
|
|
13651
13739
|
for (const [member, target2] of requested) {
|
|
13652
|
-
if (operation === "add") await planLink(plan2,
|
|
13653
|
-
else if (operation === "remove") await planUnlink(plan2,
|
|
13740
|
+
if (operation === "add") await planLink(plan2, join35(path, member), target2);
|
|
13741
|
+
else if (operation === "remove") await planUnlink(plan2, join35(path, member), target2);
|
|
13654
13742
|
}
|
|
13655
13743
|
return plan2;
|
|
13656
13744
|
}
|
|
@@ -13666,7 +13754,7 @@ async function removeSetSkills(name, names2, options = {}) {
|
|
|
13666
13754
|
|
|
13667
13755
|
// src/core/vendor-inspect.ts
|
|
13668
13756
|
import { readdir as readdir23 } from "node:fs/promises";
|
|
13669
|
-
import { join as
|
|
13757
|
+
import { join as join36 } from "node:path";
|
|
13670
13758
|
|
|
13671
13759
|
// src/core/vendor-git.ts
|
|
13672
13760
|
import { execFile as execFile4 } from "node:child_process";
|
|
@@ -14028,19 +14116,19 @@ var Observation = class {
|
|
|
14028
14116
|
async function recordedInventory(manifest, observation, options) {
|
|
14029
14117
|
const recorded = /* @__PURE__ */ new Map();
|
|
14030
14118
|
const failed2 = /* @__PURE__ */ new Set();
|
|
14031
|
-
const catalog =
|
|
14119
|
+
const catalog = join36(manifest.registry.root, "all-skills");
|
|
14032
14120
|
for (const entry3 of (await readdir23(catalog, { withFileTypes: true })).sort(
|
|
14033
14121
|
(a, b) => a.name.localeCompare(b.name)
|
|
14034
14122
|
)) {
|
|
14035
14123
|
checkVendorSignal(options);
|
|
14036
14124
|
if (!isSkillName(entry3.name) || !entry3.isDirectory()) continue;
|
|
14037
|
-
const path =
|
|
14125
|
+
const path = join36(catalog, entry3.name);
|
|
14038
14126
|
try {
|
|
14039
14127
|
const receipt2 = await readVendorProvenance(path);
|
|
14040
14128
|
if (receipt2) recorded.set(entry3.name, receipt2);
|
|
14041
14129
|
} catch (error) {
|
|
14042
14130
|
failed2.add(entry3.name);
|
|
14043
|
-
observation.capture(error,
|
|
14131
|
+
observation.capture(error, join36(path, ".source.yaml"));
|
|
14044
14132
|
}
|
|
14045
14133
|
}
|
|
14046
14134
|
return { recorded, failed: failed2 };
|
|
@@ -14057,7 +14145,7 @@ function emptySkill(source, manifest, name, upstreamCommit) {
|
|
|
14057
14145
|
return {
|
|
14058
14146
|
name,
|
|
14059
14147
|
source: source.name,
|
|
14060
|
-
path:
|
|
14148
|
+
path: join36(manifest.registry.root, "all-skills", name),
|
|
14061
14149
|
state: "unknown",
|
|
14062
14150
|
recordedCommit: null,
|
|
14063
14151
|
upstreamCommit,
|
|
@@ -14438,9 +14526,9 @@ async function inspectVendorStatus(options = {}) {
|
|
|
14438
14526
|
|
|
14439
14527
|
// src/core/vendor-sync.ts
|
|
14440
14528
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
14441
|
-
import { chmod as chmod3, lstat as lstat20, mkdir as mkdir12, open as
|
|
14442
|
-
import { dirname as dirname22, join as
|
|
14443
|
-
function
|
|
14529
|
+
import { chmod as chmod3, lstat as lstat20, mkdir as mkdir12, open as open16, readdir as readdir24, rename as rename10 } from "node:fs/promises";
|
|
14530
|
+
import { dirname as dirname22, join as join37 } from "node:path";
|
|
14531
|
+
function refuse4(code, message, path, fix) {
|
|
14444
14532
|
fail(code, message, { path, fix }, ExitCode.REFUSED);
|
|
14445
14533
|
}
|
|
14446
14534
|
function attributed(provenance, source) {
|
|
@@ -14450,21 +14538,21 @@ function baseline(provenance) {
|
|
|
14450
14538
|
return provenance.digest !== null && (provenance.digestFormat === null || ["skillex-tree-v1", "skillex-tree-v1+symlinks"].includes(provenance.digestFormat));
|
|
14451
14539
|
}
|
|
14452
14540
|
async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
14453
|
-
const path =
|
|
14541
|
+
const path = join37(catalog, skill.name);
|
|
14454
14542
|
const info = await vendorLstat(path);
|
|
14455
14543
|
let before = null;
|
|
14456
14544
|
let provenance = null;
|
|
14457
14545
|
let action = "create";
|
|
14458
14546
|
if (info) {
|
|
14459
14547
|
if (info.isSymbolicLink())
|
|
14460
|
-
|
|
14548
|
+
refuse4(
|
|
14461
14549
|
"E_VENDOR_DESTINATION_LINK",
|
|
14462
14550
|
"Vendor sync will not replace a linked canonical destination.",
|
|
14463
14551
|
path,
|
|
14464
14552
|
"Use the explicit migrate workflow to account for this link and its consumers before vendoring."
|
|
14465
14553
|
);
|
|
14466
14554
|
if (!info.isDirectory())
|
|
14467
|
-
|
|
14555
|
+
refuse4(
|
|
14468
14556
|
"E_VENDOR_DESTINATION_TYPE",
|
|
14469
14557
|
"The canonical destination is not a real skill directory.",
|
|
14470
14558
|
path,
|
|
@@ -14474,21 +14562,21 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14474
14562
|
const digest = await digestVendorTree(path);
|
|
14475
14563
|
if (provenance?.type === "vendored") {
|
|
14476
14564
|
if (!attributed(provenance, source))
|
|
14477
|
-
|
|
14565
|
+
refuse4(
|
|
14478
14566
|
"E_VENDOR_FOREIGN_SOURCE",
|
|
14479
14567
|
"The canonical skill is attributed to a different source.",
|
|
14480
14568
|
path,
|
|
14481
14569
|
"Correct the declaration or migrate the existing provenance explicitly; adopt does not transfer another vendor's ownership."
|
|
14482
14570
|
);
|
|
14483
14571
|
if (!baseline(provenance) && !options.discardLocalEdits)
|
|
14484
|
-
|
|
14572
|
+
refuse4(
|
|
14485
14573
|
"E_VENDOR_BASELINE_MISSING",
|
|
14486
14574
|
"The managed skill has no supported baseline digest proving unchanged content.",
|
|
14487
14575
|
path,
|
|
14488
14576
|
"Preserve or commit local content, then explicitly use --discard-local-edits to replace this attributed skill; it cannot be implicitly pruned."
|
|
14489
14577
|
);
|
|
14490
14578
|
if (baseline(provenance) && (digest !== provenance.digest || provenance.modifiedLocally) && !options.discardLocalEdits)
|
|
14491
|
-
|
|
14579
|
+
refuse4(
|
|
14492
14580
|
"E_VENDOR_LOCAL_EDITS",
|
|
14493
14581
|
"The vendored skill has local content or executable-mode edits.",
|
|
14494
14582
|
path,
|
|
@@ -14497,14 +14585,14 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14497
14585
|
action = digest === skill.digest && !provenance.modifiedLocally && provenance.digest === skill.digest && provenance.upstreamCommit === commit && provenance.upstreamTree === skill.tree && provenance.upstreamPath === skill.upstreamPath && provenance.upstreamVersion === source.version && provenance.extractedAt !== null && baseline(provenance) ? "unchanged" : "update";
|
|
14498
14586
|
} else {
|
|
14499
14587
|
if (!options.adopt)
|
|
14500
|
-
|
|
14588
|
+
refuse4(
|
|
14501
14589
|
"E_VENDOR_UNMANAGED",
|
|
14502
14590
|
"The canonical skill is not managed by this vendor source.",
|
|
14503
14591
|
path,
|
|
14504
14592
|
"Use --adopt to record ownership; if bytes or modes differ from upstream, also use --discard-local-edits after preserving them."
|
|
14505
14593
|
);
|
|
14506
14594
|
if (digest !== skill.digest && !options.discardLocalEdits)
|
|
14507
|
-
|
|
14595
|
+
refuse4(
|
|
14508
14596
|
"E_VENDOR_LOCAL_EDITS",
|
|
14509
14597
|
"Adopting this skill would replace local bytes or executable modes.",
|
|
14510
14598
|
path,
|
|
@@ -14514,14 +14602,14 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14514
14602
|
}
|
|
14515
14603
|
before = await captureVendorTree(path);
|
|
14516
14604
|
if (before.root.dev !== String((await lstat20(catalog, { bigint: true })).dev))
|
|
14517
|
-
|
|
14605
|
+
refuse4(
|
|
14518
14606
|
"E_VENDOR_CROSS_DEVICE",
|
|
14519
14607
|
"The existing skill is mounted on a different filesystem from catalog staging.",
|
|
14520
14608
|
path,
|
|
14521
14609
|
"Move the skill into the catalog filesystem before updating it; vendor replacement requires same-filesystem renames."
|
|
14522
14610
|
);
|
|
14523
14611
|
if (before.root.dev !== String(info.dev) || before.root.ino !== String(info.ino))
|
|
14524
|
-
|
|
14612
|
+
refuse4(
|
|
14525
14613
|
"E_VENDOR_CHANGED",
|
|
14526
14614
|
"The destination changed during preflight.",
|
|
14527
14615
|
path,
|
|
@@ -14560,7 +14648,7 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14560
14648
|
async function prepare4(options, extractedAt) {
|
|
14561
14649
|
vendorInterrupted(options);
|
|
14562
14650
|
const manifest = await readVendorSources(options);
|
|
14563
|
-
const catalog =
|
|
14651
|
+
const catalog = join37(manifest.registry.root, "all-skills");
|
|
14564
14652
|
const journal = await readVendorJournal(manifest.registry, options);
|
|
14565
14653
|
const selected = selectVendorSources(manifest, options.sources);
|
|
14566
14654
|
const sources = [];
|
|
@@ -14572,7 +14660,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14572
14660
|
const checkout = await resolveVendorCheckout(source, options);
|
|
14573
14661
|
if (!checkout.root) {
|
|
14574
14662
|
if (!source.optional)
|
|
14575
|
-
|
|
14663
|
+
refuse4(
|
|
14576
14664
|
"E_SOURCE_CHECKOUT_MISSING",
|
|
14577
14665
|
`Local checkout ${source.checkout} is unavailable.`,
|
|
14578
14666
|
manifest.path,
|
|
@@ -14625,7 +14713,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14625
14713
|
});
|
|
14626
14714
|
for (const skill of upstream.skills) {
|
|
14627
14715
|
if (names2.has(skill.name))
|
|
14628
|
-
|
|
14716
|
+
refuse4(
|
|
14629
14717
|
"E_VENDOR_DUPLICATE_SKILL",
|
|
14630
14718
|
`Sources ${names2.get(skill.name)} and ${source.name} select the same canonical skill.`,
|
|
14631
14719
|
manifest.path,
|
|
@@ -14638,7 +14726,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14638
14726
|
if (options.prune) {
|
|
14639
14727
|
for (const name of (await readdir24(catalog)).filter(isSkillName).sort()) {
|
|
14640
14728
|
if (names2.has(name)) continue;
|
|
14641
|
-
const path =
|
|
14729
|
+
const path = join37(catalog, name);
|
|
14642
14730
|
if (!(await vendorLstat(path))?.isDirectory()) continue;
|
|
14643
14731
|
let provenance;
|
|
14644
14732
|
try {
|
|
@@ -14658,14 +14746,14 @@ async function prepare4(options, extractedAt) {
|
|
|
14658
14746
|
const source = provenance?.source ? enumerated.get(provenance.source) : void 0;
|
|
14659
14747
|
if (!provenance || !source || !attributed(provenance, source)) continue;
|
|
14660
14748
|
if (!baseline(provenance))
|
|
14661
|
-
|
|
14749
|
+
refuse4(
|
|
14662
14750
|
"E_VENDOR_BASELINE_MISSING",
|
|
14663
14751
|
"An orphaned managed skill has no supported baseline proving it is safe to prune.",
|
|
14664
14752
|
path,
|
|
14665
14753
|
"Restore its verified provenance or remove it explicitly after inspection; --discard-local-edits never authorizes pruning unknown content."
|
|
14666
14754
|
);
|
|
14667
14755
|
if (provenance.modifiedLocally || await digestVendorTree(path) !== provenance.digest)
|
|
14668
|
-
|
|
14756
|
+
refuse4(
|
|
14669
14757
|
"E_VENDOR_LOCAL_EDITS",
|
|
14670
14758
|
"An orphaned managed skill has local edits and cannot be pruned.",
|
|
14671
14759
|
path,
|
|
@@ -14699,7 +14787,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14699
14787
|
}
|
|
14700
14788
|
async function absent2(path) {
|
|
14701
14789
|
if (await vendorLstat(path))
|
|
14702
|
-
|
|
14790
|
+
refuse4(
|
|
14703
14791
|
"E_VENDOR_CHANGED",
|
|
14704
14792
|
"A destination or recovery path appeared after preflight.",
|
|
14705
14793
|
path,
|
|
@@ -14711,7 +14799,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14711
14799
|
const selected = selectVendorSources(current, options.sources);
|
|
14712
14800
|
const expected = selectVendorSources(prepared.manifest, options.sources);
|
|
14713
14801
|
if (current.registry.root !== prepared.manifest.registry.root || JSON.stringify(selected) !== JSON.stringify(expected))
|
|
14714
|
-
|
|
14802
|
+
refuse4(
|
|
14715
14803
|
"E_VENDOR_INTENT_CHANGED",
|
|
14716
14804
|
"The selected vendor declarations changed during staging.",
|
|
14717
14805
|
prepared.manifest.path,
|
|
@@ -14721,7 +14809,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14721
14809
|
const checkout = await resolveVendorCheckout(source, options);
|
|
14722
14810
|
const previous = prepared.result.sources.find((entry3) => entry3.name === source.name);
|
|
14723
14811
|
if (!previous || checkout.root !== previous.checkout)
|
|
14724
|
-
|
|
14812
|
+
refuse4(
|
|
14725
14813
|
"E_VENDOR_INTENT_CHANGED",
|
|
14726
14814
|
"A selected local checkout mapping changed during staging.",
|
|
14727
14815
|
prepared.manifest.path,
|
|
@@ -14730,7 +14818,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14730
14818
|
}
|
|
14731
14819
|
}
|
|
14732
14820
|
async function syncDirectory2(path) {
|
|
14733
|
-
const handle = await
|
|
14821
|
+
const handle = await open16(path, "r");
|
|
14734
14822
|
try {
|
|
14735
14823
|
await handle.sync();
|
|
14736
14824
|
} finally {
|
|
@@ -14745,12 +14833,12 @@ async function stage(plan2, path, catalog, options) {
|
|
|
14745
14833
|
for (const entry3 of plan2.entries) {
|
|
14746
14834
|
vendorInterrupted(options);
|
|
14747
14835
|
await assertVendorIdentity(path, root);
|
|
14748
|
-
const target2 =
|
|
14836
|
+
const target2 = join37(path, entry3.path);
|
|
14749
14837
|
let parent = dirname22(entry3.path);
|
|
14750
14838
|
while (parent !== ".") {
|
|
14751
14839
|
const identity4 = parents2.get(parent);
|
|
14752
14840
|
if (!identity4) throw new Error(`Unprepared vendor content parent: ${parent}`);
|
|
14753
|
-
await assertVendorIdentity(
|
|
14841
|
+
await assertVendorIdentity(join37(path, parent), identity4);
|
|
14754
14842
|
parent = dirname22(parent);
|
|
14755
14843
|
}
|
|
14756
14844
|
if (entry3.kind === "directory") {
|
|
@@ -14758,7 +14846,7 @@ async function stage(plan2, path, catalog, options) {
|
|
|
14758
14846
|
await chmod3(target2, 493);
|
|
14759
14847
|
parents2.set(entry3.path, vendorIdentity(await lstat20(target2, { bigint: true })));
|
|
14760
14848
|
} else if (entry3.kind === "file") {
|
|
14761
|
-
const handle = await
|
|
14849
|
+
const handle = await open16(target2, "wx", entry3.mode);
|
|
14762
14850
|
try {
|
|
14763
14851
|
await handle.writeFile(entry3.bytes);
|
|
14764
14852
|
await handle.chmod(entry3.mode);
|
|
@@ -14807,7 +14895,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14807
14895
|
if (published) {
|
|
14808
14896
|
await assertVendorTree(operation.change.path, operation.after);
|
|
14809
14897
|
if (stageInfo)
|
|
14810
|
-
|
|
14898
|
+
refuse4(
|
|
14811
14899
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14812
14900
|
"Both the staged and published paths exist for one prepared skill.",
|
|
14813
14901
|
operation.change.path,
|
|
@@ -14815,7 +14903,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14815
14903
|
);
|
|
14816
14904
|
} else if (destination) {
|
|
14817
14905
|
if (!operation.before)
|
|
14818
|
-
|
|
14906
|
+
refuse4(
|
|
14819
14907
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14820
14908
|
"A foreign destination blocks vendor recovery.",
|
|
14821
14909
|
operation.change.path,
|
|
@@ -14823,7 +14911,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14823
14911
|
);
|
|
14824
14912
|
await assertVendorTree(operation.change.path, operation.before);
|
|
14825
14913
|
if (parked)
|
|
14826
|
-
|
|
14914
|
+
refuse4(
|
|
14827
14915
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14828
14916
|
"Both the old destination and its parked path exist.",
|
|
14829
14917
|
operation.change.path,
|
|
@@ -14831,7 +14919,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14831
14919
|
);
|
|
14832
14920
|
} else if (!parked && operation.before) {
|
|
14833
14921
|
if (operation.change.action !== "prune")
|
|
14834
|
-
|
|
14922
|
+
refuse4(
|
|
14835
14923
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14836
14924
|
"The old and new skill directories are both missing.",
|
|
14837
14925
|
operation.change.path,
|
|
@@ -14904,7 +14992,7 @@ async function syncVendorSources(options = {}) {
|
|
|
14904
14992
|
initiallyPending = initialJournal.document !== null;
|
|
14905
14993
|
fallback = {
|
|
14906
14994
|
registry,
|
|
14907
|
-
manifest:
|
|
14995
|
+
manifest: join37(registry.root, "all-skills", "sources.toml"),
|
|
14908
14996
|
dryRun: options.dryRun === true,
|
|
14909
14997
|
sources: [],
|
|
14910
14998
|
changes: [],
|
|
@@ -14951,7 +15039,7 @@ async function syncVendorSources(options = {}) {
|
|
|
14951
15039
|
}
|
|
14952
15040
|
prepared = await prepare4(options, extractedAt);
|
|
14953
15041
|
if (prepared.manifest.registry.root !== initialRoot)
|
|
14954
|
-
|
|
15042
|
+
refuse4(
|
|
14955
15043
|
"E_CATALOG_CHANGED",
|
|
14956
15044
|
"Registry discovery changed while waiting for its catalog lock.",
|
|
14957
15045
|
initialRoot,
|
|
@@ -14973,8 +15061,8 @@ async function syncVendorSources(options = {}) {
|
|
|
14973
15061
|
change: plan2.change,
|
|
14974
15062
|
before: plan2.before,
|
|
14975
15063
|
after: null,
|
|
14976
|
-
stage: plan2.change.action === "prune" ? null :
|
|
14977
|
-
parked: plan2.before ?
|
|
15064
|
+
stage: plan2.change.action === "prune" ? null : join37(journal.catalog, `.skillex-tmp-vendor-${id}-new`),
|
|
15065
|
+
parked: plan2.before ? join37(journal.catalog, `.skillex-tmp-vendor-${id}-old`) : null
|
|
14978
15066
|
};
|
|
14979
15067
|
});
|
|
14980
15068
|
vendorInterrupted(options);
|