@delorenj/skillex 0.1.1 → 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 +427 -195
- package/dist/core/aliases.d.ts +1 -1
- package/dist/core/content.d.ts +7 -0
- package/dist/core/migration-registry-state.d.ts +20 -1
- package/dist/core/profile-policy.d.ts +4 -0
- package/dist/core/profile-types.d.ts +1 -0
- package/dist/index.js +421 -193
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -520,7 +520,8 @@ var PROJECT_CLI_ALIASES = Object.freeze([
|
|
|
520
520
|
".gemini/skills",
|
|
521
521
|
".copilot/skills",
|
|
522
522
|
".opencode/skills",
|
|
523
|
-
".kimi-code/skills"
|
|
523
|
+
".kimi-code/skills",
|
|
524
|
+
".zcode/skills"
|
|
524
525
|
]);
|
|
525
526
|
var NEVER_TOUCH = Object.freeze([".hermes", ".augment", ".cursor", ".crush"]);
|
|
526
527
|
function aliasPaths(base, scope) {
|
|
@@ -531,7 +532,7 @@ function aliasPaths(base, scope) {
|
|
|
531
532
|
}
|
|
532
533
|
|
|
533
534
|
// src/core/catalog.ts
|
|
534
|
-
import { readdir as
|
|
535
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
535
536
|
import { join as join7 } from "node:path";
|
|
536
537
|
|
|
537
538
|
// src/core/composition.ts
|
|
@@ -954,10 +955,12 @@ async function packInventory(registry, selection2) {
|
|
|
954
955
|
}
|
|
955
956
|
|
|
956
957
|
// src/core/discovery.ts
|
|
957
|
-
import {
|
|
958
|
+
import { execFile } from "node:child_process";
|
|
959
|
+
import { lstat as lstat3, readdir as readdir3, readFile as readFile3, realpath as realpath4, stat } from "node:fs/promises";
|
|
958
960
|
import { homedir as homedir2 } from "node:os";
|
|
959
961
|
import { dirname as dirname2, join as join5, parse, resolve as resolve3 } from "node:path";
|
|
960
962
|
import { fileURLToPath } from "node:url";
|
|
963
|
+
import { promisify } from "node:util";
|
|
961
964
|
var manifestParts = [".agents", "skills.json"];
|
|
962
965
|
function errorMessage(error) {
|
|
963
966
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -1107,7 +1110,70 @@ async function installedPackageRoot() {
|
|
|
1107
1110
|
current = parent;
|
|
1108
1111
|
}
|
|
1109
1112
|
}
|
|
1110
|
-
|
|
1113
|
+
var execute = promisify(execFile);
|
|
1114
|
+
async function recordedCatalog(root) {
|
|
1115
|
+
const env = {};
|
|
1116
|
+
for (const [key2, value] of Object.entries(process.env))
|
|
1117
|
+
if (!key2.startsWith("GIT_")) env[key2] = value;
|
|
1118
|
+
let stdout;
|
|
1119
|
+
try {
|
|
1120
|
+
({ stdout } = await execute(
|
|
1121
|
+
"git",
|
|
1122
|
+
["-C", root, "ls-files", "--stage", "--full-name", "-z", "--", "all-skills"],
|
|
1123
|
+
{
|
|
1124
|
+
encoding: "utf8",
|
|
1125
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
1126
|
+
env: {
|
|
1127
|
+
...env,
|
|
1128
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
1129
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1130
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
1131
|
+
GIT_CONFIG_GLOBAL: "/dev/null"
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
));
|
|
1135
|
+
} catch {
|
|
1136
|
+
return void 0;
|
|
1137
|
+
}
|
|
1138
|
+
const records = stdout.split("\0").filter(Boolean);
|
|
1139
|
+
if (records.some((record9) => /^160000 [0-9a-f]+ \d\tall-skills$/.test(record9))) return "gitlink";
|
|
1140
|
+
if (records.some((record9) => /^\d{6} [0-9a-f]+ \d\tall-skills\//.test(record9))) return "tree";
|
|
1141
|
+
return void 0;
|
|
1142
|
+
}
|
|
1143
|
+
async function holdsDefinition(catalog, names2) {
|
|
1144
|
+
for (const name of names2) {
|
|
1145
|
+
try {
|
|
1146
|
+
if ((await stat(join5(catalog, name, "SKILL.md"))).isFile()) return true;
|
|
1147
|
+
} catch {
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return false;
|
|
1151
|
+
}
|
|
1152
|
+
async function uninitializedCatalog(root, catalog) {
|
|
1153
|
+
let declaration;
|
|
1154
|
+
try {
|
|
1155
|
+
declaration = await readFile3(join5(root, ".gitmodules"), "utf8");
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) {
|
|
1158
|
+
return void 0;
|
|
1159
|
+
}
|
|
1160
|
+
return ioFailure2(join5(root, ".gitmodules"), error);
|
|
1161
|
+
}
|
|
1162
|
+
if (!/^[\t ]*path[\t ]*=[\t ]*"?all-skills\/?"?[\t ]*$/m.test(declaration)) return void 0;
|
|
1163
|
+
if (await entry(join5(catalog, ".git"))) return void 0;
|
|
1164
|
+
let names2;
|
|
1165
|
+
try {
|
|
1166
|
+
names2 = (await readdir3(catalog)).sort();
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
return ioFailure2(catalog, error);
|
|
1169
|
+
}
|
|
1170
|
+
if (!names2.length) return names2;
|
|
1171
|
+
const recorded = await recordedCatalog(root);
|
|
1172
|
+
if (recorded === "gitlink") return names2;
|
|
1173
|
+
if (recorded === "tree") return void 0;
|
|
1174
|
+
return await holdsDefinition(catalog, names2) ? void 0 : names2;
|
|
1175
|
+
}
|
|
1176
|
+
async function catalogRoot(path, required, source) {
|
|
1111
1177
|
const found = await entry(path);
|
|
1112
1178
|
if (!found) {
|
|
1113
1179
|
if (!required) return void 0;
|
|
@@ -1138,6 +1204,21 @@ async function catalogRoot(path, required) {
|
|
|
1138
1204
|
fix: "Select the owning catalog checkout, or run skillex migrate to repair its topology."
|
|
1139
1205
|
});
|
|
1140
1206
|
}
|
|
1207
|
+
const leftovers = await uninitializedCatalog(root, catalog);
|
|
1208
|
+
if (leftovers) {
|
|
1209
|
+
const quoted = JSON.stringify(root);
|
|
1210
|
+
const more = leftovers.length > 8 ? `, and ${leftovers.length - 8} more` : "";
|
|
1211
|
+
const clear = leftovers.length ? `First move the entries git left in all-skills/ (${leftovers.slice(0, 8).join(", ")}${more}) somewhere outside it, such as a sibling directory, keeping anything you still need: git will not populate a non-empty directory. Then ` : "";
|
|
1212
|
+
const step = (text2) => clear ? `${clear}${text2}` : `${text2.charAt(0).toUpperCase()}${text2.slice(1)}`;
|
|
1213
|
+
fail(
|
|
1214
|
+
"E_REGISTRY_ROOT",
|
|
1215
|
+
`The registry's all-skills/ catalog is an uninitialized git submodule: ${catalog}`,
|
|
1216
|
+
{
|
|
1217
|
+
path: catalog,
|
|
1218
|
+
fix: source === "cache" ? `skillex never clones or fetches this registry cache. ${step(`bring it current and populate its catalog: git -C ${quoted} pull --ff-only && git -C ${quoted} submodule update --init --recursive.`)} Or drop the manifest's registry field, or pin a complete checkout with PJ_SKILLS_REGISTRY_ROOT.` : `${step(`populate the catalog submodule: git -C ${quoted} submodule update --init --recursive.`)} Or select a complete checkout with --registry-root or PJ_SKILLS_REGISTRY_ROOT.`
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1141
1222
|
return root;
|
|
1142
1223
|
}
|
|
1143
1224
|
async function discoverRegistry(options = {}) {
|
|
@@ -1149,7 +1230,7 @@ async function discoverRegistry(options = {}) {
|
|
|
1149
1230
|
const path = absolutePath(value, cwd, home, "E_REGISTRY_ROOT", "registry root");
|
|
1150
1231
|
if (searched.includes(path)) return void 0;
|
|
1151
1232
|
searched.push(path);
|
|
1152
|
-
const root = await catalogRoot(path, required);
|
|
1233
|
+
const root = await catalogRoot(path, required, source);
|
|
1153
1234
|
return root ? { root, source, searched: [...searched] } : void 0;
|
|
1154
1235
|
};
|
|
1155
1236
|
if (options.registryRoot !== void 0) {
|
|
@@ -1195,7 +1276,7 @@ async function discoverRegistry(options = {}) {
|
|
|
1195
1276
|
}
|
|
1196
1277
|
|
|
1197
1278
|
// src/core/metadata.ts
|
|
1198
|
-
import { readFile as
|
|
1279
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1199
1280
|
import { join as join6 } from "node:path";
|
|
1200
1281
|
import { parseDocument } from "yaml";
|
|
1201
1282
|
function mapping(value) {
|
|
@@ -1238,7 +1319,7 @@ async function textFile(path, missing) {
|
|
|
1238
1319
|
}
|
|
1239
1320
|
let bytes;
|
|
1240
1321
|
try {
|
|
1241
|
-
bytes = await
|
|
1322
|
+
bytes = await readFile4(path);
|
|
1242
1323
|
} catch {
|
|
1243
1324
|
fail(
|
|
1244
1325
|
"E_IO",
|
|
@@ -1443,7 +1524,7 @@ async function compositionReferences(registry, findings) {
|
|
|
1443
1524
|
await inspectPack(name, path);
|
|
1444
1525
|
inspected += 1;
|
|
1445
1526
|
}
|
|
1446
|
-
for (const entry3 of (await
|
|
1527
|
+
for (const entry3 of (await readdir4(path, { withFileTypes: true })).sort(
|
|
1447
1528
|
(a, b) => compare(a.name, b.name)
|
|
1448
1529
|
)) {
|
|
1449
1530
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_")) continue;
|
|
@@ -1488,7 +1569,7 @@ async function compositionReferences(registry, findings) {
|
|
|
1488
1569
|
try {
|
|
1489
1570
|
if (!await inspectPath(path)) continue;
|
|
1490
1571
|
await requireDirectory(path, kind2 === "set" ? "E_SET_MISSING" : "E_PACK_MISSING");
|
|
1491
|
-
const entries = (await
|
|
1572
|
+
const entries = (await readdir4(path, { withFileTypes: true })).sort(
|
|
1492
1573
|
(a, b) => compare(a.name, b.name)
|
|
1493
1574
|
);
|
|
1494
1575
|
for (const entry3 of entries) {
|
|
@@ -1526,7 +1607,7 @@ async function listSkills(options = {}) {
|
|
|
1526
1607
|
let validDefinitions = 0;
|
|
1527
1608
|
const catalog = join7(registry.root, "all-skills");
|
|
1528
1609
|
const query = options.query?.trim().toLowerCase();
|
|
1529
|
-
for (const entry3 of (await
|
|
1610
|
+
for (const entry3 of (await readdir4(catalog, { withFileTypes: true })).sort(
|
|
1530
1611
|
(a, b) => compare(a.name, b.name)
|
|
1531
1612
|
)) {
|
|
1532
1613
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_")) continue;
|
|
@@ -1575,7 +1656,7 @@ async function showSkill(name, options = {}) {
|
|
|
1575
1656
|
}
|
|
1576
1657
|
|
|
1577
1658
|
// src/core/catalog-write.ts
|
|
1578
|
-
import { chmod, lstat as lstat8, mkdir as mkdir4, open as open4, readFile as
|
|
1659
|
+
import { chmod, lstat as lstat8, mkdir as mkdir4, open as open4, readFile as readFile7, realpath as realpath9, symlink } from "node:fs/promises";
|
|
1579
1660
|
import { homedir as homedir5 } from "node:os";
|
|
1580
1661
|
import { dirname as dirname6, join as join12, resolve as resolve6 } from "node:path";
|
|
1581
1662
|
import { stringify } from "yaml";
|
|
@@ -1591,8 +1672,8 @@ import {
|
|
|
1591
1672
|
lstat as lstat4,
|
|
1592
1673
|
mkdir as mkdir2,
|
|
1593
1674
|
open as open2,
|
|
1594
|
-
readdir as
|
|
1595
|
-
readFile as
|
|
1675
|
+
readdir as readdir5,
|
|
1676
|
+
readFile as readFile5,
|
|
1596
1677
|
realpath as realpath5,
|
|
1597
1678
|
rmdir,
|
|
1598
1679
|
unlink as unlink2
|
|
@@ -1697,7 +1778,7 @@ async function claimFiles(claim2) {
|
|
|
1697
1778
|
if (!await sameDirectory(claim2.path, claim2.identity)) return void 0;
|
|
1698
1779
|
let names2;
|
|
1699
1780
|
try {
|
|
1700
|
-
names2 = await
|
|
1781
|
+
names2 = await readdir5(claim2.path);
|
|
1701
1782
|
} catch (error) {
|
|
1702
1783
|
if (error.code === "ENOENT") return void 0;
|
|
1703
1784
|
throw error;
|
|
@@ -1730,7 +1811,7 @@ async function readTicket(claim2, resource) {
|
|
|
1730
1811
|
if (stat6.size > 4096) stateFailure(path, "Lock ticket is too large to be a valid claim.");
|
|
1731
1812
|
let raw;
|
|
1732
1813
|
try {
|
|
1733
|
-
raw = JSON.parse(await
|
|
1814
|
+
raw = JSON.parse(await readFile5(path, "utf8"));
|
|
1734
1815
|
} catch (error) {
|
|
1735
1816
|
if (error.code === "ENOENT") return void 0;
|
|
1736
1817
|
if (error instanceof SyntaxError) stateFailure(path, "Lock ticket is malformed JSON.");
|
|
@@ -1762,7 +1843,7 @@ async function removeClaim(claim2, expectedTicket, ownedTicket) {
|
|
|
1762
1843
|
if (expectedTicket !== void 0 && names2.includes(ticketFile)) {
|
|
1763
1844
|
let current;
|
|
1764
1845
|
try {
|
|
1765
|
-
current = await
|
|
1846
|
+
current = await readFile5(join8(claim2.path, ticketFile), "utf8");
|
|
1766
1847
|
} catch (error) {
|
|
1767
1848
|
if (error.code !== "ENOENT") throw error;
|
|
1768
1849
|
}
|
|
@@ -1794,7 +1875,7 @@ async function removeClaim(claim2, expectedTicket, ownedTicket) {
|
|
|
1794
1875
|
async function claims(path, directory2, resource, localHostHash) {
|
|
1795
1876
|
if (!await sameDirectory(path, directory2)) stateFailure(path, "Lock directory disappeared.");
|
|
1796
1877
|
const result = [];
|
|
1797
|
-
for (const name of (await
|
|
1878
|
+
for (const name of (await readdir5(path)).sort()) {
|
|
1798
1879
|
const claimPath = join8(path, name);
|
|
1799
1880
|
const identity5 = identityFrom(name, claimPath);
|
|
1800
1881
|
const stat6 = await inspect2(claimPath);
|
|
@@ -1945,7 +2026,7 @@ import {
|
|
|
1945
2026
|
lstat as lstat6,
|
|
1946
2027
|
mkdir as mkdir3,
|
|
1947
2028
|
open as open3,
|
|
1948
|
-
readdir as
|
|
2029
|
+
readdir as readdir7,
|
|
1949
2030
|
realpath as realpath7,
|
|
1950
2031
|
rename as rename2,
|
|
1951
2032
|
rmdir as rmdir2,
|
|
@@ -1956,16 +2037,14 @@ import { dirname as dirname5, join as join10, relative as relative3, resolve as
|
|
|
1956
2037
|
|
|
1957
2038
|
// src/core/content.ts
|
|
1958
2039
|
import { createHash as createHash3 } from "node:crypto";
|
|
1959
|
-
import { lstat as lstat5, readdir as
|
|
2040
|
+
import { lstat as lstat5, readdir as readdir6, readFile as readFile6, readlink, realpath as realpath6 } from "node:fs/promises";
|
|
1960
2041
|
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join9, relative as relative2, sep as sep3 } from "node:path";
|
|
1961
2042
|
function isWithin(root, path) {
|
|
1962
2043
|
const child = relative2(root, path);
|
|
1963
2044
|
return child === "" || !isAbsolute2(child) && child !== ".." && !child.startsWith(`..${sep3}`);
|
|
1964
2045
|
}
|
|
2046
|
+
var repositoryNames = /* @__PURE__ */ new Set([".git", ".hg", ".svn"]);
|
|
1965
2047
|
var generatedNames = /* @__PURE__ */ new Set([
|
|
1966
|
-
".git",
|
|
1967
|
-
".hg",
|
|
1968
|
-
".svn",
|
|
1969
2048
|
".DS_Store",
|
|
1970
2049
|
"__pycache__",
|
|
1971
2050
|
".pytest_cache",
|
|
@@ -1976,8 +2055,11 @@ var generatedNames = /* @__PURE__ */ new Set([
|
|
|
1976
2055
|
".venv",
|
|
1977
2056
|
"node_modules"
|
|
1978
2057
|
]);
|
|
2058
|
+
function generatedName(name) {
|
|
2059
|
+
return generatedNames.has(name) || /\.(?:pyc|pyo|pid|sock|db-wal|db-shm)$/.test(name);
|
|
2060
|
+
}
|
|
1979
2061
|
function excludedName(name) {
|
|
1980
|
-
return
|
|
2062
|
+
return repositoryNames.has(name) || generatedName(name) || /(?:\.(?:log|bak|orig)|~)$/.test(name) || /(?:\.bak-|-backup\.)/.test(name) || /^\.env(?:\.|$)/.test(name) && ![".env.op", ".env.example", ".env.sample", ".env.template"].includes(name);
|
|
1981
2063
|
}
|
|
1982
2064
|
function contentFailure(path, message) {
|
|
1983
2065
|
fail(
|
|
@@ -1991,7 +2073,7 @@ async function captureContent(root) {
|
|
|
1991
2073
|
const entries = [];
|
|
1992
2074
|
const excluded = [];
|
|
1993
2075
|
const visit = async (path) => {
|
|
1994
|
-
for (const name of (await
|
|
2076
|
+
for (const name of (await readdir6(path)).sort()) {
|
|
1995
2077
|
const source = join9(path, name);
|
|
1996
2078
|
const item2 = relative2(root, source);
|
|
1997
2079
|
if (excludedName(name)) {
|
|
@@ -2003,7 +2085,7 @@ async function captureContent(root) {
|
|
|
2003
2085
|
entries.push({ path: item2, kind: "directory", mode: metadata2.mode & 511 });
|
|
2004
2086
|
await visit(source);
|
|
2005
2087
|
} else if (metadata2.isFile()) {
|
|
2006
|
-
const bytes = await
|
|
2088
|
+
const bytes = await readFile6(source);
|
|
2007
2089
|
const after = await lstat5(source);
|
|
2008
2090
|
if (metadata2.dev !== after.dev || metadata2.ino !== after.ino || metadata2.mode !== after.mode || metadata2.size !== after.size || metadata2.mtimeMs !== after.mtimeMs) {
|
|
2009
2091
|
contentFailure(source, `Source changed while being inspected: ${source}`);
|
|
@@ -2179,7 +2261,7 @@ async function captureVendorTree(root) {
|
|
|
2179
2261
|
const before = await lstat6(directory2, { bigint: true });
|
|
2180
2262
|
if (!before.isDirectory())
|
|
2181
2263
|
stateFailure2(directory2, "A content parent became a symlink or non-directory.");
|
|
2182
|
-
for (const name of (await
|
|
2264
|
+
for (const name of (await readdir7(directory2)).sort()) {
|
|
2183
2265
|
const path = join10(directory2, name);
|
|
2184
2266
|
const info = await lstat6(path, { bigint: true });
|
|
2185
2267
|
if (!info.isFile() && !info.isDirectory())
|
|
@@ -2210,7 +2292,7 @@ async function assertVendorTree(root, expected, allowMissing = false) {
|
|
|
2210
2292
|
if (identity5?.kind !== "directory")
|
|
2211
2293
|
stateFailure2(directory2, "A recovery parent has no directory ownership evidence.");
|
|
2212
2294
|
await assertVendorIdentity(directory2, identity5);
|
|
2213
|
-
for (const name of await
|
|
2295
|
+
for (const name of await readdir7(directory2)) {
|
|
2214
2296
|
const path = join10(directory2, name);
|
|
2215
2297
|
const item2 = relative3(root, path);
|
|
2216
2298
|
const entry3 = expectedEntries.get(item2);
|
|
@@ -2790,7 +2872,7 @@ async function importSkill(source, name, options = {}) {
|
|
|
2790
2872
|
const metadata2 = await readSkillMetadata(sourceRoot);
|
|
2791
2873
|
for (const name2 of ["SKILL.md", ".source.yaml"]) {
|
|
2792
2874
|
const captured = content.entries.find((entry3) => entry3.path === name2);
|
|
2793
|
-
if (captured?.kind === "file" && !captured.bytes.equals(await
|
|
2875
|
+
if (captured?.kind === "file" && !captured.bytes.equals(await readFile7(join12(sourceRoot, name2)))) {
|
|
2794
2876
|
fail("E_IMPORT_SOURCE_CHANGED", "Skill metadata changed while import was being planned.", {
|
|
2795
2877
|
path: join12(sourceRoot, name2),
|
|
2796
2878
|
fix: "Retry after the source writer has finished."
|
|
@@ -2844,12 +2926,12 @@ async function importSkill(source, name, options = {}) {
|
|
|
2844
2926
|
}
|
|
2845
2927
|
|
|
2846
2928
|
// src/core/diagnostics.ts
|
|
2847
|
-
import { readdir as
|
|
2929
|
+
import { readdir as readdir11, realpath as realpath13 } from "node:fs/promises";
|
|
2848
2930
|
import { homedir as homedir6 } from "node:os";
|
|
2849
2931
|
import { dirname as dirname10, join as join17, parse as parse2, resolve as resolve9, sep as sep5 } from "node:path";
|
|
2850
2932
|
|
|
2851
2933
|
// src/core/activation-ownership.ts
|
|
2852
|
-
import { lstat as lstat9, readdir as
|
|
2934
|
+
import { lstat as lstat9, readdir as readdir8, readlink as readlink2 } from "node:fs/promises";
|
|
2853
2935
|
import { dirname as dirname7, isAbsolute as isAbsolute3, join as join13, relative as relative4, resolve as resolve7 } from "node:path";
|
|
2854
2936
|
function refuse(path, message, code = "E_ACTIVATION_CONFLICT") {
|
|
2855
2937
|
fail(
|
|
@@ -2908,7 +2990,7 @@ async function inspectOwnedNode(path, node, missingOkay = false) {
|
|
|
2908
2990
|
const actual = await requireIdentity(path, node.identity, missingOkay);
|
|
2909
2991
|
if (!actual) return false;
|
|
2910
2992
|
if (node.identity.kind === "directory") {
|
|
2911
|
-
for (const name of await
|
|
2993
|
+
for (const name of await readdir8(path)) {
|
|
2912
2994
|
const expected = node.children?.[name];
|
|
2913
2995
|
if (!expected)
|
|
2914
2996
|
refuse(
|
|
@@ -2990,14 +3072,14 @@ function lexicalTarget(path, raw) {
|
|
|
2990
3072
|
}
|
|
2991
3073
|
|
|
2992
3074
|
// src/core/reconciliation.ts
|
|
2993
|
-
import { execFile } from "node:child_process";
|
|
3075
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
2994
3076
|
import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
|
|
2995
|
-
import { mkdir as mkdir6, readdir as
|
|
3077
|
+
import { mkdir as mkdir6, readdir as readdir10, realpath as realpath12, rename as rename4, rmdir as rmdir3, symlink as symlink3, unlink as unlink5 } from "node:fs/promises";
|
|
2996
3078
|
import { basename as basename3, dirname as dirname9, join as join16, relative as relative6, resolve as resolve8, sep as sep4 } from "node:path";
|
|
2997
|
-
import { promisify } from "node:util";
|
|
3079
|
+
import { promisify as promisify2 } from "node:util";
|
|
2998
3080
|
|
|
2999
3081
|
// src/core/packs.ts
|
|
3000
|
-
import { lstat as lstat11, readdir as
|
|
3082
|
+
import { lstat as lstat11, readdir as readdir9, readFile as readFile9, realpath as realpath11 } from "node:fs/promises";
|
|
3001
3083
|
import { join as join15 } from "node:path";
|
|
3002
3084
|
import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
|
|
3003
3085
|
|
|
@@ -3008,7 +3090,7 @@ import {
|
|
|
3008
3090
|
lstat as lstat10,
|
|
3009
3091
|
mkdir as mkdir5,
|
|
3010
3092
|
open as open5,
|
|
3011
|
-
readFile as
|
|
3093
|
+
readFile as readFile8,
|
|
3012
3094
|
readlink as readlink3,
|
|
3013
3095
|
realpath as realpath10,
|
|
3014
3096
|
rename as rename3,
|
|
@@ -3135,7 +3217,7 @@ async function saveManifest(plan2, changed3) {
|
|
|
3135
3217
|
await parents(plan2, manifest.path);
|
|
3136
3218
|
const info = await inspectPath(manifest.path);
|
|
3137
3219
|
if (manifest.before) {
|
|
3138
|
-
if (!info?.isFile() || info.dev !== manifest.before.info.dev || info.ino !== manifest.before.info.ino || !(await
|
|
3220
|
+
if (!info?.isFile() || info.dev !== manifest.before.info.dev || info.ino !== manifest.before.info.ino || !(await readFile8(manifest.path)).equals(manifest.before.bytes))
|
|
3139
3221
|
conflict(manifest.path, "The pack declaration changed while writing was being planned.");
|
|
3140
3222
|
} else if (info) conflict(manifest.path, "A pack manifest appeared during creation.");
|
|
3141
3223
|
};
|
|
@@ -3301,7 +3383,7 @@ async function readPack(registry, selected) {
|
|
|
3301
3383
|
const path = join15(first.path, "pack.toml");
|
|
3302
3384
|
const info = await lstat11(path);
|
|
3303
3385
|
if (!info.isFile()) conflict(path, "Pack manifests must be real files.");
|
|
3304
|
-
const bytes = await
|
|
3386
|
+
const bytes = await readFile9(path);
|
|
3305
3387
|
let raw;
|
|
3306
3388
|
try {
|
|
3307
3389
|
raw = parseToml2(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
@@ -3314,7 +3396,7 @@ async function readPack(registry, selected) {
|
|
|
3314
3396
|
}
|
|
3315
3397
|
const inventory = await packInventory(registry, selected);
|
|
3316
3398
|
const after = await lstat11(path);
|
|
3317
|
-
if (inventory.path !== first.path || !after.isFile() || after.dev !== info.dev || after.ino !== info.ino || !(await
|
|
3399
|
+
if (inventory.path !== first.path || !after.isFile() || after.dev !== info.dev || after.ino !== info.ino || !(await readFile9(path)).equals(bytes))
|
|
3318
3400
|
conflict(path, "The pack declaration changed during inspection.");
|
|
3319
3401
|
const header = raw.pack;
|
|
3320
3402
|
const text2 = description(header.description, path);
|
|
@@ -3361,7 +3443,7 @@ async function listPacks(options = {}) {
|
|
|
3361
3443
|
};
|
|
3362
3444
|
if (await inspectPath(root)) {
|
|
3363
3445
|
await requireDirectory(root, "E_PACK_MISSING");
|
|
3364
|
-
for (const family of (await
|
|
3446
|
+
for (const family of (await readdir9(root, { withFileTypes: true })).sort(
|
|
3365
3447
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
3366
3448
|
)) {
|
|
3367
3449
|
if (family.name.startsWith(".") || family.name.startsWith("_") || !family.isDirectory() && !family.isSymbolicLink())
|
|
@@ -3377,7 +3459,7 @@ async function listPacks(options = {}) {
|
|
|
3377
3459
|
await inspect6(named, path);
|
|
3378
3460
|
inspected += 1;
|
|
3379
3461
|
}
|
|
3380
|
-
for (const entry3 of (await
|
|
3462
|
+
for (const entry3 of (await readdir9(path, { withFileTypes: true })).sort(
|
|
3381
3463
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
3382
3464
|
)) {
|
|
3383
3465
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_") || !entry3.isDirectory() && !entry3.isSymbolicLink())
|
|
@@ -3463,7 +3545,7 @@ async function verifyPack(ref, options = {}) {
|
|
|
3463
3545
|
);
|
|
3464
3546
|
else {
|
|
3465
3547
|
const names2 = new Set(inventory.names);
|
|
3466
|
-
const entries = await
|
|
3548
|
+
const entries = await readdir9(inventory.skillsRoot, { withFileTypes: true });
|
|
3467
3549
|
const present = new Set(entries.map((entry3) => entry3.name));
|
|
3468
3550
|
for (const name of names2)
|
|
3469
3551
|
if (!present.has(name))
|
|
@@ -3541,7 +3623,7 @@ async function preparePack(selected, names2, operation, options) {
|
|
|
3541
3623
|
const root = join15(composition2.path, "skills");
|
|
3542
3624
|
await planDirectory(plan2, root);
|
|
3543
3625
|
if (await inspectPath(root)) {
|
|
3544
|
-
for (const entry3 of await
|
|
3626
|
+
for (const entry3 of await readdir9(root, { withFileTypes: true })) {
|
|
3545
3627
|
const path = join15(root, entry3.name);
|
|
3546
3628
|
const target2 = targets.get(entry3.name);
|
|
3547
3629
|
if (target2) await planLink(plan2, path, target2);
|
|
@@ -3835,7 +3917,7 @@ async function resolveSelectionWithManifests(options = {}, proposed = /* @__PURE
|
|
|
3835
3917
|
}
|
|
3836
3918
|
|
|
3837
3919
|
// src/core/reconciliation.ts
|
|
3838
|
-
var runFile =
|
|
3920
|
+
var runFile = promisify2(execFile2);
|
|
3839
3921
|
var lockResource = "skillex:activation:v2";
|
|
3840
3922
|
function errorResult(command, error) {
|
|
3841
3923
|
return makeResult(command, null, {
|
|
@@ -4129,7 +4211,7 @@ async function buildScope(scope, options, forbiddenRoots) {
|
|
|
4129
4211
|
}
|
|
4130
4212
|
return { identity: owned3, children };
|
|
4131
4213
|
};
|
|
4132
|
-
const awaitableNames = rootEntry?.info.isDirectory() ? await
|
|
4214
|
+
const awaitableNames = rootEntry?.info.isDirectory() ? await readdir10(physicalPath(root, recovery)) : [];
|
|
4133
4215
|
if (!rootEntry) {
|
|
4134
4216
|
operations.push({
|
|
4135
4217
|
path: root,
|
|
@@ -4905,7 +4987,7 @@ async function observeScope(location2, resolved, manifest, options, forbiddenRoo
|
|
|
4905
4987
|
const entries = [];
|
|
4906
4988
|
if (observedRoot.observation.reachable && ["directory", "link"].includes(observedRoot.observation.kind)) {
|
|
4907
4989
|
try {
|
|
4908
|
-
const names2 = (await
|
|
4990
|
+
const names2 = (await readdir11(root)).sort();
|
|
4909
4991
|
for (const name of names2) {
|
|
4910
4992
|
cancelled(options);
|
|
4911
4993
|
const path = join17(root, name);
|
|
@@ -5251,14 +5333,14 @@ async function explainSkill(name, options = {}) {
|
|
|
5251
5333
|
}
|
|
5252
5334
|
|
|
5253
5335
|
// src/core/doctor-sources.ts
|
|
5254
|
-
import { readdir as
|
|
5336
|
+
import { readdir as readdir13, readFile as readFile10, realpath as realpath15 } from "node:fs/promises";
|
|
5255
5337
|
import { basename as basename4, dirname as dirname11, join as join20, relative as relative8, sep as sep7 } from "node:path";
|
|
5256
5338
|
import { parse as parseToml4 } from "smol-toml";
|
|
5257
5339
|
|
|
5258
5340
|
// src/core/vendor-provenance.ts
|
|
5259
5341
|
import { createHash as createHash6 } from "node:crypto";
|
|
5260
5342
|
import { constants as constants4 } from "node:fs";
|
|
5261
|
-
import { lstat as lstat13, open as open7, readdir as
|
|
5343
|
+
import { lstat as lstat13, open as open7, readdir as readdir12 } from "node:fs/promises";
|
|
5262
5344
|
import { join as join19, relative as relative7, sep as sep6 } from "node:path";
|
|
5263
5345
|
import { stringify as stringify2 } from "yaml";
|
|
5264
5346
|
|
|
@@ -5770,7 +5852,7 @@ async function digestVendorTree(skillPath) {
|
|
|
5770
5852
|
const before = await lstat13(directory2);
|
|
5771
5853
|
if (!before.isDirectory())
|
|
5772
5854
|
unsafe(directory2, "Vendored skill directories must be real directories.");
|
|
5773
|
-
for (const name of (await
|
|
5855
|
+
for (const name of (await readdir12(directory2)).sort()) {
|
|
5774
5856
|
if (directory2 === root && name === ".source.yaml") continue;
|
|
5775
5857
|
const path = join19(directory2, name);
|
|
5776
5858
|
if (administration.has(name))
|
|
@@ -5822,7 +5904,7 @@ async function digestRecordedSkill(skillPath, provenance) {
|
|
|
5822
5904
|
async function appendRegularContent(root, path, entries) {
|
|
5823
5905
|
const info = await lstat13(path);
|
|
5824
5906
|
if (info.isDirectory()) {
|
|
5825
|
-
for (const name of (await
|
|
5907
|
+
for (const name of (await readdir12(path)).sort()) {
|
|
5826
5908
|
if (administration.has(name))
|
|
5827
5909
|
unsafe(join19(path, name), "Repository administration is not vendored skill content.");
|
|
5828
5910
|
await appendRegularContent(root, join19(path, name), entries);
|
|
@@ -6066,7 +6148,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6066
6148
|
};
|
|
6067
6149
|
await attempt(catalog, async () => {
|
|
6068
6150
|
await requireDirectory(catalog, "E_NO_REGISTRY");
|
|
6069
|
-
for (const name of (await
|
|
6151
|
+
for (const name of (await readdir13(catalog)).sort()) {
|
|
6070
6152
|
if (name === ".git") continue;
|
|
6071
6153
|
const path = join20(catalog, name);
|
|
6072
6154
|
await attempt(path, async () => {
|
|
@@ -6097,7 +6179,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6097
6179
|
});
|
|
6098
6180
|
const packManifests = [];
|
|
6099
6181
|
const walk = async (directory2) => {
|
|
6100
|
-
const names2 = await attempt(directory2, () =>
|
|
6182
|
+
const names2 = await attempt(directory2, () => readdir13(directory2));
|
|
6101
6183
|
for (const name of (names2 ?? []).sort()) {
|
|
6102
6184
|
if (name === ".git") continue;
|
|
6103
6185
|
const path = join20(directory2, name);
|
|
@@ -6132,7 +6214,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6132
6214
|
if (!await inspectPath(root)) return;
|
|
6133
6215
|
await requireDirectory(root, "E_COMPOSITION_ROOT");
|
|
6134
6216
|
await walk(root);
|
|
6135
|
-
for (const name of (await
|
|
6217
|
+
for (const name of (await readdir13(root)).sort()) {
|
|
6136
6218
|
if (!isSkillName(name)) continue;
|
|
6137
6219
|
const path = join20(root, name);
|
|
6138
6220
|
const info = await inspectPath(path);
|
|
@@ -6144,7 +6226,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6144
6226
|
if (kind2 !== "sets" || !info.isDirectory()) continue;
|
|
6145
6227
|
data2.sets++;
|
|
6146
6228
|
const members2 = await attempt(path, () => setMembers(registry.root, name));
|
|
6147
|
-
const names2 = members2?.names ?? (await
|
|
6229
|
+
const names2 = members2?.names ?? (await readdir13(path, { withFileTypes: true })).filter((item2) => item2.isSymbolicLink() && isSkillName(item2.name)).map((item2) => item2.name);
|
|
6148
6230
|
for (const member of names2)
|
|
6149
6231
|
await attempt(join20(path, member), () => setMemberTarget(registry.root, path, member));
|
|
6150
6232
|
}
|
|
@@ -6182,7 +6264,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6182
6264
|
return { data: data2, findings, exits };
|
|
6183
6265
|
}
|
|
6184
6266
|
async function toml(path, code) {
|
|
6185
|
-
const bytes = await
|
|
6267
|
+
const bytes = await readFile10(path);
|
|
6186
6268
|
try {
|
|
6187
6269
|
return parseToml4(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
6188
6270
|
} catch {
|
|
@@ -6194,8 +6276,8 @@ async function toml(path, code) {
|
|
|
6194
6276
|
}
|
|
6195
6277
|
|
|
6196
6278
|
// src/core/doctor-writers.ts
|
|
6197
|
-
import { execFile as
|
|
6198
|
-
import { readdir as
|
|
6279
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
6280
|
+
import { readdir as readdir14, readFile as readFile11, realpath as realpath16, stat as stat3 } from "node:fs/promises";
|
|
6199
6281
|
import { homedir as homedir8 } from "node:os";
|
|
6200
6282
|
import { basename as basename5, dirname as dirname12, join as join21, resolve as resolve11 } from "node:path";
|
|
6201
6283
|
import { parse as parseToml5 } from "smol-toml";
|
|
@@ -6417,12 +6499,12 @@ async function inspectDoctorWriters(options, registries, scopes) {
|
|
|
6417
6499
|
};
|
|
6418
6500
|
const serviceRoot = join21(configHome, "systemd", "user");
|
|
6419
6501
|
try {
|
|
6420
|
-
for (const entry3 of await
|
|
6502
|
+
for (const entry3 of await readdir14(serviceRoot, { withFileTypes: true })) {
|
|
6421
6503
|
const path = join21(serviceRoot, entry3.name);
|
|
6422
6504
|
if (entry3.name.endsWith(".service")) candidates.set(path, "service");
|
|
6423
6505
|
else if (entry3.isDirectory() && entry3.name.endsWith(".service.d")) {
|
|
6424
6506
|
try {
|
|
6425
|
-
for (const file of await
|
|
6507
|
+
for (const file of await readdir14(path))
|
|
6426
6508
|
if (file.endsWith(".conf")) candidates.set(join21(path, file), "service");
|
|
6427
6509
|
} catch {
|
|
6428
6510
|
unavailable(path, "Cannot inspect user service overrides.");
|
|
@@ -6442,7 +6524,7 @@ async function inspectDoctorWriters(options, registries, scopes) {
|
|
|
6442
6524
|
unavailable(path, "Writer configuration is not a bounded regular file.");
|
|
6443
6525
|
continue;
|
|
6444
6526
|
}
|
|
6445
|
-
const content = new TextDecoder("utf-8", { fatal: true }).decode(await
|
|
6527
|
+
const content = new TextDecoder("utf-8", { fatal: true }).decode(await readFile11(path));
|
|
6446
6528
|
const lines = content.split(/\r?\n/);
|
|
6447
6529
|
if (kind2 === "service") {
|
|
6448
6530
|
for (const [index, line] of lines.entries()) {
|
|
@@ -6520,7 +6602,7 @@ async function inspectDoctorWriters(options, registries, scopes) {
|
|
|
6520
6602
|
}
|
|
6521
6603
|
async function boundedSnapshot(options, env) {
|
|
6522
6604
|
const observation = options.processSnapshot ? options.processSnapshot() : new Promise((accept, reject) => {
|
|
6523
|
-
|
|
6605
|
+
execFile3(
|
|
6524
6606
|
"ps",
|
|
6525
6607
|
["-axo", "pid=,args="],
|
|
6526
6608
|
{ env, timeout: timeoutMs, maxBuffer: maxProcessBytes, encoding: "utf8" },
|
|
@@ -6690,14 +6772,14 @@ import { realpath as realpath21, stat as stat4 } from "node:fs/promises";
|
|
|
6690
6772
|
// src/core/migration-activation.ts
|
|
6691
6773
|
import { createHash as createHash8, randomUUID as randomUUID6 } from "node:crypto";
|
|
6692
6774
|
import { constants as constants6 } from "node:fs";
|
|
6693
|
-
import { mkdir as mkdir7, open as open9, readdir as
|
|
6775
|
+
import { mkdir as mkdir7, open as open9, readdir as readdir17, realpath as realpath18, rename as rename5, symlink as symlink4 } from "node:fs/promises";
|
|
6694
6776
|
import { dirname as dirname16, isAbsolute as isAbsolute7, join as join25, parse as parse3, relative as relative10, resolve as resolve14 } from "node:path";
|
|
6695
6777
|
import { isDeepStrictEqual as isDeepStrictEqual4 } from "node:util";
|
|
6696
6778
|
|
|
6697
6779
|
// src/core/migration-activation-state.ts
|
|
6698
6780
|
import { createHash as createHash7 } from "node:crypto";
|
|
6699
6781
|
import { constants as constants5 } from "node:fs";
|
|
6700
|
-
import { lstat as lstat14, open as open8, readdir as
|
|
6782
|
+
import { lstat as lstat14, open as open8, readdir as readdir15, readlink as readlink4, rmdir as rmdir4, unlink as unlink6 } from "node:fs/promises";
|
|
6701
6783
|
import { dirname as dirname13, isAbsolute as isAbsolute5, join as join22, relative as relative9, resolve as resolve12 } from "node:path";
|
|
6702
6784
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
6703
6785
|
import { parseDocument as parseDocument2 } from "yaml";
|
|
@@ -6739,7 +6821,7 @@ async function captureMigrationNode(path) {
|
|
|
6739
6821
|
const children = {};
|
|
6740
6822
|
let hash4 = null;
|
|
6741
6823
|
if (before.isDirectory()) {
|
|
6742
|
-
const names2 = await
|
|
6824
|
+
const names2 = await readdir15(path, { encoding: "buffer" });
|
|
6743
6825
|
if (names2.length > 1e4)
|
|
6744
6826
|
migrationRefusal(path, "A migration directory exceeds the bounded inventory limit.");
|
|
6745
6827
|
for (const bytes of names2.sort(Buffer.compare)) {
|
|
@@ -6955,7 +7037,7 @@ function migrationAbsolute(value, home, cwd) {
|
|
|
6955
7037
|
}
|
|
6956
7038
|
|
|
6957
7039
|
// src/core/profile-discovery.ts
|
|
6958
|
-
import { lstat as lstat15, readdir as
|
|
7040
|
+
import { lstat as lstat15, readdir as readdir16, readlink as readlink5, realpath as realpath17 } from "node:fs/promises";
|
|
6959
7041
|
import { homedir as homedir9 } from "node:os";
|
|
6960
7042
|
import { basename as basename6, dirname as dirname14, join as join23, resolve as resolve13 } from "node:path";
|
|
6961
7043
|
var namePattern = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
@@ -7198,7 +7280,7 @@ async function discoverProfiles(options = {}) {
|
|
|
7198
7280
|
await canonicalDirectory(directory2, false);
|
|
7199
7281
|
let names2;
|
|
7200
7282
|
try {
|
|
7201
|
-
names2 = (await
|
|
7283
|
+
names2 = (await readdir16(directory2, { withFileTypes: true })).filter(
|
|
7202
7284
|
(entry3) => entry3.name !== "default" && namePattern.test(entry3.name) && (entry3.isDirectory() || entry3.isSymbolicLink())
|
|
7203
7285
|
).map((entry3) => entry3.name).sort();
|
|
7204
7286
|
} catch (error) {
|
|
@@ -7770,7 +7852,7 @@ async function collectArtifacts(plan2) {
|
|
|
7770
7852
|
await guard(plan2.target, join25(parent, "child"));
|
|
7771
7853
|
const current = await entry2(parent);
|
|
7772
7854
|
if (!current) continue;
|
|
7773
|
-
for (const name of await
|
|
7855
|
+
for (const name of await readdir17(parent)) {
|
|
7774
7856
|
if (!/^\.skillex-tmp-migration-[a-f0-9-]+-(?:new|old)$/.test(name)) continue;
|
|
7775
7857
|
const path = join25(parent, name);
|
|
7776
7858
|
if (path === pending?.stage || path === pending?.parked) continue;
|
|
@@ -8313,7 +8395,7 @@ async function finishPending(target2, state, begin = () => {
|
|
|
8313
8395
|
verified: state.data.verified
|
|
8314
8396
|
});
|
|
8315
8397
|
}
|
|
8316
|
-
async function
|
|
8398
|
+
async function execute2(target2, operation, state, mark, begin) {
|
|
8317
8399
|
check(target2.options);
|
|
8318
8400
|
await guard(target2, operation.item.path);
|
|
8319
8401
|
for (const proof of operation.proofs) await assertMigrationNode(proof.path, proof.node);
|
|
@@ -8590,7 +8672,7 @@ async function migrateActivation(options = {}) {
|
|
|
8590
8672
|
"The migration item did not converge after publication."
|
|
8591
8673
|
);
|
|
8592
8674
|
attempted.add(operation.item.id);
|
|
8593
|
-
state = await
|
|
8675
|
+
state = await execute2(
|
|
8594
8676
|
selected,
|
|
8595
8677
|
operation,
|
|
8596
8678
|
current.state,
|
|
@@ -8857,7 +8939,7 @@ import {
|
|
|
8857
8939
|
lstat as lstat16,
|
|
8858
8940
|
mkdir as mkdir8,
|
|
8859
8941
|
open as open10,
|
|
8860
|
-
readdir as
|
|
8942
|
+
readdir as readdir18,
|
|
8861
8943
|
readlink as readlink6,
|
|
8862
8944
|
rename as rename6,
|
|
8863
8945
|
rmdir as rmdir5,
|
|
@@ -8952,6 +9034,7 @@ async function captureMigrationTree(root) {
|
|
|
8952
9034
|
const initial = await migrationLstat(root);
|
|
8953
9035
|
if (!initial) return null;
|
|
8954
9036
|
const entries = [];
|
|
9037
|
+
const excluded = [];
|
|
8955
9038
|
const evidence2 = [];
|
|
8956
9039
|
const visit = async (path, relpath) => {
|
|
8957
9040
|
const before = await lstat16(path, { bigint: true });
|
|
@@ -8974,14 +9057,19 @@ async function captureMigrationTree(root) {
|
|
|
8974
9057
|
target: target2 ?? null
|
|
8975
9058
|
});
|
|
8976
9059
|
if (id.kind === "directory") {
|
|
8977
|
-
for (const name of (await
|
|
9060
|
+
for (const name of (await readdir18(path)).sort()) {
|
|
8978
9061
|
if ([".git", ".hg", ".svn"].includes(name))
|
|
8979
9062
|
migrationFailure2(
|
|
8980
9063
|
join27(path, name),
|
|
8981
9064
|
"A definition or composition contains repository administration; map its authored content explicitly.",
|
|
8982
9065
|
"E_MIGRATION_CONTENT"
|
|
8983
9066
|
);
|
|
8984
|
-
|
|
9067
|
+
const child = relpath ? join27(relpath, name) : name;
|
|
9068
|
+
if (generatedName(name)) {
|
|
9069
|
+
excluded.push(child);
|
|
9070
|
+
continue;
|
|
9071
|
+
}
|
|
9072
|
+
await visit(join27(path, name), child);
|
|
8985
9073
|
}
|
|
8986
9074
|
}
|
|
8987
9075
|
if (!stable2(before, await lstat16(path, { bigint: true })))
|
|
@@ -8992,9 +9080,23 @@ async function captureMigrationTree(root) {
|
|
|
8992
9080
|
migrationFailure2(root, "Source root was replaced while it was inspected.");
|
|
8993
9081
|
return {
|
|
8994
9082
|
entries,
|
|
8995
|
-
evidence: { root: identity3(initial), entries: evidence2, digest: migrationTreeDigest(entries) }
|
|
9083
|
+
evidence: { root: identity3(initial), entries: evidence2, digest: migrationTreeDigest(entries) },
|
|
9084
|
+
excluded
|
|
8996
9085
|
};
|
|
8997
9086
|
}
|
|
9087
|
+
function refuseExcludedRemoval(path, tree) {
|
|
9088
|
+
const first = tree.excluded[0];
|
|
9089
|
+
if (first === void 0) return;
|
|
9090
|
+
fail(
|
|
9091
|
+
"E_MIGRATION_RUNTIME_CONTENT",
|
|
9092
|
+
`Migration would remove generated build, cache, or runtime content it never migrates: ${tree.excluded.join(", ")}`,
|
|
9093
|
+
{
|
|
9094
|
+
path: join27(path, first),
|
|
9095
|
+
fix: "Remove only verified generated entries (their tools rebuild them), or move them elsewhere, then rerun the migration preview."
|
|
9096
|
+
},
|
|
9097
|
+
ExitCode.REFUSED
|
|
9098
|
+
);
|
|
9099
|
+
}
|
|
8998
9100
|
async function assertMigrationIdentity(path, expected) {
|
|
8999
9101
|
if (!equalIdentity2(await migrationLstat(path), expected))
|
|
9000
9102
|
migrationFailure2(path, "A migration entry changed identity.");
|
|
@@ -9104,6 +9206,7 @@ async function removeMigrationTree(path, expected) {
|
|
|
9104
9206
|
const currentTree = await captureMigrationTree(path);
|
|
9105
9207
|
if (!currentTree) return;
|
|
9106
9208
|
await assertMigrationIdentity(path, expected.root);
|
|
9209
|
+
refuseExcludedRemoval(path, currentTree);
|
|
9107
9210
|
for (const current of currentTree.evidence.entries) {
|
|
9108
9211
|
const known = expected.entries.find((entry3) => entry3.path === current.path);
|
|
9109
9212
|
if (!known || JSON.stringify(known) !== JSON.stringify(current))
|
|
@@ -9210,6 +9313,7 @@ async function publishMigrationOperation(operation) {
|
|
|
9210
9313
|
} else {
|
|
9211
9314
|
if (destination) {
|
|
9212
9315
|
await assertMigrationTree(operation.path, operation.before);
|
|
9316
|
+
refuseExcludedRemoval(operation.path, destination);
|
|
9213
9317
|
if (!operation.parked || parked)
|
|
9214
9318
|
migrationFailure2(operation.path, "Cannot park the original migration entry safely.");
|
|
9215
9319
|
await assertMigrationTree(operation.parked, null);
|
|
@@ -10356,7 +10460,7 @@ async function migrateManifest(options, nameMap = {}) {
|
|
|
10356
10460
|
|
|
10357
10461
|
// src/core/migration-registry.ts
|
|
10358
10462
|
import { createHash as createHash10 } from "node:crypto";
|
|
10359
|
-
import { lstat as lstat18, readdir as
|
|
10463
|
+
import { lstat as lstat18, readdir as readdir19, realpath as realpath20 } from "node:fs/promises";
|
|
10360
10464
|
import { basename as basename8, dirname as dirname19, isAbsolute as isAbsolute10, join as join29, relative as relative13, sep as sep9 } from "node:path";
|
|
10361
10465
|
import { parse as parseToml7, stringify as stringifyToml2 } from "smol-toml";
|
|
10362
10466
|
function record8(value) {
|
|
@@ -10374,7 +10478,7 @@ function payloadDigest(entries) {
|
|
|
10374
10478
|
async function sameCaseOnlyEntry(source, destination, before) {
|
|
10375
10479
|
if (dirname19(source) !== dirname19(destination) || basename8(source) === basename8(destination) || basename8(source).toLowerCase() !== basename8(destination).toLowerCase())
|
|
10376
10480
|
return false;
|
|
10377
|
-
const names2 = await
|
|
10481
|
+
const names2 = await readdir19(dirname19(source));
|
|
10378
10482
|
if (!names2.includes(basename8(source)) || names2.includes(basename8(destination))) return false;
|
|
10379
10483
|
const destinationTree = await captureMigrationTree(destination);
|
|
10380
10484
|
return JSON.stringify(destinationTree?.evidence) === JSON.stringify(before);
|
|
@@ -10454,6 +10558,26 @@ function refuse2(path, code, message) {
|
|
|
10454
10558
|
ExitCode.REFUSED
|
|
10455
10559
|
);
|
|
10456
10560
|
}
|
|
10561
|
+
var runtimeContentFix = "Remove only verified generated entries (their tools rebuild them), or move them outside the composition, then rerun the migration preview.";
|
|
10562
|
+
function runtimeContentMessage(holder) {
|
|
10563
|
+
return `A ${holder} contains generated build, cache, or runtime content that migration never carries or removes.`;
|
|
10564
|
+
}
|
|
10565
|
+
function hiddenPath(path) {
|
|
10566
|
+
return path.split(sep9).some((part) => part.startsWith(".") || part.startsWith("_"));
|
|
10567
|
+
}
|
|
10568
|
+
function skippedDetail(skipped, where) {
|
|
10569
|
+
return skipped.length ? [
|
|
10570
|
+
`Generated build, cache, or runtime entries ${where} are not definition content: they are not copied, not carried, and stay at the source: ${skipped.join(", ")}`
|
|
10571
|
+
] : [];
|
|
10572
|
+
}
|
|
10573
|
+
function runtimeContent(path, holder) {
|
|
10574
|
+
fail(
|
|
10575
|
+
"E_MIGRATION_RUNTIME_CONTENT",
|
|
10576
|
+
runtimeContentMessage(holder),
|
|
10577
|
+
{ path, fix: runtimeContentFix },
|
|
10578
|
+
ExitCode.REFUSED
|
|
10579
|
+
);
|
|
10580
|
+
}
|
|
10457
10581
|
function safeKey(value) {
|
|
10458
10582
|
const parts = value.split("/");
|
|
10459
10583
|
return !!value && !value.includes("\\") && ![...value].some(
|
|
@@ -10588,21 +10712,15 @@ async function importDefinition(plan2, lexicalSource, preferred, selected) {
|
|
|
10588
10712
|
if (metadata2.provenance) plan2.provenance[lexicalSource] = metadata2.provenance;
|
|
10589
10713
|
const tree = selected ? {
|
|
10590
10714
|
entries: selected,
|
|
10591
|
-
evidence: { ...captured.evidence, digest: migrationTreeDigest(selected) }
|
|
10715
|
+
evidence: { ...captured.evidence, digest: migrationTreeDigest(selected) },
|
|
10716
|
+
excluded: captured.excluded
|
|
10592
10717
|
} : captured;
|
|
10593
|
-
const
|
|
10594
|
-
(
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
"A candidate definition contains generated Python cache content.",
|
|
10600
|
-
{
|
|
10601
|
-
path: join29(lexicalSource, runtime.path),
|
|
10602
|
-
fix: "Remove only verified generated Python cache entries from the source, then rerun the migration preview."
|
|
10603
|
-
},
|
|
10604
|
-
ExitCode.REFUSED
|
|
10605
|
-
);
|
|
10718
|
+
const skipped = selected ? captured.excluded.filter(
|
|
10719
|
+
(item2) => selected.some(
|
|
10720
|
+
(entry3) => entry3.kind === "directory" && entry3.path !== "" && item2.startsWith(`${entry3.path}${sep9}`)
|
|
10721
|
+
)
|
|
10722
|
+
) : captured.excluded;
|
|
10723
|
+
plan2.skipped.set(lexicalSource, skipped);
|
|
10606
10724
|
const expected = mapped(plan2.options.mapping?.digests, plan2, lexicalSource) ?? mapped(plan2.options.mapping?.digests, plan2, source);
|
|
10607
10725
|
if (expected !== void 0 && expected !== tree.evidence.digest)
|
|
10608
10726
|
refuse2(
|
|
@@ -10655,7 +10773,8 @@ async function importDefinition(plan2, lexicalSource, preferred, selected) {
|
|
|
10655
10773
|
details: [
|
|
10656
10774
|
`source: ${lexicalSource}`,
|
|
10657
10775
|
`canonical name: ${name}`,
|
|
10658
|
-
"All captured definition bytes, support entries, modes, and provenance are preserved; internal absolute links are made portable."
|
|
10776
|
+
skipped.length ? "Every authored definition byte, support entry, mode, and provenance is preserved; internal absolute links are made portable." : "All captured definition bytes, support entries, modes, and provenance are preserved; internal absolute links are made portable.",
|
|
10777
|
+
...skippedDetail(skipped, "at this source")
|
|
10659
10778
|
],
|
|
10660
10779
|
dependsOn: []
|
|
10661
10780
|
};
|
|
@@ -10675,7 +10794,8 @@ async function importDefinition(plan2, lexicalSource, preferred, selected) {
|
|
|
10675
10794
|
beforeDigest: tree.evidence.digest,
|
|
10676
10795
|
afterDigest: reused.digest,
|
|
10677
10796
|
details: [
|
|
10678
|
-
dependency ? "Definition is retained at its canonical destination before its original composition entry is replaced." : "Existing canonical authored payload is identical; differing original provenance is preserved separately in the migration receipt."
|
|
10797
|
+
dependency ? "Definition is retained at its canonical destination before its original composition entry is replaced." : "Existing canonical authored payload is identical; differing original provenance is preserved separately in the migration receipt.",
|
|
10798
|
+
...skippedDetail(skipped, "at this source")
|
|
10679
10799
|
],
|
|
10680
10800
|
dependsOn: dependency ? [dependency] : []
|
|
10681
10801
|
});
|
|
@@ -10835,6 +10955,8 @@ async function composition(plan2, path, kind2) {
|
|
|
10835
10955
|
"E_MIGRATION_REFERENCE",
|
|
10836
10956
|
"A null reference mapping cannot remove a real definition; name and preserve its content instead."
|
|
10837
10957
|
);
|
|
10958
|
+
const generated = before.excluded.find((item3) => item3.startsWith(`${definition}${sep9}`));
|
|
10959
|
+
if (generated !== void 0) runtimeContent(join29(path, generated), "candidate definition");
|
|
10838
10960
|
const skill2 = await importDefinition(plan2, lexical, basename8(definition));
|
|
10839
10961
|
members2.push({
|
|
10840
10962
|
relativePath: definition,
|
|
@@ -10849,7 +10971,9 @@ async function composition(plan2, path, kind2) {
|
|
|
10849
10971
|
}
|
|
10850
10972
|
}
|
|
10851
10973
|
for (const entry3 of tree.entries) {
|
|
10852
|
-
if (!entry3.path || entry3.kind !== "link" ||
|
|
10974
|
+
if (!entry3.path || entry3.kind !== "link" || // A hidden link is outside the composition's namespace: never a member, never rewritten,
|
|
10975
|
+
// and its referent (often installer-owned) is never imported into the catalog.
|
|
10976
|
+
hiddenPath(entry3.path) || definitionRoots.some((parent) => entry3.path.startsWith(`${parent}${sep9}`)))
|
|
10853
10977
|
continue;
|
|
10854
10978
|
const lexical = join29(path, entry3.path);
|
|
10855
10979
|
const explicitReference = mapped(plan2.options.mapping?.references, plan2, lexical);
|
|
@@ -10913,7 +11037,7 @@ async function composition(plan2, path, kind2) {
|
|
|
10913
11037
|
if (kind2 === "pack" || dirname19(member.relativePath) !== ".")
|
|
10914
11038
|
entries.push(memberLink(plan2, destination, member.relativePath, member.name));
|
|
10915
11039
|
}
|
|
10916
|
-
let selected = members2.map((member) => member.name);
|
|
11040
|
+
let selected = members2.filter((member) => !hiddenPath(member.relativePath)).map((member) => member.name);
|
|
10917
11041
|
if (kind2 === "pack") {
|
|
10918
11042
|
const declared = [];
|
|
10919
11043
|
if (raw.freeform !== void 0 && (!record8(raw.freeform) || !Array.isArray(raw.freeform.skills) || !raw.freeform.skills.every((item3) => typeof item3 === "string")))
|
|
@@ -11065,6 +11189,18 @@ async function composition(plan2, path, kind2) {
|
|
|
11065
11189
|
entries = entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
11066
11190
|
const afterDigest = migrationTreeDigest(entries);
|
|
11067
11191
|
const changed3 = destination !== path || before.evidence.digest !== afterDigest;
|
|
11192
|
+
if (changed3)
|
|
11193
|
+
for (const item3 of before.excluded) {
|
|
11194
|
+
if (definitionRoots.some((root) => item3.startsWith(`${root}${sep9}`))) continue;
|
|
11195
|
+
finding(
|
|
11196
|
+
plan2,
|
|
11197
|
+
join29(path, item3),
|
|
11198
|
+
"E_MIGRATION_RUNTIME_CONTENT",
|
|
11199
|
+
runtimeContentMessage("composition"),
|
|
11200
|
+
runtimeContentFix
|
|
11201
|
+
);
|
|
11202
|
+
hasBlocker = true;
|
|
11203
|
+
}
|
|
11068
11204
|
const dependencies = [
|
|
11069
11205
|
...new Set(members2.flatMap((member) => member.dependency ? [member.dependency] : []))
|
|
11070
11206
|
];
|
|
@@ -11132,12 +11268,13 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11132
11268
|
provenance: { ...previousProvenance },
|
|
11133
11269
|
externalRoots: new Set(previousExternalRoots),
|
|
11134
11270
|
canonical: /* @__PURE__ */ new Map(),
|
|
11135
|
-
imported: /* @__PURE__ */ new Map()
|
|
11271
|
+
imported: /* @__PURE__ */ new Map(),
|
|
11272
|
+
skipped: /* @__PURE__ */ new Map()
|
|
11136
11273
|
};
|
|
11137
11274
|
const catalog = join29(registry.root, "all-skills");
|
|
11138
11275
|
const links = [];
|
|
11139
11276
|
const retiredAliases = [];
|
|
11140
|
-
for (const entry3 of (await
|
|
11277
|
+
for (const entry3 of (await readdir19(catalog, { withFileTypes: true })).sort(
|
|
11141
11278
|
(a, b) => a.name.localeCompare(b.name)
|
|
11142
11279
|
)) {
|
|
11143
11280
|
interrupted2(options);
|
|
@@ -11210,7 +11347,10 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11210
11347
|
state: "ready",
|
|
11211
11348
|
beforeDigest: before.evidence.digest,
|
|
11212
11349
|
afterDigest: skill2.digest,
|
|
11213
|
-
details: [
|
|
11350
|
+
details: plan2.skipped.get(path)?.length ? [
|
|
11351
|
+
"Identical authored definition content is retained at the canonical target; retire only this exact alias after composition rewrites.",
|
|
11352
|
+
...skippedDetail(plan2.skipped.get(path) ?? [], "at the alias source")
|
|
11353
|
+
] : [
|
|
11214
11354
|
"Identical definition content is retained at the canonical target; retire only this exact alias after composition rewrites."
|
|
11215
11355
|
],
|
|
11216
11356
|
dependsOn: skill2.dependency ? [skill2.dependency] : []
|
|
@@ -11240,7 +11380,7 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11240
11380
|
blocked3(plan2, root, new Error("Composition family root must be a real directory."));
|
|
11241
11381
|
continue;
|
|
11242
11382
|
}
|
|
11243
|
-
for (const entry3 of (await
|
|
11383
|
+
for (const entry3 of (await readdir19(root, { withFileTypes: true })).sort(
|
|
11244
11384
|
(a, b) => a.name.localeCompare(b.name)
|
|
11245
11385
|
)) {
|
|
11246
11386
|
const path = join29(root, entry3.name);
|
|
@@ -11262,7 +11402,7 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11262
11402
|
else if (await inspectPath(join29(path, "pack.toml"))) await composition(plan2, path, "pack");
|
|
11263
11403
|
else {
|
|
11264
11404
|
const versions = [];
|
|
11265
|
-
for (const child of await
|
|
11405
|
+
for (const child of await readdir19(path, { withFileTypes: true }))
|
|
11266
11406
|
if ((child.isDirectory() || child.isSymbolicLink()) && await inspectPath(join29(path, child.name, "pack.toml")))
|
|
11267
11407
|
versions.push(child.name);
|
|
11268
11408
|
if (versions.length)
|
|
@@ -11532,7 +11672,7 @@ async function migrateRegistry(options = {}) {
|
|
|
11532
11672
|
// src/core/migration-sources.ts
|
|
11533
11673
|
import { createHash as createHash11, randomUUID as randomUUID8 } from "node:crypto";
|
|
11534
11674
|
import { constants as constants9 } from "node:fs";
|
|
11535
|
-
import { link as link6, open as open12, readdir as
|
|
11675
|
+
import { link as link6, open as open12, readdir as readdir20, unlink as unlink8 } from "node:fs/promises";
|
|
11536
11676
|
import { basename as basename9, join as join30 } from "node:path";
|
|
11537
11677
|
var binding3 = { namespace: "migrations", targetParts: ["all-skills", "sources.toml"] };
|
|
11538
11678
|
var prefix = ".skillex-tmp-migrate-sources-";
|
|
@@ -11551,7 +11691,7 @@ function refused2(path, message) {
|
|
|
11551
11691
|
async function validateProvenance(registry, text2, path) {
|
|
11552
11692
|
const sources = parseVendorSourcesText(text2, path);
|
|
11553
11693
|
const catalog = join30(registry.root, "all-skills");
|
|
11554
|
-
for (const name of (await
|
|
11694
|
+
for (const name of (await readdir20(catalog)).sort()) {
|
|
11555
11695
|
const skill2 = join30(catalog, name);
|
|
11556
11696
|
if (!(await inspectPath(skill2))?.isDirectory() || !await inspectPath(join30(skill2, "SKILL.md")))
|
|
11557
11697
|
continue;
|
|
@@ -11583,7 +11723,7 @@ async function migrateSources(input) {
|
|
|
11583
11723
|
const old = snapshot3.document?.data;
|
|
11584
11724
|
if (old && (old.version !== 1 || typeof old.source !== "string" || typeof old.digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(old.digest) || !["pending", "verified"].includes(old.phase) || typeof old.stage !== "string" || basename9(old.stage) !== old.stage || !new RegExp(`^${prefix}[a-f0-9-]{36}$`).test(old.stage) || !/^\d+$/.test(old.dev) || !/^\d+$/.test(old.ino)))
|
|
11585
11725
|
refused2(snapshot3.path, "The source migration receipt is malformed.");
|
|
11586
|
-
const artifacts = (await
|
|
11726
|
+
const artifacts = (await readdir20(catalog)).filter((name) => name.startsWith(prefix));
|
|
11587
11727
|
const unknown = artifacts.filter((name) => name !== old?.stage || old.phase !== "pending");
|
|
11588
11728
|
if (unknown.length)
|
|
11589
11729
|
return makeResult("migrate sources", data2, {
|
|
@@ -11883,10 +12023,94 @@ async function migrate(input = {}) {
|
|
|
11883
12023
|
|
|
11884
12024
|
// src/core/profile-sync.ts
|
|
11885
12025
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
11886
|
-
import { constants as
|
|
11887
|
-
import { mkdir as mkdir10, open as
|
|
11888
|
-
import { join as
|
|
12026
|
+
import { constants as constants11 } from "node:fs";
|
|
12027
|
+
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";
|
|
12028
|
+
import { join as join32, resolve as resolve17 } from "node:path";
|
|
11889
12029
|
import { isDeepStrictEqual as isDeepStrictEqual6 } from "node:util";
|
|
12030
|
+
|
|
12031
|
+
// src/core/profile-policy.ts
|
|
12032
|
+
import { constants as constants10 } from "node:fs";
|
|
12033
|
+
import { open as open13, readFile as readFile12 } from "node:fs/promises";
|
|
12034
|
+
import { join as join31 } from "node:path";
|
|
12035
|
+
import { parseDocument as parseDocument3 } from "yaml";
|
|
12036
|
+
var metadataFiles = /* @__PURE__ */ new Set([
|
|
12037
|
+
".usage.json",
|
|
12038
|
+
".usage.json.lock",
|
|
12039
|
+
".curator_state",
|
|
12040
|
+
".curator_suppressed",
|
|
12041
|
+
".sync_state"
|
|
12042
|
+
]);
|
|
12043
|
+
var metadataDirectories = /* @__PURE__ */ new Set([".curator_backups"]);
|
|
12044
|
+
function hermesMetadata(item2) {
|
|
12045
|
+
if (item2.kind === "file") return metadataFiles.has(item2.name);
|
|
12046
|
+
if (item2.kind === "directory") return metadataDirectories.has(item2.name);
|
|
12047
|
+
return false;
|
|
12048
|
+
}
|
|
12049
|
+
function refuse3(path, message) {
|
|
12050
|
+
return fail(
|
|
12051
|
+
"E_PROFILE_SKILLEX_ONLY",
|
|
12052
|
+
message,
|
|
12053
|
+
{
|
|
12054
|
+
path,
|
|
12055
|
+
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."
|
|
12056
|
+
},
|
|
12057
|
+
ExitCode.REFUSED
|
|
12058
|
+
);
|
|
12059
|
+
}
|
|
12060
|
+
async function strictProfile(profile, options) {
|
|
12061
|
+
if (options.skillexOnly !== void 0 && typeof options.skillexOnly !== "boolean")
|
|
12062
|
+
refuse3(profile.root, "skillexOnly must be boolean.");
|
|
12063
|
+
const marker = await entry2(join31(profile.root, ".skillex-only"));
|
|
12064
|
+
if (marker && !marker.info.isFile())
|
|
12065
|
+
refuse3(marker.path, "The strict policy marker must be a regular file.");
|
|
12066
|
+
return options.skillexOnly === true || marker !== void 0;
|
|
12067
|
+
}
|
|
12068
|
+
async function assertStrictProfile(profile, options, local) {
|
|
12069
|
+
if (!await strictProfile(profile, options)) return false;
|
|
12070
|
+
for (const item2 of local)
|
|
12071
|
+
if (!hermesMetadata(item2))
|
|
12072
|
+
refuse3(item2.path, `Skillex-only profile contains unowned entry ${item2.name}.`);
|
|
12073
|
+
const path = join31(profile.root, "config.yaml");
|
|
12074
|
+
const config = await entry2(path);
|
|
12075
|
+
if (config) {
|
|
12076
|
+
if (!config.info.isFile())
|
|
12077
|
+
refuse3(path, "Strict profile config must be a regular generated file.");
|
|
12078
|
+
const document = parseDocument3(await readFile12(path, "utf8"), { uniqueKeys: true });
|
|
12079
|
+
if (document.errors.length) refuse3(path, "Cannot validate strict skills configuration.");
|
|
12080
|
+
const dirs = document.toJS()?.skills?.external_dirs;
|
|
12081
|
+
if (dirs !== void 0 && (!Array.isArray(dirs) || dirs.length))
|
|
12082
|
+
refuse3(path, "Skillex-only profiles require skills.external_dirs: [].");
|
|
12083
|
+
}
|
|
12084
|
+
const optOut = await entry2(join31(profile.root, ".no-bundled-skills"));
|
|
12085
|
+
if (optOut && !optOut.info.isFile())
|
|
12086
|
+
refuse3(optOut.path, "Bundled opt-out marker must be a regular file.");
|
|
12087
|
+
return true;
|
|
12088
|
+
}
|
|
12089
|
+
async function publishStrictPolicy(profile) {
|
|
12090
|
+
for (const name of [".no-bundled-skills", ".skillex-only"]) {
|
|
12091
|
+
const path = join31(profile.root, name);
|
|
12092
|
+
const existing = await entry2(path);
|
|
12093
|
+
if (existing) {
|
|
12094
|
+
if (!existing.info.isFile()) refuse3(path, "Policy markers must be regular files.");
|
|
12095
|
+
continue;
|
|
12096
|
+
}
|
|
12097
|
+
const handle = await open13(
|
|
12098
|
+
path,
|
|
12099
|
+
constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW,
|
|
12100
|
+
384
|
|
12101
|
+
);
|
|
12102
|
+
try {
|
|
12103
|
+
await handle.writeFile(
|
|
12104
|
+
"Skillex owns this PM skill projection. Use skillex profile sync; no local installs.\n"
|
|
12105
|
+
);
|
|
12106
|
+
await handle.sync();
|
|
12107
|
+
} finally {
|
|
12108
|
+
await handle.close();
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
|
|
12113
|
+
// src/core/profile-sync.ts
|
|
11890
12114
|
function refused3(code, message, path, fix) {
|
|
11891
12115
|
fail(code, message, { path, fix }, ExitCode.REFUSED);
|
|
11892
12116
|
}
|
|
@@ -11905,14 +12129,14 @@ function receiptOptions(profile, options, scopes = []) {
|
|
|
11905
12129
|
const { home, cwd } = normalized;
|
|
11906
12130
|
const expanded = (value) => resolve17(
|
|
11907
12131
|
cwd,
|
|
11908
|
-
value === "~" ? home : value.startsWith("~/") ?
|
|
12132
|
+
value === "~" ? home : value.startsWith("~/") ? join32(home, value.slice(2)) : value
|
|
11909
12133
|
);
|
|
11910
12134
|
return {
|
|
11911
12135
|
...normalized,
|
|
11912
12136
|
forbiddenRoots: [
|
|
11913
12137
|
.../* @__PURE__ */ new Set([
|
|
11914
12138
|
profile.root,
|
|
11915
|
-
|
|
12139
|
+
join32(home, ".agents"),
|
|
11916
12140
|
...options.project ? [expanded(options.project)] : [],
|
|
11917
12141
|
...options.registryRoot ? [expanded(options.registryRoot)] : [],
|
|
11918
12142
|
...scopes.flatMap((scope) => [
|
|
@@ -11974,7 +12198,7 @@ async function resolveProjection(options) {
|
|
|
11974
12198
|
}
|
|
11975
12199
|
const locations = /* @__PURE__ */ new Map();
|
|
11976
12200
|
for (const scope of scopes) {
|
|
11977
|
-
locations.set(
|
|
12201
|
+
locations.set(join32(scope.registry.root, "all-skills"), "catalog");
|
|
11978
12202
|
if (scope.pack) locations.set(scope.pack.path, "pack");
|
|
11979
12203
|
}
|
|
11980
12204
|
const sources = [];
|
|
@@ -12000,8 +12224,8 @@ async function observations(profile, state) {
|
|
|
12000
12224
|
"Use the explicit migrate workflow to replace this whole-root alias or conflicting file while preserving profile content."
|
|
12001
12225
|
);
|
|
12002
12226
|
if (root)
|
|
12003
|
-
for (const name of (await
|
|
12004
|
-
const path =
|
|
12227
|
+
for (const name of (await readdir21(profile.skillsRoot)).sort()) {
|
|
12228
|
+
const path = join32(profile.skillsRoot, name);
|
|
12005
12229
|
const actual = await entry2(path);
|
|
12006
12230
|
if (!actual) continue;
|
|
12007
12231
|
entries.set(name, actual);
|
|
@@ -12053,7 +12277,7 @@ async function buildWork(profile, state, options) {
|
|
|
12053
12277
|
for (const scope of projection.scopes) {
|
|
12054
12278
|
for (const protectedRoot of [
|
|
12055
12279
|
scope.registry.root,
|
|
12056
|
-
...scope.scope === "project" ? [scope.root] : [
|
|
12280
|
+
...scope.scope === "project" ? [scope.root] : [join32(scope.root, ".agents")]
|
|
12057
12281
|
])
|
|
12058
12282
|
if (within2(profile.skillsRoot, protectedRoot) || within2(protectedRoot, profile.skillsRoot))
|
|
12059
12283
|
refused3(
|
|
@@ -12073,18 +12297,19 @@ async function buildWork(profile, state, options) {
|
|
|
12073
12297
|
);
|
|
12074
12298
|
state = await readProfileState(profile, receiptOptions(profile, options, projection.scopes));
|
|
12075
12299
|
const observed = await observations(profile, state);
|
|
12300
|
+
await assertStrictProfile(profile, options, observed.local);
|
|
12076
12301
|
const links = { ...state.data.links };
|
|
12077
12302
|
const changes = [];
|
|
12078
12303
|
if (!observed.root) changes.push({ action: "mkdir", path: profile.skillsRoot });
|
|
12079
12304
|
for (const [name, claim2] of Object.entries(links))
|
|
12080
12305
|
if (!owned2(observed.entries.get(name), claim2)) {
|
|
12081
12306
|
delete links[name];
|
|
12082
|
-
changes.push({ action: "release", name, path:
|
|
12307
|
+
changes.push({ action: "release", name, path: join32(profile.skillsRoot, name) });
|
|
12083
12308
|
}
|
|
12084
12309
|
const candidates = [];
|
|
12085
12310
|
const desiredNames = new Set(projection.desired.map((desired) => desired.name));
|
|
12086
12311
|
for (const desired of projection.desired) {
|
|
12087
|
-
const path =
|
|
12312
|
+
const path = join32(profile.skillsRoot, desired.name);
|
|
12088
12313
|
const actual = observed.entries.get(desired.name);
|
|
12089
12314
|
const recorded = claim(links, desired.name);
|
|
12090
12315
|
const state2 = actual ? recorded ? lexicalTarget(path, actual.raw ?? "") === desired.target ? "unchanged" : "update" : "shadowed" : "create";
|
|
@@ -12101,7 +12326,7 @@ async function buildWork(profile, state, options) {
|
|
|
12101
12326
|
}
|
|
12102
12327
|
for (const name of Object.keys(links).sort())
|
|
12103
12328
|
if (!desiredNames.has(name))
|
|
12104
|
-
changes.push({ action: "prune", name, path:
|
|
12329
|
+
changes.push({ action: "prune", name, path: join32(profile.skillsRoot, name) });
|
|
12105
12330
|
const next = {
|
|
12106
12331
|
version: 1,
|
|
12107
12332
|
root: observed.root ? identity(observed.root) : null,
|
|
@@ -12139,6 +12364,7 @@ async function inspectProfile(profile, options = {}) {
|
|
|
12139
12364
|
}
|
|
12140
12365
|
try {
|
|
12141
12366
|
preserved = (await observations(profile, state)).local;
|
|
12367
|
+
await assertStrictProfile(profile, options, preserved);
|
|
12142
12368
|
} catch (error) {
|
|
12143
12369
|
const failure6 = asFailure(error);
|
|
12144
12370
|
findings.push(...failure6.findings);
|
|
@@ -12190,9 +12416,9 @@ async function inspectProfile(profile, options = {}) {
|
|
|
12190
12416
|
}
|
|
12191
12417
|
async function flush3(path, expected) {
|
|
12192
12418
|
await requireIdentity(path, expected);
|
|
12193
|
-
const handle = await
|
|
12419
|
+
const handle = await open14(
|
|
12194
12420
|
path,
|
|
12195
|
-
|
|
12421
|
+
constants11.O_RDONLY | constants11.O_DIRECTORY | constants11.O_NOFOLLOW
|
|
12196
12422
|
);
|
|
12197
12423
|
try {
|
|
12198
12424
|
if (!matches2({ path, info: await handle.stat({ bigint: true }) }, expected))
|
|
@@ -12237,7 +12463,7 @@ async function recoverProfile(profile, state, options, mark, findings) {
|
|
|
12237
12463
|
if (!pending || !root) return state;
|
|
12238
12464
|
checkProfileSignal(options);
|
|
12239
12465
|
await rootGuard(profile, root);
|
|
12240
|
-
const path =
|
|
12466
|
+
const path = join32(profile.skillsRoot, pending.name);
|
|
12241
12467
|
let current = await entry2(path);
|
|
12242
12468
|
const parked = await entry2(pending.parked);
|
|
12243
12469
|
const staged = await entry2(pending.stage);
|
|
@@ -12363,8 +12589,8 @@ async function apply2(work, mark, begin) {
|
|
|
12363
12589
|
}
|
|
12364
12590
|
}
|
|
12365
12591
|
const id = randomUUID9();
|
|
12366
|
-
const stage2 =
|
|
12367
|
-
const parked =
|
|
12592
|
+
const stage2 = join32(profile.skillsRoot, `.skillex-tmp-profile-${id}-new`);
|
|
12593
|
+
const parked = join32(profile.skillsRoot, `.skillex-tmp-profile-${id}-old`);
|
|
12368
12594
|
await absent(stage2);
|
|
12369
12595
|
await absent(parked);
|
|
12370
12596
|
let next;
|
|
@@ -12509,7 +12735,7 @@ async function runProfileSync(name, options) {
|
|
|
12509
12735
|
{ findings, exit: readExit(findings, state.data.pending !== void 0) }
|
|
12510
12736
|
);
|
|
12511
12737
|
}
|
|
12512
|
-
if (work && !work.result.changes.length)
|
|
12738
|
+
if (work && !work.result.changes.length && !await strictProfile(profile, options))
|
|
12513
12739
|
return makeResult(
|
|
12514
12740
|
"profile sync",
|
|
12515
12741
|
{ ...work.result, project: work.projection.project, dryRun: false, applied },
|
|
@@ -12537,6 +12763,8 @@ async function runProfileSync(name, options) {
|
|
|
12537
12763
|
});
|
|
12538
12764
|
}
|
|
12539
12765
|
await assertProfileIdentity(selected);
|
|
12766
|
+
if (await assertStrictProfile(selected, options, (await observations(selected, state)).local))
|
|
12767
|
+
await publishStrictPolicy(selected);
|
|
12540
12768
|
const currentProfile = await discoverProfile(name, options);
|
|
12541
12769
|
if (currentProfile.root !== selected.root || currentProfile.skillsRoot !== selected.skillsRoot)
|
|
12542
12770
|
refused3(
|
|
@@ -12574,21 +12802,21 @@ async function runProfileSync(name, options) {
|
|
|
12574
12802
|
const latest = await readProfileState(profile, receiptOptions(profile, options));
|
|
12575
12803
|
const pending = latest.data.pending;
|
|
12576
12804
|
if (pending) {
|
|
12577
|
-
const current = await entry2(
|
|
12805
|
+
const current = await entry2(join32(profile.skillsRoot, pending.name));
|
|
12578
12806
|
if (pending.next && matches2(current, pending.next) && !applied.some((change2) => change2.path === current?.path))
|
|
12579
12807
|
applied.push({
|
|
12580
12808
|
action: pending.previous ? "update" : "create",
|
|
12581
12809
|
name: pending.name,
|
|
12582
|
-
path:
|
|
12810
|
+
path: join32(profile.skillsRoot, pending.name),
|
|
12583
12811
|
target: pending.next.raw
|
|
12584
12812
|
});
|
|
12585
12813
|
if (!pending.next && !current && pending.previous && matches2(await entry2(pending.parked), pending.previous) && !applied.some(
|
|
12586
|
-
(change2) => change2.path ===
|
|
12814
|
+
(change2) => change2.path === join32(profile.skillsRoot, pending.name)
|
|
12587
12815
|
))
|
|
12588
12816
|
applied.push({
|
|
12589
12817
|
action: "prune",
|
|
12590
12818
|
name: pending.name,
|
|
12591
|
-
path:
|
|
12819
|
+
path: join32(profile.skillsRoot, pending.name)
|
|
12592
12820
|
});
|
|
12593
12821
|
}
|
|
12594
12822
|
state = latest;
|
|
@@ -12730,13 +12958,13 @@ async function syncProfile(name, options) {
|
|
|
12730
12958
|
|
|
12731
12959
|
// src/core/selection-commands.ts
|
|
12732
12960
|
import { realpath as realpath24, stat as stat5 } from "node:fs/promises";
|
|
12733
|
-
import { dirname as dirname21, join as
|
|
12961
|
+
import { dirname as dirname21, join as join34, parse as parse4, resolve as resolve19 } from "node:path";
|
|
12734
12962
|
|
|
12735
12963
|
// src/core/selection-manifest.ts
|
|
12736
12964
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
12737
|
-
import { constants as
|
|
12738
|
-
import { link as link7, lstat as lstat19, mkdir as mkdir11, open as
|
|
12739
|
-
import { dirname as dirname20, join as
|
|
12965
|
+
import { constants as constants12 } from "node:fs";
|
|
12966
|
+
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";
|
|
12967
|
+
import { dirname as dirname20, join as join33, resolve as resolve18 } from "node:path";
|
|
12740
12968
|
import { isDeepStrictEqual as isDeepStrictEqual7 } from "node:util";
|
|
12741
12969
|
var SelectionManifestWriteError = class extends SkillexError {
|
|
12742
12970
|
constructor(error, published) {
|
|
@@ -12828,13 +13056,13 @@ async function contextFor3(root) {
|
|
|
12828
13056
|
unsafe2(path, "A scope ancestor is missing or is not a real directory.");
|
|
12829
13057
|
parents2.set(path, info2);
|
|
12830
13058
|
}
|
|
12831
|
-
const agents =
|
|
13059
|
+
const agents = join33(canonical2, ".agents");
|
|
12832
13060
|
const info = await inspect5(agents);
|
|
12833
13061
|
if (info) {
|
|
12834
13062
|
if (!info.isDirectory()) unsafe2(agents, "The scope .agents path must be a real directory.");
|
|
12835
13063
|
parents2.set(agents, info);
|
|
12836
13064
|
}
|
|
12837
|
-
return { root: canonical2, agents, path:
|
|
13065
|
+
return { root: canonical2, agents, path: join33(agents, "skills.json"), parents: parents2 };
|
|
12838
13066
|
}
|
|
12839
13067
|
async function assertParents4(context2) {
|
|
12840
13068
|
for (const [path, expected] of context2.parents) {
|
|
@@ -12851,7 +13079,7 @@ async function readStored2(path) {
|
|
|
12851
13079
|
const first = await inspect5(path);
|
|
12852
13080
|
if (!first) return void 0;
|
|
12853
13081
|
if (!first.isFile()) unsafe2(path, "The selection declaration must be a real regular file.");
|
|
12854
|
-
const handle = await
|
|
13082
|
+
const handle = await open15(path, constants12.O_RDONLY | constants12.O_NOFOLLOW);
|
|
12855
13083
|
try {
|
|
12856
13084
|
const before = await handle.stat({ bigint: true });
|
|
12857
13085
|
if (!before.isFile() || !sameObject2(first, before))
|
|
@@ -12923,9 +13151,9 @@ async function assertPrevious2(context2, previous) {
|
|
|
12923
13151
|
return current;
|
|
12924
13152
|
}
|
|
12925
13153
|
async function flushDirectory2(path, identity5) {
|
|
12926
|
-
const handle = await
|
|
13154
|
+
const handle = await open15(
|
|
12927
13155
|
path,
|
|
12928
|
-
|
|
13156
|
+
constants12.O_RDONLY | constants12.O_DIRECTORY | constants12.O_NOFOLLOW
|
|
12929
13157
|
);
|
|
12930
13158
|
try {
|
|
12931
13159
|
if (!sameObject2(identity5, await handle.stat({ bigint: true })))
|
|
@@ -12993,11 +13221,11 @@ async function writeSelectionManifest(previous, raw) {
|
|
|
12993
13221
|
await flushDirectory2(context2.root, rootIdentity);
|
|
12994
13222
|
}
|
|
12995
13223
|
await assertPrevious2(context2, prior);
|
|
12996
|
-
const path =
|
|
13224
|
+
const path = join33(context2.agents, `.skillex-tmp-${randomUUID10()}`);
|
|
12997
13225
|
const mode = prior.file ? Number(prior.file.identity.mode & 0o7777n) : 420;
|
|
12998
|
-
const file = await
|
|
13226
|
+
const file = await open15(
|
|
12999
13227
|
path,
|
|
13000
|
-
|
|
13228
|
+
constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW,
|
|
13001
13229
|
mode
|
|
13002
13230
|
);
|
|
13003
13231
|
try {
|
|
@@ -13109,7 +13337,7 @@ async function directory(value, cwd, home) {
|
|
|
13109
13337
|
fail("E_PROJECT_ROOT", "A project or working directory must be a nonempty path.", {
|
|
13110
13338
|
fix: "Pass an existing directory as --project or cwd."
|
|
13111
13339
|
});
|
|
13112
|
-
const expanded = value === "~" ? home : value.startsWith("~/") ?
|
|
13340
|
+
const expanded = value === "~" ? home : value.startsWith("~/") ? join34(home, value.slice(2)) : value;
|
|
13113
13341
|
const path = resolve19(cwd, expanded);
|
|
13114
13342
|
const info = await inspectPath(path);
|
|
13115
13343
|
if (!info || !(await stat5(path)).isDirectory())
|
|
@@ -13122,7 +13350,7 @@ async function directory(value, cwd, home) {
|
|
|
13122
13350
|
async function nearestGitRoot(cwd, home) {
|
|
13123
13351
|
let current = cwd;
|
|
13124
13352
|
while (current !== home && current !== parse4(current).root) {
|
|
13125
|
-
if (await inspectPath(
|
|
13353
|
+
if (await inspectPath(join34(current, ".git"))) return current;
|
|
13126
13354
|
current = dirname21(current);
|
|
13127
13355
|
}
|
|
13128
13356
|
return void 0;
|
|
@@ -13289,7 +13517,7 @@ async function prepare3(options, edit) {
|
|
|
13289
13517
|
...options,
|
|
13290
13518
|
home: target2.home,
|
|
13291
13519
|
forbiddenRoots: [
|
|
13292
|
-
|
|
13520
|
+
join34(target2.root, ".agents"),
|
|
13293
13521
|
...target2.scope === "project" ? [target2.root] : []
|
|
13294
13522
|
]
|
|
13295
13523
|
});
|
|
@@ -13443,8 +13671,8 @@ async function setInheritance(enabled, options = {}) {
|
|
|
13443
13671
|
}
|
|
13444
13672
|
|
|
13445
13673
|
// src/core/sets.ts
|
|
13446
|
-
import { readdir as
|
|
13447
|
-
import { join as
|
|
13674
|
+
import { readdir as readdir22 } from "node:fs/promises";
|
|
13675
|
+
import { join as join35 } from "node:path";
|
|
13448
13676
|
function validateName(name) {
|
|
13449
13677
|
parseManifest({ sets: [{ name }] }, "set name");
|
|
13450
13678
|
}
|
|
@@ -13461,12 +13689,12 @@ async function listSets(options = {}) {
|
|
|
13461
13689
|
const command = "set list";
|
|
13462
13690
|
try {
|
|
13463
13691
|
const registry = await discoverRegistry(options);
|
|
13464
|
-
const path =
|
|
13692
|
+
const path = join35(registry.root, "sets");
|
|
13465
13693
|
const sets = [];
|
|
13466
13694
|
const failures = [];
|
|
13467
13695
|
if (await inspectPath(path)) {
|
|
13468
13696
|
await requireDirectory(path, "E_SET_MISSING");
|
|
13469
|
-
for (const entry3 of (await
|
|
13697
|
+
for (const entry3 of (await readdir22(path, { withFileTypes: true })).sort(
|
|
13470
13698
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
13471
13699
|
)) {
|
|
13472
13700
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_") || !entry3.isDirectory() && !entry3.isSymbolicLink())
|
|
@@ -13474,7 +13702,7 @@ async function listSets(options = {}) {
|
|
|
13474
13702
|
try {
|
|
13475
13703
|
sets.push(await readSet(registry.root, entry3.name));
|
|
13476
13704
|
} catch (error) {
|
|
13477
|
-
failures.push(collectedError(error,
|
|
13705
|
+
failures.push(collectedError(error, join35(path, entry3.name)));
|
|
13478
13706
|
}
|
|
13479
13707
|
}
|
|
13480
13708
|
}
|
|
@@ -13496,7 +13724,7 @@ async function showSet(name, options = {}) {
|
|
|
13496
13724
|
async function prepareSet(name, names2, operation, options) {
|
|
13497
13725
|
validateName(name);
|
|
13498
13726
|
const registry = await discoverRegistry(options);
|
|
13499
|
-
const path =
|
|
13727
|
+
const path = join35(registry.root, "sets", name);
|
|
13500
13728
|
const exists = await inspectPath(path);
|
|
13501
13729
|
const previous = operation === "create" && !exists ? { kind: "set", name, path, skills: [] } : await readSet(registry.root, name);
|
|
13502
13730
|
const requested = await requestedSkills(registry.root, names2, path);
|
|
@@ -13512,10 +13740,10 @@ async function prepareSet(name, names2, operation, options) {
|
|
|
13512
13740
|
const plan2 = await newPlan(registry, composition2, options.dryRun === true);
|
|
13513
13741
|
await planDirectory(plan2, path);
|
|
13514
13742
|
for (const skill2 of previous.skills)
|
|
13515
|
-
await inspectCanonicalLink(plan2,
|
|
13743
|
+
await inspectCanonicalLink(plan2, join35(path, skill2.name), skill2.path);
|
|
13516
13744
|
for (const [member, target2] of requested) {
|
|
13517
|
-
if (operation === "add") await planLink(plan2,
|
|
13518
|
-
else if (operation === "remove") await planUnlink(plan2,
|
|
13745
|
+
if (operation === "add") await planLink(plan2, join35(path, member), target2);
|
|
13746
|
+
else if (operation === "remove") await planUnlink(plan2, join35(path, member), target2);
|
|
13519
13747
|
}
|
|
13520
13748
|
return plan2;
|
|
13521
13749
|
}
|
|
@@ -13530,15 +13758,15 @@ async function removeSetSkills(name, names2, options = {}) {
|
|
|
13530
13758
|
}
|
|
13531
13759
|
|
|
13532
13760
|
// src/core/vendor-inspect.ts
|
|
13533
|
-
import { readdir as
|
|
13534
|
-
import { join as
|
|
13761
|
+
import { readdir as readdir23 } from "node:fs/promises";
|
|
13762
|
+
import { join as join36 } from "node:path";
|
|
13535
13763
|
|
|
13536
13764
|
// src/core/vendor-git.ts
|
|
13537
|
-
import { execFile as
|
|
13765
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
13538
13766
|
import { realpath as realpath25 } from "node:fs/promises";
|
|
13539
13767
|
import { posix } from "node:path";
|
|
13540
|
-
import { promisify as
|
|
13541
|
-
var
|
|
13768
|
+
import { promisify as promisify3 } from "node:util";
|
|
13769
|
+
var execute3 = promisify3(execFile4);
|
|
13542
13770
|
var oidPattern = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
13543
13771
|
function vendorInterrupted(options) {
|
|
13544
13772
|
if (options.signal?.aborted) {
|
|
@@ -13568,7 +13796,7 @@ function gitEnvironment(options) {
|
|
|
13568
13796
|
async function git(root, args2, options, missingRef = false, allowAbsent = false) {
|
|
13569
13797
|
vendorInterrupted(options);
|
|
13570
13798
|
try {
|
|
13571
|
-
const result = await
|
|
13799
|
+
const result = await execute3(
|
|
13572
13800
|
"git",
|
|
13573
13801
|
["--no-lazy-fetch", "-c", "protocol.allow=never", "-C", root, ...args2],
|
|
13574
13802
|
{
|
|
@@ -13893,19 +14121,19 @@ var Observation = class {
|
|
|
13893
14121
|
async function recordedInventory(manifest, observation, options) {
|
|
13894
14122
|
const recorded = /* @__PURE__ */ new Map();
|
|
13895
14123
|
const failed2 = /* @__PURE__ */ new Set();
|
|
13896
|
-
const catalog =
|
|
13897
|
-
for (const entry3 of (await
|
|
14124
|
+
const catalog = join36(manifest.registry.root, "all-skills");
|
|
14125
|
+
for (const entry3 of (await readdir23(catalog, { withFileTypes: true })).sort(
|
|
13898
14126
|
(a, b) => a.name.localeCompare(b.name)
|
|
13899
14127
|
)) {
|
|
13900
14128
|
checkVendorSignal(options);
|
|
13901
14129
|
if (!isSkillName(entry3.name) || !entry3.isDirectory()) continue;
|
|
13902
|
-
const path =
|
|
14130
|
+
const path = join36(catalog, entry3.name);
|
|
13903
14131
|
try {
|
|
13904
14132
|
const receipt2 = await readVendorProvenance(path);
|
|
13905
14133
|
if (receipt2) recorded.set(entry3.name, receipt2);
|
|
13906
14134
|
} catch (error) {
|
|
13907
14135
|
failed2.add(entry3.name);
|
|
13908
|
-
observation.capture(error,
|
|
14136
|
+
observation.capture(error, join36(path, ".source.yaml"));
|
|
13909
14137
|
}
|
|
13910
14138
|
}
|
|
13911
14139
|
return { recorded, failed: failed2 };
|
|
@@ -13922,7 +14150,7 @@ function emptySkill(source, manifest, name, upstreamCommit) {
|
|
|
13922
14150
|
return {
|
|
13923
14151
|
name,
|
|
13924
14152
|
source: source.name,
|
|
13925
|
-
path:
|
|
14153
|
+
path: join36(manifest.registry.root, "all-skills", name),
|
|
13926
14154
|
state: "unknown",
|
|
13927
14155
|
recordedCommit: null,
|
|
13928
14156
|
upstreamCommit,
|
|
@@ -14303,9 +14531,9 @@ async function inspectVendorStatus(options = {}) {
|
|
|
14303
14531
|
|
|
14304
14532
|
// src/core/vendor-sync.ts
|
|
14305
14533
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
14306
|
-
import { chmod as chmod3, lstat as lstat20, mkdir as mkdir12, open as
|
|
14307
|
-
import { dirname as dirname22, join as
|
|
14308
|
-
function
|
|
14534
|
+
import { chmod as chmod3, lstat as lstat20, mkdir as mkdir12, open as open16, readdir as readdir24, rename as rename10 } from "node:fs/promises";
|
|
14535
|
+
import { dirname as dirname22, join as join37 } from "node:path";
|
|
14536
|
+
function refuse4(code, message, path, fix) {
|
|
14309
14537
|
fail(code, message, { path, fix }, ExitCode.REFUSED);
|
|
14310
14538
|
}
|
|
14311
14539
|
function attributed(provenance, source) {
|
|
@@ -14315,21 +14543,21 @@ function baseline(provenance) {
|
|
|
14315
14543
|
return provenance.digest !== null && (provenance.digestFormat === null || ["skillex-tree-v1", "skillex-tree-v1+symlinks"].includes(provenance.digestFormat));
|
|
14316
14544
|
}
|
|
14317
14545
|
async function planSkill(source, skill2, commit, extractedAt, catalog, options) {
|
|
14318
|
-
const path =
|
|
14546
|
+
const path = join37(catalog, skill2.name);
|
|
14319
14547
|
const info = await vendorLstat(path);
|
|
14320
14548
|
let before = null;
|
|
14321
14549
|
let provenance = null;
|
|
14322
14550
|
let action = "create";
|
|
14323
14551
|
if (info) {
|
|
14324
14552
|
if (info.isSymbolicLink())
|
|
14325
|
-
|
|
14553
|
+
refuse4(
|
|
14326
14554
|
"E_VENDOR_DESTINATION_LINK",
|
|
14327
14555
|
"Vendor sync will not replace a linked canonical destination.",
|
|
14328
14556
|
path,
|
|
14329
14557
|
"Use the explicit migrate workflow to account for this link and its consumers before vendoring."
|
|
14330
14558
|
);
|
|
14331
14559
|
if (!info.isDirectory())
|
|
14332
|
-
|
|
14560
|
+
refuse4(
|
|
14333
14561
|
"E_VENDOR_DESTINATION_TYPE",
|
|
14334
14562
|
"The canonical destination is not a real skill directory.",
|
|
14335
14563
|
path,
|
|
@@ -14339,21 +14567,21 @@ async function planSkill(source, skill2, commit, extractedAt, catalog, options)
|
|
|
14339
14567
|
const digest = await digestVendorTree(path);
|
|
14340
14568
|
if (provenance?.type === "vendored") {
|
|
14341
14569
|
if (!attributed(provenance, source))
|
|
14342
|
-
|
|
14570
|
+
refuse4(
|
|
14343
14571
|
"E_VENDOR_FOREIGN_SOURCE",
|
|
14344
14572
|
"The canonical skill is attributed to a different source.",
|
|
14345
14573
|
path,
|
|
14346
14574
|
"Correct the declaration or migrate the existing provenance explicitly; adopt does not transfer another vendor's ownership."
|
|
14347
14575
|
);
|
|
14348
14576
|
if (!baseline(provenance) && !options.discardLocalEdits)
|
|
14349
|
-
|
|
14577
|
+
refuse4(
|
|
14350
14578
|
"E_VENDOR_BASELINE_MISSING",
|
|
14351
14579
|
"The managed skill has no supported baseline digest proving unchanged content.",
|
|
14352
14580
|
path,
|
|
14353
14581
|
"Preserve or commit local content, then explicitly use --discard-local-edits to replace this attributed skill; it cannot be implicitly pruned."
|
|
14354
14582
|
);
|
|
14355
14583
|
if (baseline(provenance) && (digest !== provenance.digest || provenance.modifiedLocally) && !options.discardLocalEdits)
|
|
14356
|
-
|
|
14584
|
+
refuse4(
|
|
14357
14585
|
"E_VENDOR_LOCAL_EDITS",
|
|
14358
14586
|
"The vendored skill has local content or executable-mode edits.",
|
|
14359
14587
|
path,
|
|
@@ -14362,14 +14590,14 @@ async function planSkill(source, skill2, commit, extractedAt, catalog, options)
|
|
|
14362
14590
|
action = digest === skill2.digest && !provenance.modifiedLocally && provenance.digest === skill2.digest && provenance.upstreamCommit === commit && provenance.upstreamTree === skill2.tree && provenance.upstreamPath === skill2.upstreamPath && provenance.upstreamVersion === source.version && provenance.extractedAt !== null && baseline(provenance) ? "unchanged" : "update";
|
|
14363
14591
|
} else {
|
|
14364
14592
|
if (!options.adopt)
|
|
14365
|
-
|
|
14593
|
+
refuse4(
|
|
14366
14594
|
"E_VENDOR_UNMANAGED",
|
|
14367
14595
|
"The canonical skill is not managed by this vendor source.",
|
|
14368
14596
|
path,
|
|
14369
14597
|
"Use --adopt to record ownership; if bytes or modes differ from upstream, also use --discard-local-edits after preserving them."
|
|
14370
14598
|
);
|
|
14371
14599
|
if (digest !== skill2.digest && !options.discardLocalEdits)
|
|
14372
|
-
|
|
14600
|
+
refuse4(
|
|
14373
14601
|
"E_VENDOR_LOCAL_EDITS",
|
|
14374
14602
|
"Adopting this skill would replace local bytes or executable modes.",
|
|
14375
14603
|
path,
|
|
@@ -14379,14 +14607,14 @@ async function planSkill(source, skill2, commit, extractedAt, catalog, options)
|
|
|
14379
14607
|
}
|
|
14380
14608
|
before = await captureVendorTree(path);
|
|
14381
14609
|
if (before.root.dev !== String((await lstat20(catalog, { bigint: true })).dev))
|
|
14382
|
-
|
|
14610
|
+
refuse4(
|
|
14383
14611
|
"E_VENDOR_CROSS_DEVICE",
|
|
14384
14612
|
"The existing skill is mounted on a different filesystem from catalog staging.",
|
|
14385
14613
|
path,
|
|
14386
14614
|
"Move the skill into the catalog filesystem before updating it; vendor replacement requires same-filesystem renames."
|
|
14387
14615
|
);
|
|
14388
14616
|
if (before.root.dev !== String(info.dev) || before.root.ino !== String(info.ino))
|
|
14389
|
-
|
|
14617
|
+
refuse4(
|
|
14390
14618
|
"E_VENDOR_CHANGED",
|
|
14391
14619
|
"The destination changed during preflight.",
|
|
14392
14620
|
path,
|
|
@@ -14425,7 +14653,7 @@ async function planSkill(source, skill2, commit, extractedAt, catalog, options)
|
|
|
14425
14653
|
async function prepare4(options, extractedAt) {
|
|
14426
14654
|
vendorInterrupted(options);
|
|
14427
14655
|
const manifest = await readVendorSources(options);
|
|
14428
|
-
const catalog =
|
|
14656
|
+
const catalog = join37(manifest.registry.root, "all-skills");
|
|
14429
14657
|
const journal = await readVendorJournal(manifest.registry, options);
|
|
14430
14658
|
const selected = selectVendorSources(manifest, options.sources);
|
|
14431
14659
|
const sources = [];
|
|
@@ -14437,7 +14665,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14437
14665
|
const checkout = await resolveVendorCheckout(source, options);
|
|
14438
14666
|
if (!checkout.root) {
|
|
14439
14667
|
if (!source.optional)
|
|
14440
|
-
|
|
14668
|
+
refuse4(
|
|
14441
14669
|
"E_SOURCE_CHECKOUT_MISSING",
|
|
14442
14670
|
`Local checkout ${source.checkout} is unavailable.`,
|
|
14443
14671
|
manifest.path,
|
|
@@ -14490,7 +14718,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14490
14718
|
});
|
|
14491
14719
|
for (const skill2 of upstream.skills) {
|
|
14492
14720
|
if (names2.has(skill2.name))
|
|
14493
|
-
|
|
14721
|
+
refuse4(
|
|
14494
14722
|
"E_VENDOR_DUPLICATE_SKILL",
|
|
14495
14723
|
`Sources ${names2.get(skill2.name)} and ${source.name} select the same canonical skill.`,
|
|
14496
14724
|
manifest.path,
|
|
@@ -14501,9 +14729,9 @@ async function prepare4(options, extractedAt) {
|
|
|
14501
14729
|
}
|
|
14502
14730
|
}
|
|
14503
14731
|
if (options.prune) {
|
|
14504
|
-
for (const name of (await
|
|
14732
|
+
for (const name of (await readdir24(catalog)).filter(isSkillName).sort()) {
|
|
14505
14733
|
if (names2.has(name)) continue;
|
|
14506
|
-
const path =
|
|
14734
|
+
const path = join37(catalog, name);
|
|
14507
14735
|
if (!(await vendorLstat(path))?.isDirectory()) continue;
|
|
14508
14736
|
let provenance;
|
|
14509
14737
|
try {
|
|
@@ -14523,14 +14751,14 @@ async function prepare4(options, extractedAt) {
|
|
|
14523
14751
|
const source = provenance?.source ? enumerated.get(provenance.source) : void 0;
|
|
14524
14752
|
if (!provenance || !source || !attributed(provenance, source)) continue;
|
|
14525
14753
|
if (!baseline(provenance))
|
|
14526
|
-
|
|
14754
|
+
refuse4(
|
|
14527
14755
|
"E_VENDOR_BASELINE_MISSING",
|
|
14528
14756
|
"An orphaned managed skill has no supported baseline proving it is safe to prune.",
|
|
14529
14757
|
path,
|
|
14530
14758
|
"Restore its verified provenance or remove it explicitly after inspection; --discard-local-edits never authorizes pruning unknown content."
|
|
14531
14759
|
);
|
|
14532
14760
|
if (provenance.modifiedLocally || await digestVendorTree(path) !== provenance.digest)
|
|
14533
|
-
|
|
14761
|
+
refuse4(
|
|
14534
14762
|
"E_VENDOR_LOCAL_EDITS",
|
|
14535
14763
|
"An orphaned managed skill has local edits and cannot be pruned.",
|
|
14536
14764
|
path,
|
|
@@ -14564,7 +14792,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14564
14792
|
}
|
|
14565
14793
|
async function absent2(path) {
|
|
14566
14794
|
if (await vendorLstat(path))
|
|
14567
|
-
|
|
14795
|
+
refuse4(
|
|
14568
14796
|
"E_VENDOR_CHANGED",
|
|
14569
14797
|
"A destination or recovery path appeared after preflight.",
|
|
14570
14798
|
path,
|
|
@@ -14576,7 +14804,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14576
14804
|
const selected = selectVendorSources(current, options.sources);
|
|
14577
14805
|
const expected = selectVendorSources(prepared.manifest, options.sources);
|
|
14578
14806
|
if (current.registry.root !== prepared.manifest.registry.root || JSON.stringify(selected) !== JSON.stringify(expected))
|
|
14579
|
-
|
|
14807
|
+
refuse4(
|
|
14580
14808
|
"E_VENDOR_INTENT_CHANGED",
|
|
14581
14809
|
"The selected vendor declarations changed during staging.",
|
|
14582
14810
|
prepared.manifest.path,
|
|
@@ -14586,7 +14814,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14586
14814
|
const checkout = await resolveVendorCheckout(source, options);
|
|
14587
14815
|
const previous = prepared.result.sources.find((entry3) => entry3.name === source.name);
|
|
14588
14816
|
if (!previous || checkout.root !== previous.checkout)
|
|
14589
|
-
|
|
14817
|
+
refuse4(
|
|
14590
14818
|
"E_VENDOR_INTENT_CHANGED",
|
|
14591
14819
|
"A selected local checkout mapping changed during staging.",
|
|
14592
14820
|
prepared.manifest.path,
|
|
@@ -14595,7 +14823,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14595
14823
|
}
|
|
14596
14824
|
}
|
|
14597
14825
|
async function syncDirectory2(path) {
|
|
14598
|
-
const handle = await
|
|
14826
|
+
const handle = await open16(path, "r");
|
|
14599
14827
|
try {
|
|
14600
14828
|
await handle.sync();
|
|
14601
14829
|
} finally {
|
|
@@ -14610,12 +14838,12 @@ async function stage(plan2, path, catalog, options) {
|
|
|
14610
14838
|
for (const entry3 of plan2.entries) {
|
|
14611
14839
|
vendorInterrupted(options);
|
|
14612
14840
|
await assertVendorIdentity(path, root);
|
|
14613
|
-
const target2 =
|
|
14841
|
+
const target2 = join37(path, entry3.path);
|
|
14614
14842
|
let parent = dirname22(entry3.path);
|
|
14615
14843
|
while (parent !== ".") {
|
|
14616
14844
|
const identity5 = parents2.get(parent);
|
|
14617
14845
|
if (!identity5) throw new Error(`Unprepared vendor content parent: ${parent}`);
|
|
14618
|
-
await assertVendorIdentity(
|
|
14846
|
+
await assertVendorIdentity(join37(path, parent), identity5);
|
|
14619
14847
|
parent = dirname22(parent);
|
|
14620
14848
|
}
|
|
14621
14849
|
if (entry3.kind === "directory") {
|
|
@@ -14623,7 +14851,7 @@ async function stage(plan2, path, catalog, options) {
|
|
|
14623
14851
|
await chmod3(target2, 493);
|
|
14624
14852
|
parents2.set(entry3.path, vendorIdentity(await lstat20(target2, { bigint: true })));
|
|
14625
14853
|
} else if (entry3.kind === "file") {
|
|
14626
|
-
const handle = await
|
|
14854
|
+
const handle = await open16(target2, "wx", entry3.mode);
|
|
14627
14855
|
try {
|
|
14628
14856
|
await handle.writeFile(entry3.bytes);
|
|
14629
14857
|
await handle.chmod(entry3.mode);
|
|
@@ -14672,7 +14900,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14672
14900
|
if (published) {
|
|
14673
14901
|
await assertVendorTree(operation.change.path, operation.after);
|
|
14674
14902
|
if (stageInfo)
|
|
14675
|
-
|
|
14903
|
+
refuse4(
|
|
14676
14904
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14677
14905
|
"Both the staged and published paths exist for one prepared skill.",
|
|
14678
14906
|
operation.change.path,
|
|
@@ -14680,7 +14908,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14680
14908
|
);
|
|
14681
14909
|
} else if (destination) {
|
|
14682
14910
|
if (!operation.before)
|
|
14683
|
-
|
|
14911
|
+
refuse4(
|
|
14684
14912
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14685
14913
|
"A foreign destination blocks vendor recovery.",
|
|
14686
14914
|
operation.change.path,
|
|
@@ -14688,7 +14916,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14688
14916
|
);
|
|
14689
14917
|
await assertVendorTree(operation.change.path, operation.before);
|
|
14690
14918
|
if (parked)
|
|
14691
|
-
|
|
14919
|
+
refuse4(
|
|
14692
14920
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14693
14921
|
"Both the old destination and its parked path exist.",
|
|
14694
14922
|
operation.change.path,
|
|
@@ -14696,7 +14924,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14696
14924
|
);
|
|
14697
14925
|
} else if (!parked && operation.before) {
|
|
14698
14926
|
if (operation.change.action !== "prune")
|
|
14699
|
-
|
|
14927
|
+
refuse4(
|
|
14700
14928
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14701
14929
|
"The old and new skill directories are both missing.",
|
|
14702
14930
|
operation.change.path,
|
|
@@ -14769,7 +14997,7 @@ async function syncVendorSources(options = {}) {
|
|
|
14769
14997
|
initiallyPending = initialJournal.document !== null;
|
|
14770
14998
|
fallback = {
|
|
14771
14999
|
registry,
|
|
14772
|
-
manifest:
|
|
15000
|
+
manifest: join37(registry.root, "all-skills", "sources.toml"),
|
|
14773
15001
|
dryRun: options.dryRun === true,
|
|
14774
15002
|
sources: [],
|
|
14775
15003
|
changes: [],
|
|
@@ -14816,7 +15044,7 @@ async function syncVendorSources(options = {}) {
|
|
|
14816
15044
|
}
|
|
14817
15045
|
prepared = await prepare4(options, extractedAt);
|
|
14818
15046
|
if (prepared.manifest.registry.root !== initialRoot)
|
|
14819
|
-
|
|
15047
|
+
refuse4(
|
|
14820
15048
|
"E_CATALOG_CHANGED",
|
|
14821
15049
|
"Registry discovery changed while waiting for its catalog lock.",
|
|
14822
15050
|
initialRoot,
|
|
@@ -14838,8 +15066,8 @@ async function syncVendorSources(options = {}) {
|
|
|
14838
15066
|
change: plan2.change,
|
|
14839
15067
|
before: plan2.before,
|
|
14840
15068
|
after: null,
|
|
14841
|
-
stage: plan2.change.action === "prune" ? null :
|
|
14842
|
-
parked: plan2.before ?
|
|
15069
|
+
stage: plan2.change.action === "prune" ? null : join37(journal.catalog, `.skillex-tmp-vendor-${id}-new`),
|
|
15070
|
+
parked: plan2.before ? join37(journal.catalog, `.skillex-tmp-vendor-${id}-old`) : null
|
|
14843
15071
|
};
|
|
14844
15072
|
});
|
|
14845
15073
|
vendorInterrupted(options);
|
|
@@ -15371,14 +15599,18 @@ function registerProfileCommands(program2, output2) {
|
|
|
15371
15599
|
).option(
|
|
15372
15600
|
"--dry-run",
|
|
15373
15601
|
"Preview links, preserved entries, and receipts without writing or recovering."
|
|
15602
|
+
).option(
|
|
15603
|
+
"--skillex-only",
|
|
15604
|
+
"Persist strict catalog-only ownership; refuse local skills and external discovery."
|
|
15374
15605
|
).action(async (name, _local, command) => {
|
|
15375
|
-
const { project, dryRun } = command.opts();
|
|
15606
|
+
const { project, dryRun, skillexOnly } = command.opts();
|
|
15376
15607
|
await run2(
|
|
15377
15608
|
command,
|
|
15378
15609
|
(options) => syncProfile(name, {
|
|
15379
15610
|
...options,
|
|
15380
15611
|
project,
|
|
15381
|
-
...dryRun === void 0 ? {} : { dryRun }
|
|
15612
|
+
...dryRun === void 0 ? {} : { dryRun },
|
|
15613
|
+
...skillexOnly === void 0 ? {} : { skillexOnly }
|
|
15382
15614
|
}),
|
|
15383
15615
|
syncText
|
|
15384
15616
|
);
|