@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/index.js
CHANGED
|
@@ -515,7 +515,8 @@ var PROJECT_CLI_ALIASES = Object.freeze([
|
|
|
515
515
|
".gemini/skills",
|
|
516
516
|
".copilot/skills",
|
|
517
517
|
".opencode/skills",
|
|
518
|
-
".kimi-code/skills"
|
|
518
|
+
".kimi-code/skills",
|
|
519
|
+
".zcode/skills"
|
|
519
520
|
]);
|
|
520
521
|
var NEVER_TOUCH = Object.freeze([".hermes", ".augment", ".cursor", ".crush"]);
|
|
521
522
|
function aliasPaths(base, scope) {
|
|
@@ -526,7 +527,7 @@ function aliasPaths(base, scope) {
|
|
|
526
527
|
}
|
|
527
528
|
|
|
528
529
|
// src/core/catalog.ts
|
|
529
|
-
import { readdir as
|
|
530
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
530
531
|
import { join as join7 } from "node:path";
|
|
531
532
|
|
|
532
533
|
// src/core/composition.ts
|
|
@@ -949,10 +950,12 @@ async function packInventory(registry, selection2) {
|
|
|
949
950
|
}
|
|
950
951
|
|
|
951
952
|
// src/core/discovery.ts
|
|
952
|
-
import {
|
|
953
|
+
import { execFile } from "node:child_process";
|
|
954
|
+
import { lstat as lstat3, readdir as readdir3, readFile as readFile3, realpath as realpath4, stat } from "node:fs/promises";
|
|
953
955
|
import { homedir as homedir2 } from "node:os";
|
|
954
956
|
import { dirname as dirname2, join as join5, parse, resolve as resolve3 } from "node:path";
|
|
955
957
|
import { fileURLToPath } from "node:url";
|
|
958
|
+
import { promisify } from "node:util";
|
|
956
959
|
var manifestParts = [".agents", "skills.json"];
|
|
957
960
|
function errorMessage(error) {
|
|
958
961
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -1102,7 +1105,70 @@ async function installedPackageRoot() {
|
|
|
1102
1105
|
current = parent;
|
|
1103
1106
|
}
|
|
1104
1107
|
}
|
|
1105
|
-
|
|
1108
|
+
var execute = promisify(execFile);
|
|
1109
|
+
async function recordedCatalog(root) {
|
|
1110
|
+
const env = {};
|
|
1111
|
+
for (const [key2, value] of Object.entries(process.env))
|
|
1112
|
+
if (!key2.startsWith("GIT_")) env[key2] = value;
|
|
1113
|
+
let stdout;
|
|
1114
|
+
try {
|
|
1115
|
+
({ stdout } = await execute(
|
|
1116
|
+
"git",
|
|
1117
|
+
["-C", root, "ls-files", "--stage", "--full-name", "-z", "--", "all-skills"],
|
|
1118
|
+
{
|
|
1119
|
+
encoding: "utf8",
|
|
1120
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
1121
|
+
env: {
|
|
1122
|
+
...env,
|
|
1123
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
1124
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1125
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
1126
|
+
GIT_CONFIG_GLOBAL: "/dev/null"
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
));
|
|
1130
|
+
} catch {
|
|
1131
|
+
return void 0;
|
|
1132
|
+
}
|
|
1133
|
+
const records = stdout.split("\0").filter(Boolean);
|
|
1134
|
+
if (records.some((record9) => /^160000 [0-9a-f]+ \d\tall-skills$/.test(record9))) return "gitlink";
|
|
1135
|
+
if (records.some((record9) => /^\d{6} [0-9a-f]+ \d\tall-skills\//.test(record9))) return "tree";
|
|
1136
|
+
return void 0;
|
|
1137
|
+
}
|
|
1138
|
+
async function holdsDefinition(catalog, names2) {
|
|
1139
|
+
for (const name of names2) {
|
|
1140
|
+
try {
|
|
1141
|
+
if ((await stat(join5(catalog, name, "SKILL.md"))).isFile()) return true;
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
async function uninitializedCatalog(root, catalog) {
|
|
1148
|
+
let declaration;
|
|
1149
|
+
try {
|
|
1150
|
+
declaration = await readFile3(join5(root, ".gitmodules"), "utf8");
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) {
|
|
1153
|
+
return void 0;
|
|
1154
|
+
}
|
|
1155
|
+
return ioFailure2(join5(root, ".gitmodules"), error);
|
|
1156
|
+
}
|
|
1157
|
+
if (!/^[\t ]*path[\t ]*=[\t ]*"?all-skills\/?"?[\t ]*$/m.test(declaration)) return void 0;
|
|
1158
|
+
if (await entry(join5(catalog, ".git"))) return void 0;
|
|
1159
|
+
let names2;
|
|
1160
|
+
try {
|
|
1161
|
+
names2 = (await readdir3(catalog)).sort();
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
return ioFailure2(catalog, error);
|
|
1164
|
+
}
|
|
1165
|
+
if (!names2.length) return names2;
|
|
1166
|
+
const recorded = await recordedCatalog(root);
|
|
1167
|
+
if (recorded === "gitlink") return names2;
|
|
1168
|
+
if (recorded === "tree") return void 0;
|
|
1169
|
+
return await holdsDefinition(catalog, names2) ? void 0 : names2;
|
|
1170
|
+
}
|
|
1171
|
+
async function catalogRoot(path, required, source) {
|
|
1106
1172
|
const found = await entry(path);
|
|
1107
1173
|
if (!found) {
|
|
1108
1174
|
if (!required) return void 0;
|
|
@@ -1133,6 +1199,21 @@ async function catalogRoot(path, required) {
|
|
|
1133
1199
|
fix: "Select the owning catalog checkout, or run skillex migrate to repair its topology."
|
|
1134
1200
|
});
|
|
1135
1201
|
}
|
|
1202
|
+
const leftovers = await uninitializedCatalog(root, catalog);
|
|
1203
|
+
if (leftovers) {
|
|
1204
|
+
const quoted = JSON.stringify(root);
|
|
1205
|
+
const more = leftovers.length > 8 ? `, and ${leftovers.length - 8} more` : "";
|
|
1206
|
+
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 ` : "";
|
|
1207
|
+
const step = (text2) => clear ? `${clear}${text2}` : `${text2.charAt(0).toUpperCase()}${text2.slice(1)}`;
|
|
1208
|
+
fail(
|
|
1209
|
+
"E_REGISTRY_ROOT",
|
|
1210
|
+
`The registry's all-skills/ catalog is an uninitialized git submodule: ${catalog}`,
|
|
1211
|
+
{
|
|
1212
|
+
path: catalog,
|
|
1213
|
+
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.`
|
|
1214
|
+
}
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1136
1217
|
return root;
|
|
1137
1218
|
}
|
|
1138
1219
|
async function discoverRegistry(options = {}) {
|
|
@@ -1144,7 +1225,7 @@ async function discoverRegistry(options = {}) {
|
|
|
1144
1225
|
const path = absolutePath(value, cwd, home, "E_REGISTRY_ROOT", "registry root");
|
|
1145
1226
|
if (searched.includes(path)) return void 0;
|
|
1146
1227
|
searched.push(path);
|
|
1147
|
-
const root = await catalogRoot(path, required);
|
|
1228
|
+
const root = await catalogRoot(path, required, source);
|
|
1148
1229
|
return root ? { root, source, searched: [...searched] } : void 0;
|
|
1149
1230
|
};
|
|
1150
1231
|
if (options.registryRoot !== void 0) {
|
|
@@ -1190,7 +1271,7 @@ async function discoverRegistry(options = {}) {
|
|
|
1190
1271
|
}
|
|
1191
1272
|
|
|
1192
1273
|
// src/core/metadata.ts
|
|
1193
|
-
import { readFile as
|
|
1274
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1194
1275
|
import { join as join6 } from "node:path";
|
|
1195
1276
|
import { parseDocument } from "yaml";
|
|
1196
1277
|
function mapping(value) {
|
|
@@ -1233,7 +1314,7 @@ async function textFile(path, missing) {
|
|
|
1233
1314
|
}
|
|
1234
1315
|
let bytes;
|
|
1235
1316
|
try {
|
|
1236
|
-
bytes = await
|
|
1317
|
+
bytes = await readFile4(path);
|
|
1237
1318
|
} catch {
|
|
1238
1319
|
fail(
|
|
1239
1320
|
"E_IO",
|
|
@@ -1438,7 +1519,7 @@ async function compositionReferences(registry, findings) {
|
|
|
1438
1519
|
await inspectPack(name, path);
|
|
1439
1520
|
inspected += 1;
|
|
1440
1521
|
}
|
|
1441
|
-
for (const entry3 of (await
|
|
1522
|
+
for (const entry3 of (await readdir4(path, { withFileTypes: true })).sort(
|
|
1442
1523
|
(a, b) => compare(a.name, b.name)
|
|
1443
1524
|
)) {
|
|
1444
1525
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_")) continue;
|
|
@@ -1483,7 +1564,7 @@ async function compositionReferences(registry, findings) {
|
|
|
1483
1564
|
try {
|
|
1484
1565
|
if (!await inspectPath(path)) continue;
|
|
1485
1566
|
await requireDirectory(path, kind2 === "set" ? "E_SET_MISSING" : "E_PACK_MISSING");
|
|
1486
|
-
const entries = (await
|
|
1567
|
+
const entries = (await readdir4(path, { withFileTypes: true })).sort(
|
|
1487
1568
|
(a, b) => compare(a.name, b.name)
|
|
1488
1569
|
);
|
|
1489
1570
|
for (const entry3 of entries) {
|
|
@@ -1521,7 +1602,7 @@ async function listSkills(options = {}) {
|
|
|
1521
1602
|
let validDefinitions = 0;
|
|
1522
1603
|
const catalog = join7(registry.root, "all-skills");
|
|
1523
1604
|
const query = options.query?.trim().toLowerCase();
|
|
1524
|
-
for (const entry3 of (await
|
|
1605
|
+
for (const entry3 of (await readdir4(catalog, { withFileTypes: true })).sort(
|
|
1525
1606
|
(a, b) => compare(a.name, b.name)
|
|
1526
1607
|
)) {
|
|
1527
1608
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_")) continue;
|
|
@@ -1570,7 +1651,7 @@ async function showSkill(name, options = {}) {
|
|
|
1570
1651
|
}
|
|
1571
1652
|
|
|
1572
1653
|
// src/core/catalog-write.ts
|
|
1573
|
-
import { chmod, lstat as lstat8, mkdir as mkdir4, open as open4, readFile as
|
|
1654
|
+
import { chmod, lstat as lstat8, mkdir as mkdir4, open as open4, readFile as readFile7, realpath as realpath9, symlink } from "node:fs/promises";
|
|
1574
1655
|
import { homedir as homedir5 } from "node:os";
|
|
1575
1656
|
import { dirname as dirname6, join as join12, resolve as resolve6 } from "node:path";
|
|
1576
1657
|
import { stringify } from "yaml";
|
|
@@ -1586,8 +1667,8 @@ import {
|
|
|
1586
1667
|
lstat as lstat4,
|
|
1587
1668
|
mkdir as mkdir2,
|
|
1588
1669
|
open as open2,
|
|
1589
|
-
readdir as
|
|
1590
|
-
readFile as
|
|
1670
|
+
readdir as readdir5,
|
|
1671
|
+
readFile as readFile5,
|
|
1591
1672
|
realpath as realpath5,
|
|
1592
1673
|
rmdir,
|
|
1593
1674
|
unlink as unlink2
|
|
@@ -1692,7 +1773,7 @@ async function claimFiles(claim2) {
|
|
|
1692
1773
|
if (!await sameDirectory(claim2.path, claim2.identity)) return void 0;
|
|
1693
1774
|
let names2;
|
|
1694
1775
|
try {
|
|
1695
|
-
names2 = await
|
|
1776
|
+
names2 = await readdir5(claim2.path);
|
|
1696
1777
|
} catch (error) {
|
|
1697
1778
|
if (error.code === "ENOENT") return void 0;
|
|
1698
1779
|
throw error;
|
|
@@ -1725,7 +1806,7 @@ async function readTicket(claim2, resource) {
|
|
|
1725
1806
|
if (stat6.size > 4096) stateFailure(path, "Lock ticket is too large to be a valid claim.");
|
|
1726
1807
|
let raw;
|
|
1727
1808
|
try {
|
|
1728
|
-
raw = JSON.parse(await
|
|
1809
|
+
raw = JSON.parse(await readFile5(path, "utf8"));
|
|
1729
1810
|
} catch (error) {
|
|
1730
1811
|
if (error.code === "ENOENT") return void 0;
|
|
1731
1812
|
if (error instanceof SyntaxError) stateFailure(path, "Lock ticket is malformed JSON.");
|
|
@@ -1757,7 +1838,7 @@ async function removeClaim(claim2, expectedTicket, ownedTicket) {
|
|
|
1757
1838
|
if (expectedTicket !== void 0 && names2.includes(ticketFile)) {
|
|
1758
1839
|
let current;
|
|
1759
1840
|
try {
|
|
1760
|
-
current = await
|
|
1841
|
+
current = await readFile5(join8(claim2.path, ticketFile), "utf8");
|
|
1761
1842
|
} catch (error) {
|
|
1762
1843
|
if (error.code !== "ENOENT") throw error;
|
|
1763
1844
|
}
|
|
@@ -1789,7 +1870,7 @@ async function removeClaim(claim2, expectedTicket, ownedTicket) {
|
|
|
1789
1870
|
async function claims(path, directory2, resource, localHostHash) {
|
|
1790
1871
|
if (!await sameDirectory(path, directory2)) stateFailure(path, "Lock directory disappeared.");
|
|
1791
1872
|
const result = [];
|
|
1792
|
-
for (const name of (await
|
|
1873
|
+
for (const name of (await readdir5(path)).sort()) {
|
|
1793
1874
|
const claimPath = join8(path, name);
|
|
1794
1875
|
const identity4 = identityFrom(name, claimPath);
|
|
1795
1876
|
const stat6 = await inspect2(claimPath);
|
|
@@ -1940,7 +2021,7 @@ import {
|
|
|
1940
2021
|
lstat as lstat6,
|
|
1941
2022
|
mkdir as mkdir3,
|
|
1942
2023
|
open as open3,
|
|
1943
|
-
readdir as
|
|
2024
|
+
readdir as readdir7,
|
|
1944
2025
|
realpath as realpath7,
|
|
1945
2026
|
rename as rename2,
|
|
1946
2027
|
rmdir as rmdir2,
|
|
@@ -1951,16 +2032,14 @@ import { dirname as dirname5, join as join10, relative as relative3, resolve as
|
|
|
1951
2032
|
|
|
1952
2033
|
// src/core/content.ts
|
|
1953
2034
|
import { createHash as createHash3 } from "node:crypto";
|
|
1954
|
-
import { lstat as lstat5, readdir as
|
|
2035
|
+
import { lstat as lstat5, readdir as readdir6, readFile as readFile6, readlink, realpath as realpath6 } from "node:fs/promises";
|
|
1955
2036
|
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join9, relative as relative2, sep as sep3 } from "node:path";
|
|
1956
2037
|
function isWithin(root, path) {
|
|
1957
2038
|
const child = relative2(root, path);
|
|
1958
2039
|
return child === "" || !isAbsolute2(child) && child !== ".." && !child.startsWith(`..${sep3}`);
|
|
1959
2040
|
}
|
|
2041
|
+
var repositoryNames = /* @__PURE__ */ new Set([".git", ".hg", ".svn"]);
|
|
1960
2042
|
var generatedNames = /* @__PURE__ */ new Set([
|
|
1961
|
-
".git",
|
|
1962
|
-
".hg",
|
|
1963
|
-
".svn",
|
|
1964
2043
|
".DS_Store",
|
|
1965
2044
|
"__pycache__",
|
|
1966
2045
|
".pytest_cache",
|
|
@@ -1971,8 +2050,11 @@ var generatedNames = /* @__PURE__ */ new Set([
|
|
|
1971
2050
|
".venv",
|
|
1972
2051
|
"node_modules"
|
|
1973
2052
|
]);
|
|
2053
|
+
function generatedName(name) {
|
|
2054
|
+
return generatedNames.has(name) || /\.(?:pyc|pyo|pid|sock|db-wal|db-shm)$/.test(name);
|
|
2055
|
+
}
|
|
1974
2056
|
function excludedName(name) {
|
|
1975
|
-
return
|
|
2057
|
+
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);
|
|
1976
2058
|
}
|
|
1977
2059
|
function contentFailure(path, message) {
|
|
1978
2060
|
fail(
|
|
@@ -1986,7 +2068,7 @@ async function captureContent(root) {
|
|
|
1986
2068
|
const entries = [];
|
|
1987
2069
|
const excluded = [];
|
|
1988
2070
|
const visit = async (path) => {
|
|
1989
|
-
for (const name of (await
|
|
2071
|
+
for (const name of (await readdir6(path)).sort()) {
|
|
1990
2072
|
const source = join9(path, name);
|
|
1991
2073
|
const item2 = relative2(root, source);
|
|
1992
2074
|
if (excludedName(name)) {
|
|
@@ -1998,7 +2080,7 @@ async function captureContent(root) {
|
|
|
1998
2080
|
entries.push({ path: item2, kind: "directory", mode: metadata2.mode & 511 });
|
|
1999
2081
|
await visit(source);
|
|
2000
2082
|
} else if (metadata2.isFile()) {
|
|
2001
|
-
const bytes = await
|
|
2083
|
+
const bytes = await readFile6(source);
|
|
2002
2084
|
const after = await lstat5(source);
|
|
2003
2085
|
if (metadata2.dev !== after.dev || metadata2.ino !== after.ino || metadata2.mode !== after.mode || metadata2.size !== after.size || metadata2.mtimeMs !== after.mtimeMs) {
|
|
2004
2086
|
contentFailure(source, `Source changed while being inspected: ${source}`);
|
|
@@ -2174,7 +2256,7 @@ async function captureVendorTree(root) {
|
|
|
2174
2256
|
const before = await lstat6(directory2, { bigint: true });
|
|
2175
2257
|
if (!before.isDirectory())
|
|
2176
2258
|
stateFailure2(directory2, "A content parent became a symlink or non-directory.");
|
|
2177
|
-
for (const name of (await
|
|
2259
|
+
for (const name of (await readdir7(directory2)).sort()) {
|
|
2178
2260
|
const path = join10(directory2, name);
|
|
2179
2261
|
const info = await lstat6(path, { bigint: true });
|
|
2180
2262
|
if (!info.isFile() && !info.isDirectory())
|
|
@@ -2205,7 +2287,7 @@ async function assertVendorTree(root, expected, allowMissing = false) {
|
|
|
2205
2287
|
if (identity4?.kind !== "directory")
|
|
2206
2288
|
stateFailure2(directory2, "A recovery parent has no directory ownership evidence.");
|
|
2207
2289
|
await assertVendorIdentity(directory2, identity4);
|
|
2208
|
-
for (const name of await
|
|
2290
|
+
for (const name of await readdir7(directory2)) {
|
|
2209
2291
|
const path = join10(directory2, name);
|
|
2210
2292
|
const item2 = relative3(root, path);
|
|
2211
2293
|
const entry3 = expectedEntries.get(item2);
|
|
@@ -2785,7 +2867,7 @@ async function importSkill(source, name, options = {}) {
|
|
|
2785
2867
|
const metadata2 = await readSkillMetadata(sourceRoot);
|
|
2786
2868
|
for (const name2 of ["SKILL.md", ".source.yaml"]) {
|
|
2787
2869
|
const captured = content.entries.find((entry3) => entry3.path === name2);
|
|
2788
|
-
if (captured?.kind === "file" && !captured.bytes.equals(await
|
|
2870
|
+
if (captured?.kind === "file" && !captured.bytes.equals(await readFile7(join12(sourceRoot, name2)))) {
|
|
2789
2871
|
fail("E_IMPORT_SOURCE_CHANGED", "Skill metadata changed while import was being planned.", {
|
|
2790
2872
|
path: join12(sourceRoot, name2),
|
|
2791
2873
|
fix: "Retry after the source writer has finished."
|
|
@@ -2839,12 +2921,12 @@ async function importSkill(source, name, options = {}) {
|
|
|
2839
2921
|
}
|
|
2840
2922
|
|
|
2841
2923
|
// src/core/diagnostics.ts
|
|
2842
|
-
import { readdir as
|
|
2924
|
+
import { readdir as readdir11, realpath as realpath13 } from "node:fs/promises";
|
|
2843
2925
|
import { homedir as homedir6 } from "node:os";
|
|
2844
2926
|
import { dirname as dirname10, join as join17, parse as parse2, resolve as resolve9, sep as sep5 } from "node:path";
|
|
2845
2927
|
|
|
2846
2928
|
// src/core/activation-ownership.ts
|
|
2847
|
-
import { lstat as lstat9, readdir as
|
|
2929
|
+
import { lstat as lstat9, readdir as readdir8, readlink as readlink2 } from "node:fs/promises";
|
|
2848
2930
|
import { dirname as dirname7, isAbsolute as isAbsolute3, join as join13, relative as relative4, resolve as resolve7 } from "node:path";
|
|
2849
2931
|
function refuse(path, message, code = "E_ACTIVATION_CONFLICT") {
|
|
2850
2932
|
fail(
|
|
@@ -2903,7 +2985,7 @@ async function inspectOwnedNode(path, node, missingOkay = false) {
|
|
|
2903
2985
|
const actual = await requireIdentity(path, node.identity, missingOkay);
|
|
2904
2986
|
if (!actual) return false;
|
|
2905
2987
|
if (node.identity.kind === "directory") {
|
|
2906
|
-
for (const name of await
|
|
2988
|
+
for (const name of await readdir8(path)) {
|
|
2907
2989
|
const expected = node.children?.[name];
|
|
2908
2990
|
if (!expected)
|
|
2909
2991
|
refuse(
|
|
@@ -2985,14 +3067,14 @@ function lexicalTarget(path, raw) {
|
|
|
2985
3067
|
}
|
|
2986
3068
|
|
|
2987
3069
|
// src/core/reconciliation.ts
|
|
2988
|
-
import { execFile } from "node:child_process";
|
|
3070
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
2989
3071
|
import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
|
|
2990
|
-
import { mkdir as mkdir6, readdir as
|
|
3072
|
+
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";
|
|
2991
3073
|
import { basename as basename3, dirname as dirname9, join as join16, relative as relative6, resolve as resolve8, sep as sep4 } from "node:path";
|
|
2992
|
-
import { promisify } from "node:util";
|
|
3074
|
+
import { promisify as promisify2 } from "node:util";
|
|
2993
3075
|
|
|
2994
3076
|
// src/core/packs.ts
|
|
2995
|
-
import { lstat as lstat11, readdir as
|
|
3077
|
+
import { lstat as lstat11, readdir as readdir9, readFile as readFile9, realpath as realpath11 } from "node:fs/promises";
|
|
2996
3078
|
import { join as join15 } from "node:path";
|
|
2997
3079
|
import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
|
|
2998
3080
|
|
|
@@ -3003,7 +3085,7 @@ import {
|
|
|
3003
3085
|
lstat as lstat10,
|
|
3004
3086
|
mkdir as mkdir5,
|
|
3005
3087
|
open as open5,
|
|
3006
|
-
readFile as
|
|
3088
|
+
readFile as readFile8,
|
|
3007
3089
|
readlink as readlink3,
|
|
3008
3090
|
realpath as realpath10,
|
|
3009
3091
|
rename as rename3,
|
|
@@ -3130,7 +3212,7 @@ async function saveManifest(plan2, changed3) {
|
|
|
3130
3212
|
await parents(plan2, manifest.path);
|
|
3131
3213
|
const info = await inspectPath(manifest.path);
|
|
3132
3214
|
if (manifest.before) {
|
|
3133
|
-
if (!info?.isFile() || info.dev !== manifest.before.info.dev || info.ino !== manifest.before.info.ino || !(await
|
|
3215
|
+
if (!info?.isFile() || info.dev !== manifest.before.info.dev || info.ino !== manifest.before.info.ino || !(await readFile8(manifest.path)).equals(manifest.before.bytes))
|
|
3134
3216
|
conflict(manifest.path, "The pack declaration changed while writing was being planned.");
|
|
3135
3217
|
} else if (info) conflict(manifest.path, "A pack manifest appeared during creation.");
|
|
3136
3218
|
};
|
|
@@ -3296,7 +3378,7 @@ async function readPack(registry, selected) {
|
|
|
3296
3378
|
const path = join15(first.path, "pack.toml");
|
|
3297
3379
|
const info = await lstat11(path);
|
|
3298
3380
|
if (!info.isFile()) conflict(path, "Pack manifests must be real files.");
|
|
3299
|
-
const bytes = await
|
|
3381
|
+
const bytes = await readFile9(path);
|
|
3300
3382
|
let raw;
|
|
3301
3383
|
try {
|
|
3302
3384
|
raw = parseToml2(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
@@ -3309,7 +3391,7 @@ async function readPack(registry, selected) {
|
|
|
3309
3391
|
}
|
|
3310
3392
|
const inventory = await packInventory(registry, selected);
|
|
3311
3393
|
const after = await lstat11(path);
|
|
3312
|
-
if (inventory.path !== first.path || !after.isFile() || after.dev !== info.dev || after.ino !== info.ino || !(await
|
|
3394
|
+
if (inventory.path !== first.path || !after.isFile() || after.dev !== info.dev || after.ino !== info.ino || !(await readFile9(path)).equals(bytes))
|
|
3313
3395
|
conflict(path, "The pack declaration changed during inspection.");
|
|
3314
3396
|
const header = raw.pack;
|
|
3315
3397
|
const text2 = description(header.description, path);
|
|
@@ -3356,7 +3438,7 @@ async function listPacks(options = {}) {
|
|
|
3356
3438
|
};
|
|
3357
3439
|
if (await inspectPath(root)) {
|
|
3358
3440
|
await requireDirectory(root, "E_PACK_MISSING");
|
|
3359
|
-
for (const family of (await
|
|
3441
|
+
for (const family of (await readdir9(root, { withFileTypes: true })).sort(
|
|
3360
3442
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
3361
3443
|
)) {
|
|
3362
3444
|
if (family.name.startsWith(".") || family.name.startsWith("_") || !family.isDirectory() && !family.isSymbolicLink())
|
|
@@ -3372,7 +3454,7 @@ async function listPacks(options = {}) {
|
|
|
3372
3454
|
await inspect6(named, path);
|
|
3373
3455
|
inspected += 1;
|
|
3374
3456
|
}
|
|
3375
|
-
for (const entry3 of (await
|
|
3457
|
+
for (const entry3 of (await readdir9(path, { withFileTypes: true })).sort(
|
|
3376
3458
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
3377
3459
|
)) {
|
|
3378
3460
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_") || !entry3.isDirectory() && !entry3.isSymbolicLink())
|
|
@@ -3458,7 +3540,7 @@ async function verifyPack(ref, options = {}) {
|
|
|
3458
3540
|
);
|
|
3459
3541
|
else {
|
|
3460
3542
|
const names2 = new Set(inventory.names);
|
|
3461
|
-
const entries = await
|
|
3543
|
+
const entries = await readdir9(inventory.skillsRoot, { withFileTypes: true });
|
|
3462
3544
|
const present = new Set(entries.map((entry3) => entry3.name));
|
|
3463
3545
|
for (const name of names2)
|
|
3464
3546
|
if (!present.has(name))
|
|
@@ -3536,7 +3618,7 @@ async function preparePack(selected, names2, operation, options) {
|
|
|
3536
3618
|
const root = join15(composition2.path, "skills");
|
|
3537
3619
|
await planDirectory(plan2, root);
|
|
3538
3620
|
if (await inspectPath(root)) {
|
|
3539
|
-
for (const entry3 of await
|
|
3621
|
+
for (const entry3 of await readdir9(root, { withFileTypes: true })) {
|
|
3540
3622
|
const path = join15(root, entry3.name);
|
|
3541
3623
|
const target2 = targets.get(entry3.name);
|
|
3542
3624
|
if (target2) await planLink(plan2, path, target2);
|
|
@@ -3830,7 +3912,7 @@ async function resolveSelectionWithManifests(options = {}, proposed = /* @__PURE
|
|
|
3830
3912
|
}
|
|
3831
3913
|
|
|
3832
3914
|
// src/core/reconciliation.ts
|
|
3833
|
-
var runFile =
|
|
3915
|
+
var runFile = promisify2(execFile2);
|
|
3834
3916
|
var lockResource = "skillex:activation:v2";
|
|
3835
3917
|
function errorResult(command, error) {
|
|
3836
3918
|
return makeResult(command, null, {
|
|
@@ -4124,7 +4206,7 @@ async function buildScope(scope, options, forbiddenRoots) {
|
|
|
4124
4206
|
}
|
|
4125
4207
|
return { identity: owned3, children };
|
|
4126
4208
|
};
|
|
4127
|
-
const awaitableNames = rootEntry?.info.isDirectory() ? await
|
|
4209
|
+
const awaitableNames = rootEntry?.info.isDirectory() ? await readdir10(physicalPath(root, recovery)) : [];
|
|
4128
4210
|
if (!rootEntry) {
|
|
4129
4211
|
operations.push({
|
|
4130
4212
|
path: root,
|
|
@@ -4900,7 +4982,7 @@ async function observeScope(location2, resolved, manifest, options, forbiddenRoo
|
|
|
4900
4982
|
const entries = [];
|
|
4901
4983
|
if (observedRoot.observation.reachable && ["directory", "link"].includes(observedRoot.observation.kind)) {
|
|
4902
4984
|
try {
|
|
4903
|
-
const names2 = (await
|
|
4985
|
+
const names2 = (await readdir11(root)).sort();
|
|
4904
4986
|
for (const name of names2) {
|
|
4905
4987
|
cancelled(options);
|
|
4906
4988
|
const path = join17(root, name);
|
|
@@ -5246,14 +5328,14 @@ async function explainSkill(name, options = {}) {
|
|
|
5246
5328
|
}
|
|
5247
5329
|
|
|
5248
5330
|
// src/core/doctor-sources.ts
|
|
5249
|
-
import { readdir as
|
|
5331
|
+
import { readdir as readdir13, readFile as readFile10, realpath as realpath15 } from "node:fs/promises";
|
|
5250
5332
|
import { basename as basename4, dirname as dirname11, join as join20, relative as relative8, sep as sep7 } from "node:path";
|
|
5251
5333
|
import { parse as parseToml4 } from "smol-toml";
|
|
5252
5334
|
|
|
5253
5335
|
// src/core/vendor-provenance.ts
|
|
5254
5336
|
import { createHash as createHash6 } from "node:crypto";
|
|
5255
5337
|
import { constants as constants4 } from "node:fs";
|
|
5256
|
-
import { lstat as lstat13, open as open7, readdir as
|
|
5338
|
+
import { lstat as lstat13, open as open7, readdir as readdir12 } from "node:fs/promises";
|
|
5257
5339
|
import { join as join19, relative as relative7, sep as sep6 } from "node:path";
|
|
5258
5340
|
import { stringify as stringify2 } from "yaml";
|
|
5259
5341
|
|
|
@@ -5765,7 +5847,7 @@ async function digestVendorTree(skillPath) {
|
|
|
5765
5847
|
const before = await lstat13(directory2);
|
|
5766
5848
|
if (!before.isDirectory())
|
|
5767
5849
|
unsafe(directory2, "Vendored skill directories must be real directories.");
|
|
5768
|
-
for (const name of (await
|
|
5850
|
+
for (const name of (await readdir12(directory2)).sort()) {
|
|
5769
5851
|
if (directory2 === root && name === ".source.yaml") continue;
|
|
5770
5852
|
const path = join19(directory2, name);
|
|
5771
5853
|
if (administration.has(name))
|
|
@@ -5817,7 +5899,7 @@ async function digestRecordedSkill(skillPath, provenance) {
|
|
|
5817
5899
|
async function appendRegularContent(root, path, entries) {
|
|
5818
5900
|
const info = await lstat13(path);
|
|
5819
5901
|
if (info.isDirectory()) {
|
|
5820
|
-
for (const name of (await
|
|
5902
|
+
for (const name of (await readdir12(path)).sort()) {
|
|
5821
5903
|
if (administration.has(name))
|
|
5822
5904
|
unsafe(join19(path, name), "Repository administration is not vendored skill content.");
|
|
5823
5905
|
await appendRegularContent(root, join19(path, name), entries);
|
|
@@ -6061,7 +6143,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6061
6143
|
};
|
|
6062
6144
|
await attempt(catalog, async () => {
|
|
6063
6145
|
await requireDirectory(catalog, "E_NO_REGISTRY");
|
|
6064
|
-
for (const name of (await
|
|
6146
|
+
for (const name of (await readdir13(catalog)).sort()) {
|
|
6065
6147
|
if (name === ".git") continue;
|
|
6066
6148
|
const path = join20(catalog, name);
|
|
6067
6149
|
await attempt(path, async () => {
|
|
@@ -6092,7 +6174,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6092
6174
|
});
|
|
6093
6175
|
const packManifests = [];
|
|
6094
6176
|
const walk = async (directory2) => {
|
|
6095
|
-
const names2 = await attempt(directory2, () =>
|
|
6177
|
+
const names2 = await attempt(directory2, () => readdir13(directory2));
|
|
6096
6178
|
for (const name of (names2 ?? []).sort()) {
|
|
6097
6179
|
if (name === ".git") continue;
|
|
6098
6180
|
const path = join20(directory2, name);
|
|
@@ -6127,7 +6209,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6127
6209
|
if (!await inspectPath(root)) return;
|
|
6128
6210
|
await requireDirectory(root, "E_COMPOSITION_ROOT");
|
|
6129
6211
|
await walk(root);
|
|
6130
|
-
for (const name of (await
|
|
6212
|
+
for (const name of (await readdir13(root)).sort()) {
|
|
6131
6213
|
if (!isSkillName(name)) continue;
|
|
6132
6214
|
const path = join20(root, name);
|
|
6133
6215
|
const info = await inspectPath(path);
|
|
@@ -6139,7 +6221,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6139
6221
|
if (kind2 !== "sets" || !info.isDirectory()) continue;
|
|
6140
6222
|
data2.sets++;
|
|
6141
6223
|
const members2 = await attempt(path, () => setMembers(registry.root, name));
|
|
6142
|
-
const names2 = members2?.names ?? (await
|
|
6224
|
+
const names2 = members2?.names ?? (await readdir13(path, { withFileTypes: true })).filter((item2) => item2.isSymbolicLink() && isSkillName(item2.name)).map((item2) => item2.name);
|
|
6143
6225
|
for (const member of names2)
|
|
6144
6226
|
await attempt(join20(path, member), () => setMemberTarget(registry.root, path, member));
|
|
6145
6227
|
}
|
|
@@ -6177,7 +6259,7 @@ async function inspectDoctorSources(registry) {
|
|
|
6177
6259
|
return { data: data2, findings, exits };
|
|
6178
6260
|
}
|
|
6179
6261
|
async function toml(path, code) {
|
|
6180
|
-
const bytes = await
|
|
6262
|
+
const bytes = await readFile10(path);
|
|
6181
6263
|
try {
|
|
6182
6264
|
return parseToml4(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
6183
6265
|
} catch {
|
|
@@ -6189,8 +6271,8 @@ async function toml(path, code) {
|
|
|
6189
6271
|
}
|
|
6190
6272
|
|
|
6191
6273
|
// src/core/doctor-writers.ts
|
|
6192
|
-
import { execFile as
|
|
6193
|
-
import { readdir as
|
|
6274
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
6275
|
+
import { readdir as readdir14, readFile as readFile11, realpath as realpath16, stat as stat3 } from "node:fs/promises";
|
|
6194
6276
|
import { homedir as homedir8 } from "node:os";
|
|
6195
6277
|
import { basename as basename5, dirname as dirname12, join as join21, resolve as resolve11 } from "node:path";
|
|
6196
6278
|
import { parse as parseToml5 } from "smol-toml";
|
|
@@ -6412,12 +6494,12 @@ async function inspectDoctorWriters(options, registries, scopes) {
|
|
|
6412
6494
|
};
|
|
6413
6495
|
const serviceRoot = join21(configHome, "systemd", "user");
|
|
6414
6496
|
try {
|
|
6415
|
-
for (const entry3 of await
|
|
6497
|
+
for (const entry3 of await readdir14(serviceRoot, { withFileTypes: true })) {
|
|
6416
6498
|
const path = join21(serviceRoot, entry3.name);
|
|
6417
6499
|
if (entry3.name.endsWith(".service")) candidates.set(path, "service");
|
|
6418
6500
|
else if (entry3.isDirectory() && entry3.name.endsWith(".service.d")) {
|
|
6419
6501
|
try {
|
|
6420
|
-
for (const file of await
|
|
6502
|
+
for (const file of await readdir14(path))
|
|
6421
6503
|
if (file.endsWith(".conf")) candidates.set(join21(path, file), "service");
|
|
6422
6504
|
} catch {
|
|
6423
6505
|
unavailable(path, "Cannot inspect user service overrides.");
|
|
@@ -6437,7 +6519,7 @@ async function inspectDoctorWriters(options, registries, scopes) {
|
|
|
6437
6519
|
unavailable(path, "Writer configuration is not a bounded regular file.");
|
|
6438
6520
|
continue;
|
|
6439
6521
|
}
|
|
6440
|
-
const content = new TextDecoder("utf-8", { fatal: true }).decode(await
|
|
6522
|
+
const content = new TextDecoder("utf-8", { fatal: true }).decode(await readFile11(path));
|
|
6441
6523
|
const lines = content.split(/\r?\n/);
|
|
6442
6524
|
if (kind2 === "service") {
|
|
6443
6525
|
for (const [index, line] of lines.entries()) {
|
|
@@ -6515,7 +6597,7 @@ async function inspectDoctorWriters(options, registries, scopes) {
|
|
|
6515
6597
|
}
|
|
6516
6598
|
async function boundedSnapshot(options, env) {
|
|
6517
6599
|
const observation = options.processSnapshot ? options.processSnapshot() : new Promise((accept, reject) => {
|
|
6518
|
-
|
|
6600
|
+
execFile3(
|
|
6519
6601
|
"ps",
|
|
6520
6602
|
["-axo", "pid=,args="],
|
|
6521
6603
|
{ env, timeout: timeoutMs, maxBuffer: maxProcessBytes, encoding: "utf8" },
|
|
@@ -6685,14 +6767,14 @@ import { realpath as realpath21, stat as stat4 } from "node:fs/promises";
|
|
|
6685
6767
|
// src/core/migration-activation.ts
|
|
6686
6768
|
import { createHash as createHash8, randomUUID as randomUUID6 } from "node:crypto";
|
|
6687
6769
|
import { constants as constants6 } from "node:fs";
|
|
6688
|
-
import { mkdir as mkdir7, open as open9, readdir as
|
|
6770
|
+
import { mkdir as mkdir7, open as open9, readdir as readdir17, realpath as realpath18, rename as rename5, symlink as symlink4 } from "node:fs/promises";
|
|
6689
6771
|
import { dirname as dirname16, isAbsolute as isAbsolute7, join as join25, parse as parse3, relative as relative10, resolve as resolve14 } from "node:path";
|
|
6690
6772
|
import { isDeepStrictEqual as isDeepStrictEqual4 } from "node:util";
|
|
6691
6773
|
|
|
6692
6774
|
// src/core/migration-activation-state.ts
|
|
6693
6775
|
import { createHash as createHash7 } from "node:crypto";
|
|
6694
6776
|
import { constants as constants5 } from "node:fs";
|
|
6695
|
-
import { lstat as lstat14, open as open8, readdir as
|
|
6777
|
+
import { lstat as lstat14, open as open8, readdir as readdir15, readlink as readlink4, rmdir as rmdir4, unlink as unlink6 } from "node:fs/promises";
|
|
6696
6778
|
import { dirname as dirname13, isAbsolute as isAbsolute5, join as join22, relative as relative9, resolve as resolve12 } from "node:path";
|
|
6697
6779
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
6698
6780
|
import { parseDocument as parseDocument2 } from "yaml";
|
|
@@ -6734,7 +6816,7 @@ async function captureMigrationNode(path) {
|
|
|
6734
6816
|
const children = {};
|
|
6735
6817
|
let hash4 = null;
|
|
6736
6818
|
if (before.isDirectory()) {
|
|
6737
|
-
const names2 = await
|
|
6819
|
+
const names2 = await readdir15(path, { encoding: "buffer" });
|
|
6738
6820
|
if (names2.length > 1e4)
|
|
6739
6821
|
migrationRefusal(path, "A migration directory exceeds the bounded inventory limit.");
|
|
6740
6822
|
for (const bytes of names2.sort(Buffer.compare)) {
|
|
@@ -6950,7 +7032,7 @@ function migrationAbsolute(value, home, cwd) {
|
|
|
6950
7032
|
}
|
|
6951
7033
|
|
|
6952
7034
|
// src/core/profile-discovery.ts
|
|
6953
|
-
import { lstat as lstat15, readdir as
|
|
7035
|
+
import { lstat as lstat15, readdir as readdir16, readlink as readlink5, realpath as realpath17 } from "node:fs/promises";
|
|
6954
7036
|
import { homedir as homedir9 } from "node:os";
|
|
6955
7037
|
import { basename as basename6, dirname as dirname14, join as join23, resolve as resolve13 } from "node:path";
|
|
6956
7038
|
var namePattern = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
@@ -7193,7 +7275,7 @@ async function discoverProfiles(options = {}) {
|
|
|
7193
7275
|
await canonicalDirectory(directory2, false);
|
|
7194
7276
|
let names2;
|
|
7195
7277
|
try {
|
|
7196
|
-
names2 = (await
|
|
7278
|
+
names2 = (await readdir16(directory2, { withFileTypes: true })).filter(
|
|
7197
7279
|
(entry3) => entry3.name !== "default" && namePattern.test(entry3.name) && (entry3.isDirectory() || entry3.isSymbolicLink())
|
|
7198
7280
|
).map((entry3) => entry3.name).sort();
|
|
7199
7281
|
} catch (error) {
|
|
@@ -7765,7 +7847,7 @@ async function collectArtifacts(plan2) {
|
|
|
7765
7847
|
await guard(plan2.target, join25(parent, "child"));
|
|
7766
7848
|
const current = await entry2(parent);
|
|
7767
7849
|
if (!current) continue;
|
|
7768
|
-
for (const name of await
|
|
7850
|
+
for (const name of await readdir17(parent)) {
|
|
7769
7851
|
if (!/^\.skillex-tmp-migration-[a-f0-9-]+-(?:new|old)$/.test(name)) continue;
|
|
7770
7852
|
const path = join25(parent, name);
|
|
7771
7853
|
if (path === pending?.stage || path === pending?.parked) continue;
|
|
@@ -8308,7 +8390,7 @@ async function finishPending(target2, state, begin = () => {
|
|
|
8308
8390
|
verified: state.data.verified
|
|
8309
8391
|
});
|
|
8310
8392
|
}
|
|
8311
|
-
async function
|
|
8393
|
+
async function execute2(target2, operation, state, mark, begin) {
|
|
8312
8394
|
check(target2.options);
|
|
8313
8395
|
await guard(target2, operation.item.path);
|
|
8314
8396
|
for (const proof of operation.proofs) await assertMigrationNode(proof.path, proof.node);
|
|
@@ -8585,7 +8667,7 @@ async function migrateActivation(options = {}) {
|
|
|
8585
8667
|
"The migration item did not converge after publication."
|
|
8586
8668
|
);
|
|
8587
8669
|
attempted.add(operation.item.id);
|
|
8588
|
-
state = await
|
|
8670
|
+
state = await execute2(
|
|
8589
8671
|
selected,
|
|
8590
8672
|
operation,
|
|
8591
8673
|
current.state,
|
|
@@ -8852,7 +8934,7 @@ import {
|
|
|
8852
8934
|
lstat as lstat16,
|
|
8853
8935
|
mkdir as mkdir8,
|
|
8854
8936
|
open as open10,
|
|
8855
|
-
readdir as
|
|
8937
|
+
readdir as readdir18,
|
|
8856
8938
|
readlink as readlink6,
|
|
8857
8939
|
rename as rename6,
|
|
8858
8940
|
rmdir as rmdir5,
|
|
@@ -8947,6 +9029,7 @@ async function captureMigrationTree(root) {
|
|
|
8947
9029
|
const initial = await migrationLstat(root);
|
|
8948
9030
|
if (!initial) return null;
|
|
8949
9031
|
const entries = [];
|
|
9032
|
+
const excluded = [];
|
|
8950
9033
|
const evidence2 = [];
|
|
8951
9034
|
const visit = async (path, relpath) => {
|
|
8952
9035
|
const before = await lstat16(path, { bigint: true });
|
|
@@ -8969,14 +9052,19 @@ async function captureMigrationTree(root) {
|
|
|
8969
9052
|
target: target2 ?? null
|
|
8970
9053
|
});
|
|
8971
9054
|
if (id.kind === "directory") {
|
|
8972
|
-
for (const name of (await
|
|
9055
|
+
for (const name of (await readdir18(path)).sort()) {
|
|
8973
9056
|
if ([".git", ".hg", ".svn"].includes(name))
|
|
8974
9057
|
migrationFailure2(
|
|
8975
9058
|
join27(path, name),
|
|
8976
9059
|
"A definition or composition contains repository administration; map its authored content explicitly.",
|
|
8977
9060
|
"E_MIGRATION_CONTENT"
|
|
8978
9061
|
);
|
|
8979
|
-
|
|
9062
|
+
const child = relpath ? join27(relpath, name) : name;
|
|
9063
|
+
if (generatedName(name)) {
|
|
9064
|
+
excluded.push(child);
|
|
9065
|
+
continue;
|
|
9066
|
+
}
|
|
9067
|
+
await visit(join27(path, name), child);
|
|
8980
9068
|
}
|
|
8981
9069
|
}
|
|
8982
9070
|
if (!stable2(before, await lstat16(path, { bigint: true })))
|
|
@@ -8987,9 +9075,23 @@ async function captureMigrationTree(root) {
|
|
|
8987
9075
|
migrationFailure2(root, "Source root was replaced while it was inspected.");
|
|
8988
9076
|
return {
|
|
8989
9077
|
entries,
|
|
8990
|
-
evidence: { root: identity3(initial), entries: evidence2, digest: migrationTreeDigest(entries) }
|
|
9078
|
+
evidence: { root: identity3(initial), entries: evidence2, digest: migrationTreeDigest(entries) },
|
|
9079
|
+
excluded
|
|
8991
9080
|
};
|
|
8992
9081
|
}
|
|
9082
|
+
function refuseExcludedRemoval(path, tree) {
|
|
9083
|
+
const first = tree.excluded[0];
|
|
9084
|
+
if (first === void 0) return;
|
|
9085
|
+
fail(
|
|
9086
|
+
"E_MIGRATION_RUNTIME_CONTENT",
|
|
9087
|
+
`Migration would remove generated build, cache, or runtime content it never migrates: ${tree.excluded.join(", ")}`,
|
|
9088
|
+
{
|
|
9089
|
+
path: join27(path, first),
|
|
9090
|
+
fix: "Remove only verified generated entries (their tools rebuild them), or move them elsewhere, then rerun the migration preview."
|
|
9091
|
+
},
|
|
9092
|
+
ExitCode.REFUSED
|
|
9093
|
+
);
|
|
9094
|
+
}
|
|
8993
9095
|
async function assertMigrationIdentity(path, expected) {
|
|
8994
9096
|
if (!equalIdentity2(await migrationLstat(path), expected))
|
|
8995
9097
|
migrationFailure2(path, "A migration entry changed identity.");
|
|
@@ -9099,6 +9201,7 @@ async function removeMigrationTree(path, expected) {
|
|
|
9099
9201
|
const currentTree = await captureMigrationTree(path);
|
|
9100
9202
|
if (!currentTree) return;
|
|
9101
9203
|
await assertMigrationIdentity(path, expected.root);
|
|
9204
|
+
refuseExcludedRemoval(path, currentTree);
|
|
9102
9205
|
for (const current of currentTree.evidence.entries) {
|
|
9103
9206
|
const known = expected.entries.find((entry3) => entry3.path === current.path);
|
|
9104
9207
|
if (!known || JSON.stringify(known) !== JSON.stringify(current))
|
|
@@ -9205,6 +9308,7 @@ async function publishMigrationOperation(operation) {
|
|
|
9205
9308
|
} else {
|
|
9206
9309
|
if (destination) {
|
|
9207
9310
|
await assertMigrationTree(operation.path, operation.before);
|
|
9311
|
+
refuseExcludedRemoval(operation.path, destination);
|
|
9208
9312
|
if (!operation.parked || parked)
|
|
9209
9313
|
migrationFailure2(operation.path, "Cannot park the original migration entry safely.");
|
|
9210
9314
|
await assertMigrationTree(operation.parked, null);
|
|
@@ -10351,7 +10455,7 @@ async function migrateManifest(options, nameMap = {}) {
|
|
|
10351
10455
|
|
|
10352
10456
|
// src/core/migration-registry.ts
|
|
10353
10457
|
import { createHash as createHash10 } from "node:crypto";
|
|
10354
|
-
import { lstat as lstat18, readdir as
|
|
10458
|
+
import { lstat as lstat18, readdir as readdir19, realpath as realpath20 } from "node:fs/promises";
|
|
10355
10459
|
import { basename as basename8, dirname as dirname19, isAbsolute as isAbsolute10, join as join29, relative as relative13, sep as sep9 } from "node:path";
|
|
10356
10460
|
import { parse as parseToml7, stringify as stringifyToml2 } from "smol-toml";
|
|
10357
10461
|
function record8(value) {
|
|
@@ -10369,7 +10473,7 @@ function payloadDigest(entries) {
|
|
|
10369
10473
|
async function sameCaseOnlyEntry(source, destination, before) {
|
|
10370
10474
|
if (dirname19(source) !== dirname19(destination) || basename8(source) === basename8(destination) || basename8(source).toLowerCase() !== basename8(destination).toLowerCase())
|
|
10371
10475
|
return false;
|
|
10372
|
-
const names2 = await
|
|
10476
|
+
const names2 = await readdir19(dirname19(source));
|
|
10373
10477
|
if (!names2.includes(basename8(source)) || names2.includes(basename8(destination))) return false;
|
|
10374
10478
|
const destinationTree = await captureMigrationTree(destination);
|
|
10375
10479
|
return JSON.stringify(destinationTree?.evidence) === JSON.stringify(before);
|
|
@@ -10449,6 +10553,26 @@ function refuse2(path, code, message) {
|
|
|
10449
10553
|
ExitCode.REFUSED
|
|
10450
10554
|
);
|
|
10451
10555
|
}
|
|
10556
|
+
var runtimeContentFix = "Remove only verified generated entries (their tools rebuild them), or move them outside the composition, then rerun the migration preview.";
|
|
10557
|
+
function runtimeContentMessage(holder) {
|
|
10558
|
+
return `A ${holder} contains generated build, cache, or runtime content that migration never carries or removes.`;
|
|
10559
|
+
}
|
|
10560
|
+
function hiddenPath(path) {
|
|
10561
|
+
return path.split(sep9).some((part) => part.startsWith(".") || part.startsWith("_"));
|
|
10562
|
+
}
|
|
10563
|
+
function skippedDetail(skipped, where) {
|
|
10564
|
+
return skipped.length ? [
|
|
10565
|
+
`Generated build, cache, or runtime entries ${where} are not definition content: they are not copied, not carried, and stay at the source: ${skipped.join(", ")}`
|
|
10566
|
+
] : [];
|
|
10567
|
+
}
|
|
10568
|
+
function runtimeContent(path, holder) {
|
|
10569
|
+
fail(
|
|
10570
|
+
"E_MIGRATION_RUNTIME_CONTENT",
|
|
10571
|
+
runtimeContentMessage(holder),
|
|
10572
|
+
{ path, fix: runtimeContentFix },
|
|
10573
|
+
ExitCode.REFUSED
|
|
10574
|
+
);
|
|
10575
|
+
}
|
|
10452
10576
|
function safeKey(value) {
|
|
10453
10577
|
const parts = value.split("/");
|
|
10454
10578
|
return !!value && !value.includes("\\") && ![...value].some(
|
|
@@ -10583,21 +10707,15 @@ async function importDefinition(plan2, lexicalSource, preferred, selected) {
|
|
|
10583
10707
|
if (metadata2.provenance) plan2.provenance[lexicalSource] = metadata2.provenance;
|
|
10584
10708
|
const tree = selected ? {
|
|
10585
10709
|
entries: selected,
|
|
10586
|
-
evidence: { ...captured.evidence, digest: migrationTreeDigest(selected) }
|
|
10710
|
+
evidence: { ...captured.evidence, digest: migrationTreeDigest(selected) },
|
|
10711
|
+
excluded: captured.excluded
|
|
10587
10712
|
} : captured;
|
|
10588
|
-
const
|
|
10589
|
-
(
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
"A candidate definition contains generated Python cache content.",
|
|
10595
|
-
{
|
|
10596
|
-
path: join29(lexicalSource, runtime.path),
|
|
10597
|
-
fix: "Remove only verified generated Python cache entries from the source, then rerun the migration preview."
|
|
10598
|
-
},
|
|
10599
|
-
ExitCode.REFUSED
|
|
10600
|
-
);
|
|
10713
|
+
const skipped = selected ? captured.excluded.filter(
|
|
10714
|
+
(item2) => selected.some(
|
|
10715
|
+
(entry3) => entry3.kind === "directory" && entry3.path !== "" && item2.startsWith(`${entry3.path}${sep9}`)
|
|
10716
|
+
)
|
|
10717
|
+
) : captured.excluded;
|
|
10718
|
+
plan2.skipped.set(lexicalSource, skipped);
|
|
10601
10719
|
const expected = mapped(plan2.options.mapping?.digests, plan2, lexicalSource) ?? mapped(plan2.options.mapping?.digests, plan2, source);
|
|
10602
10720
|
if (expected !== void 0 && expected !== tree.evidence.digest)
|
|
10603
10721
|
refuse2(
|
|
@@ -10650,7 +10768,8 @@ async function importDefinition(plan2, lexicalSource, preferred, selected) {
|
|
|
10650
10768
|
details: [
|
|
10651
10769
|
`source: ${lexicalSource}`,
|
|
10652
10770
|
`canonical name: ${name}`,
|
|
10653
|
-
"All captured definition bytes, support entries, modes, and provenance are preserved; internal absolute links are made portable."
|
|
10771
|
+
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.",
|
|
10772
|
+
...skippedDetail(skipped, "at this source")
|
|
10654
10773
|
],
|
|
10655
10774
|
dependsOn: []
|
|
10656
10775
|
};
|
|
@@ -10670,7 +10789,8 @@ async function importDefinition(plan2, lexicalSource, preferred, selected) {
|
|
|
10670
10789
|
beforeDigest: tree.evidence.digest,
|
|
10671
10790
|
afterDigest: reused.digest,
|
|
10672
10791
|
details: [
|
|
10673
|
-
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."
|
|
10792
|
+
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.",
|
|
10793
|
+
...skippedDetail(skipped, "at this source")
|
|
10674
10794
|
],
|
|
10675
10795
|
dependsOn: dependency ? [dependency] : []
|
|
10676
10796
|
});
|
|
@@ -10830,6 +10950,8 @@ async function composition(plan2, path, kind2) {
|
|
|
10830
10950
|
"E_MIGRATION_REFERENCE",
|
|
10831
10951
|
"A null reference mapping cannot remove a real definition; name and preserve its content instead."
|
|
10832
10952
|
);
|
|
10953
|
+
const generated = before.excluded.find((item3) => item3.startsWith(`${definition}${sep9}`));
|
|
10954
|
+
if (generated !== void 0) runtimeContent(join29(path, generated), "candidate definition");
|
|
10833
10955
|
const skill = await importDefinition(plan2, lexical, basename8(definition));
|
|
10834
10956
|
members2.push({
|
|
10835
10957
|
relativePath: definition,
|
|
@@ -10844,7 +10966,9 @@ async function composition(plan2, path, kind2) {
|
|
|
10844
10966
|
}
|
|
10845
10967
|
}
|
|
10846
10968
|
for (const entry3 of tree.entries) {
|
|
10847
|
-
if (!entry3.path || entry3.kind !== "link" ||
|
|
10969
|
+
if (!entry3.path || entry3.kind !== "link" || // A hidden link is outside the composition's namespace: never a member, never rewritten,
|
|
10970
|
+
// and its referent (often installer-owned) is never imported into the catalog.
|
|
10971
|
+
hiddenPath(entry3.path) || definitionRoots.some((parent) => entry3.path.startsWith(`${parent}${sep9}`)))
|
|
10848
10972
|
continue;
|
|
10849
10973
|
const lexical = join29(path, entry3.path);
|
|
10850
10974
|
const explicitReference = mapped(plan2.options.mapping?.references, plan2, lexical);
|
|
@@ -10908,7 +11032,7 @@ async function composition(plan2, path, kind2) {
|
|
|
10908
11032
|
if (kind2 === "pack" || dirname19(member.relativePath) !== ".")
|
|
10909
11033
|
entries.push(memberLink(plan2, destination, member.relativePath, member.name));
|
|
10910
11034
|
}
|
|
10911
|
-
let selected = members2.map((member) => member.name);
|
|
11035
|
+
let selected = members2.filter((member) => !hiddenPath(member.relativePath)).map((member) => member.name);
|
|
10912
11036
|
if (kind2 === "pack") {
|
|
10913
11037
|
const declared = [];
|
|
10914
11038
|
if (raw.freeform !== void 0 && (!record8(raw.freeform) || !Array.isArray(raw.freeform.skills) || !raw.freeform.skills.every((item3) => typeof item3 === "string")))
|
|
@@ -11060,6 +11184,18 @@ async function composition(plan2, path, kind2) {
|
|
|
11060
11184
|
entries = entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
11061
11185
|
const afterDigest = migrationTreeDigest(entries);
|
|
11062
11186
|
const changed3 = destination !== path || before.evidence.digest !== afterDigest;
|
|
11187
|
+
if (changed3)
|
|
11188
|
+
for (const item3 of before.excluded) {
|
|
11189
|
+
if (definitionRoots.some((root) => item3.startsWith(`${root}${sep9}`))) continue;
|
|
11190
|
+
finding(
|
|
11191
|
+
plan2,
|
|
11192
|
+
join29(path, item3),
|
|
11193
|
+
"E_MIGRATION_RUNTIME_CONTENT",
|
|
11194
|
+
runtimeContentMessage("composition"),
|
|
11195
|
+
runtimeContentFix
|
|
11196
|
+
);
|
|
11197
|
+
hasBlocker = true;
|
|
11198
|
+
}
|
|
11063
11199
|
const dependencies = [
|
|
11064
11200
|
...new Set(members2.flatMap((member) => member.dependency ? [member.dependency] : []))
|
|
11065
11201
|
];
|
|
@@ -11127,12 +11263,13 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11127
11263
|
provenance: { ...previousProvenance },
|
|
11128
11264
|
externalRoots: new Set(previousExternalRoots),
|
|
11129
11265
|
canonical: /* @__PURE__ */ new Map(),
|
|
11130
|
-
imported: /* @__PURE__ */ new Map()
|
|
11266
|
+
imported: /* @__PURE__ */ new Map(),
|
|
11267
|
+
skipped: /* @__PURE__ */ new Map()
|
|
11131
11268
|
};
|
|
11132
11269
|
const catalog = join29(registry.root, "all-skills");
|
|
11133
11270
|
const links = [];
|
|
11134
11271
|
const retiredAliases = [];
|
|
11135
|
-
for (const entry3 of (await
|
|
11272
|
+
for (const entry3 of (await readdir19(catalog, { withFileTypes: true })).sort(
|
|
11136
11273
|
(a, b) => a.name.localeCompare(b.name)
|
|
11137
11274
|
)) {
|
|
11138
11275
|
interrupted2(options);
|
|
@@ -11205,7 +11342,10 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11205
11342
|
state: "ready",
|
|
11206
11343
|
beforeDigest: before.evidence.digest,
|
|
11207
11344
|
afterDigest: skill.digest,
|
|
11208
|
-
details: [
|
|
11345
|
+
details: plan2.skipped.get(path)?.length ? [
|
|
11346
|
+
"Identical authored definition content is retained at the canonical target; retire only this exact alias after composition rewrites.",
|
|
11347
|
+
...skippedDetail(plan2.skipped.get(path) ?? [], "at the alias source")
|
|
11348
|
+
] : [
|
|
11209
11349
|
"Identical definition content is retained at the canonical target; retire only this exact alias after composition rewrites."
|
|
11210
11350
|
],
|
|
11211
11351
|
dependsOn: skill.dependency ? [skill.dependency] : []
|
|
@@ -11235,7 +11375,7 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11235
11375
|
blocked3(plan2, root, new Error("Composition family root must be a real directory."));
|
|
11236
11376
|
continue;
|
|
11237
11377
|
}
|
|
11238
|
-
for (const entry3 of (await
|
|
11378
|
+
for (const entry3 of (await readdir19(root, { withFileTypes: true })).sort(
|
|
11239
11379
|
(a, b) => a.name.localeCompare(b.name)
|
|
11240
11380
|
)) {
|
|
11241
11381
|
const path = join29(root, entry3.name);
|
|
@@ -11257,7 +11397,7 @@ async function prepare2(registry, options, previousNames = {}, previousProvenanc
|
|
|
11257
11397
|
else if (await inspectPath(join29(path, "pack.toml"))) await composition(plan2, path, "pack");
|
|
11258
11398
|
else {
|
|
11259
11399
|
const versions = [];
|
|
11260
|
-
for (const child of await
|
|
11400
|
+
for (const child of await readdir19(path, { withFileTypes: true }))
|
|
11261
11401
|
if ((child.isDirectory() || child.isSymbolicLink()) && await inspectPath(join29(path, child.name, "pack.toml")))
|
|
11262
11402
|
versions.push(child.name);
|
|
11263
11403
|
if (versions.length)
|
|
@@ -11527,7 +11667,7 @@ async function migrateRegistry(options = {}) {
|
|
|
11527
11667
|
// src/core/migration-sources.ts
|
|
11528
11668
|
import { createHash as createHash11, randomUUID as randomUUID8 } from "node:crypto";
|
|
11529
11669
|
import { constants as constants9 } from "node:fs";
|
|
11530
|
-
import { link as link6, open as open12, readdir as
|
|
11670
|
+
import { link as link6, open as open12, readdir as readdir20, unlink as unlink8 } from "node:fs/promises";
|
|
11531
11671
|
import { basename as basename9, join as join30 } from "node:path";
|
|
11532
11672
|
var binding3 = { namespace: "migrations", targetParts: ["all-skills", "sources.toml"] };
|
|
11533
11673
|
var prefix = ".skillex-tmp-migrate-sources-";
|
|
@@ -11546,7 +11686,7 @@ function refused2(path, message) {
|
|
|
11546
11686
|
async function validateProvenance(registry, text2, path) {
|
|
11547
11687
|
const sources = parseVendorSourcesText(text2, path);
|
|
11548
11688
|
const catalog = join30(registry.root, "all-skills");
|
|
11549
|
-
for (const name of (await
|
|
11689
|
+
for (const name of (await readdir20(catalog)).sort()) {
|
|
11550
11690
|
const skill = join30(catalog, name);
|
|
11551
11691
|
if (!(await inspectPath(skill))?.isDirectory() || !await inspectPath(join30(skill, "SKILL.md")))
|
|
11552
11692
|
continue;
|
|
@@ -11578,7 +11718,7 @@ async function migrateSources(input) {
|
|
|
11578
11718
|
const old = snapshot3.document?.data;
|
|
11579
11719
|
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)))
|
|
11580
11720
|
refused2(snapshot3.path, "The source migration receipt is malformed.");
|
|
11581
|
-
const artifacts = (await
|
|
11721
|
+
const artifacts = (await readdir20(catalog)).filter((name) => name.startsWith(prefix));
|
|
11582
11722
|
const unknown = artifacts.filter((name) => name !== old?.stage || old.phase !== "pending");
|
|
11583
11723
|
if (unknown.length)
|
|
11584
11724
|
return makeResult("migrate sources", data2, {
|
|
@@ -11878,10 +12018,94 @@ async function migrate(input = {}) {
|
|
|
11878
12018
|
|
|
11879
12019
|
// src/core/profile-sync.ts
|
|
11880
12020
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
11881
|
-
import { constants as
|
|
11882
|
-
import { mkdir as mkdir10, open as
|
|
11883
|
-
import { join as
|
|
12021
|
+
import { constants as constants11 } from "node:fs";
|
|
12022
|
+
import { mkdir as mkdir10, open as open14, readdir as readdir21, realpath as realpath22, rename as rename8, symlink as symlink6, unlink as unlink9 } from "node:fs/promises";
|
|
12023
|
+
import { join as join32, resolve as resolve17 } from "node:path";
|
|
11884
12024
|
import { isDeepStrictEqual as isDeepStrictEqual6 } from "node:util";
|
|
12025
|
+
|
|
12026
|
+
// src/core/profile-policy.ts
|
|
12027
|
+
import { constants as constants10 } from "node:fs";
|
|
12028
|
+
import { open as open13, readFile as readFile12 } from "node:fs/promises";
|
|
12029
|
+
import { join as join31 } from "node:path";
|
|
12030
|
+
import { parseDocument as parseDocument3 } from "yaml";
|
|
12031
|
+
var metadataFiles = /* @__PURE__ */ new Set([
|
|
12032
|
+
".usage.json",
|
|
12033
|
+
".usage.json.lock",
|
|
12034
|
+
".curator_state",
|
|
12035
|
+
".curator_suppressed",
|
|
12036
|
+
".sync_state"
|
|
12037
|
+
]);
|
|
12038
|
+
var metadataDirectories = /* @__PURE__ */ new Set([".curator_backups"]);
|
|
12039
|
+
function hermesMetadata(item2) {
|
|
12040
|
+
if (item2.kind === "file") return metadataFiles.has(item2.name);
|
|
12041
|
+
if (item2.kind === "directory") return metadataDirectories.has(item2.name);
|
|
12042
|
+
return false;
|
|
12043
|
+
}
|
|
12044
|
+
function refuse3(path, message) {
|
|
12045
|
+
return fail(
|
|
12046
|
+
"E_PROFILE_SKILLEX_ONLY",
|
|
12047
|
+
message,
|
|
12048
|
+
{
|
|
12049
|
+
path,
|
|
12050
|
+
fix: "Preserve foreign content outside discovery roots; clear skills.external_dirs through the locked config renderer, then preview skillex profile sync NAME --project PATH --skillex-only. Never delete or adopt local payloads implicitly."
|
|
12051
|
+
},
|
|
12052
|
+
ExitCode.REFUSED
|
|
12053
|
+
);
|
|
12054
|
+
}
|
|
12055
|
+
async function strictProfile(profile, options) {
|
|
12056
|
+
if (options.skillexOnly !== void 0 && typeof options.skillexOnly !== "boolean")
|
|
12057
|
+
refuse3(profile.root, "skillexOnly must be boolean.");
|
|
12058
|
+
const marker = await entry2(join31(profile.root, ".skillex-only"));
|
|
12059
|
+
if (marker && !marker.info.isFile())
|
|
12060
|
+
refuse3(marker.path, "The strict policy marker must be a regular file.");
|
|
12061
|
+
return options.skillexOnly === true || marker !== void 0;
|
|
12062
|
+
}
|
|
12063
|
+
async function assertStrictProfile(profile, options, local) {
|
|
12064
|
+
if (!await strictProfile(profile, options)) return false;
|
|
12065
|
+
for (const item2 of local)
|
|
12066
|
+
if (!hermesMetadata(item2))
|
|
12067
|
+
refuse3(item2.path, `Skillex-only profile contains unowned entry ${item2.name}.`);
|
|
12068
|
+
const path = join31(profile.root, "config.yaml");
|
|
12069
|
+
const config = await entry2(path);
|
|
12070
|
+
if (config) {
|
|
12071
|
+
if (!config.info.isFile())
|
|
12072
|
+
refuse3(path, "Strict profile config must be a regular generated file.");
|
|
12073
|
+
const document = parseDocument3(await readFile12(path, "utf8"), { uniqueKeys: true });
|
|
12074
|
+
if (document.errors.length) refuse3(path, "Cannot validate strict skills configuration.");
|
|
12075
|
+
const dirs = document.toJS()?.skills?.external_dirs;
|
|
12076
|
+
if (dirs !== void 0 && (!Array.isArray(dirs) || dirs.length))
|
|
12077
|
+
refuse3(path, "Skillex-only profiles require skills.external_dirs: [].");
|
|
12078
|
+
}
|
|
12079
|
+
const optOut = await entry2(join31(profile.root, ".no-bundled-skills"));
|
|
12080
|
+
if (optOut && !optOut.info.isFile())
|
|
12081
|
+
refuse3(optOut.path, "Bundled opt-out marker must be a regular file.");
|
|
12082
|
+
return true;
|
|
12083
|
+
}
|
|
12084
|
+
async function publishStrictPolicy(profile) {
|
|
12085
|
+
for (const name of [".no-bundled-skills", ".skillex-only"]) {
|
|
12086
|
+
const path = join31(profile.root, name);
|
|
12087
|
+
const existing = await entry2(path);
|
|
12088
|
+
if (existing) {
|
|
12089
|
+
if (!existing.info.isFile()) refuse3(path, "Policy markers must be regular files.");
|
|
12090
|
+
continue;
|
|
12091
|
+
}
|
|
12092
|
+
const handle = await open13(
|
|
12093
|
+
path,
|
|
12094
|
+
constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW,
|
|
12095
|
+
384
|
|
12096
|
+
);
|
|
12097
|
+
try {
|
|
12098
|
+
await handle.writeFile(
|
|
12099
|
+
"Skillex owns this PM skill projection. Use skillex profile sync; no local installs.\n"
|
|
12100
|
+
);
|
|
12101
|
+
await handle.sync();
|
|
12102
|
+
} finally {
|
|
12103
|
+
await handle.close();
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
}
|
|
12107
|
+
|
|
12108
|
+
// src/core/profile-sync.ts
|
|
11885
12109
|
function refused3(code, message, path, fix) {
|
|
11886
12110
|
fail(code, message, { path, fix }, ExitCode.REFUSED);
|
|
11887
12111
|
}
|
|
@@ -11900,14 +12124,14 @@ function receiptOptions(profile, options, scopes = []) {
|
|
|
11900
12124
|
const { home, cwd } = normalized;
|
|
11901
12125
|
const expanded = (value) => resolve17(
|
|
11902
12126
|
cwd,
|
|
11903
|
-
value === "~" ? home : value.startsWith("~/") ?
|
|
12127
|
+
value === "~" ? home : value.startsWith("~/") ? join32(home, value.slice(2)) : value
|
|
11904
12128
|
);
|
|
11905
12129
|
return {
|
|
11906
12130
|
...normalized,
|
|
11907
12131
|
forbiddenRoots: [
|
|
11908
12132
|
.../* @__PURE__ */ new Set([
|
|
11909
12133
|
profile.root,
|
|
11910
|
-
|
|
12134
|
+
join32(home, ".agents"),
|
|
11911
12135
|
...options.project ? [expanded(options.project)] : [],
|
|
11912
12136
|
...options.registryRoot ? [expanded(options.registryRoot)] : [],
|
|
11913
12137
|
...scopes.flatMap((scope) => [
|
|
@@ -11969,7 +12193,7 @@ async function resolveProjection(options) {
|
|
|
11969
12193
|
}
|
|
11970
12194
|
const locations = /* @__PURE__ */ new Map();
|
|
11971
12195
|
for (const scope of scopes) {
|
|
11972
|
-
locations.set(
|
|
12196
|
+
locations.set(join32(scope.registry.root, "all-skills"), "catalog");
|
|
11973
12197
|
if (scope.pack) locations.set(scope.pack.path, "pack");
|
|
11974
12198
|
}
|
|
11975
12199
|
const sources = [];
|
|
@@ -11995,8 +12219,8 @@ async function observations(profile, state) {
|
|
|
11995
12219
|
"Use the explicit migrate workflow to replace this whole-root alias or conflicting file while preserving profile content."
|
|
11996
12220
|
);
|
|
11997
12221
|
if (root)
|
|
11998
|
-
for (const name of (await
|
|
11999
|
-
const path =
|
|
12222
|
+
for (const name of (await readdir21(profile.skillsRoot)).sort()) {
|
|
12223
|
+
const path = join32(profile.skillsRoot, name);
|
|
12000
12224
|
const actual = await entry2(path);
|
|
12001
12225
|
if (!actual) continue;
|
|
12002
12226
|
entries.set(name, actual);
|
|
@@ -12048,7 +12272,7 @@ async function buildWork(profile, state, options) {
|
|
|
12048
12272
|
for (const scope of projection.scopes) {
|
|
12049
12273
|
for (const protectedRoot of [
|
|
12050
12274
|
scope.registry.root,
|
|
12051
|
-
...scope.scope === "project" ? [scope.root] : [
|
|
12275
|
+
...scope.scope === "project" ? [scope.root] : [join32(scope.root, ".agents")]
|
|
12052
12276
|
])
|
|
12053
12277
|
if (within2(profile.skillsRoot, protectedRoot) || within2(protectedRoot, profile.skillsRoot))
|
|
12054
12278
|
refused3(
|
|
@@ -12068,18 +12292,19 @@ async function buildWork(profile, state, options) {
|
|
|
12068
12292
|
);
|
|
12069
12293
|
state = await readProfileState(profile, receiptOptions(profile, options, projection.scopes));
|
|
12070
12294
|
const observed = await observations(profile, state);
|
|
12295
|
+
await assertStrictProfile(profile, options, observed.local);
|
|
12071
12296
|
const links = { ...state.data.links };
|
|
12072
12297
|
const changes = [];
|
|
12073
12298
|
if (!observed.root) changes.push({ action: "mkdir", path: profile.skillsRoot });
|
|
12074
12299
|
for (const [name, claim2] of Object.entries(links))
|
|
12075
12300
|
if (!owned2(observed.entries.get(name), claim2)) {
|
|
12076
12301
|
delete links[name];
|
|
12077
|
-
changes.push({ action: "release", name, path:
|
|
12302
|
+
changes.push({ action: "release", name, path: join32(profile.skillsRoot, name) });
|
|
12078
12303
|
}
|
|
12079
12304
|
const candidates = [];
|
|
12080
12305
|
const desiredNames = new Set(projection.desired.map((desired) => desired.name));
|
|
12081
12306
|
for (const desired of projection.desired) {
|
|
12082
|
-
const path =
|
|
12307
|
+
const path = join32(profile.skillsRoot, desired.name);
|
|
12083
12308
|
const actual = observed.entries.get(desired.name);
|
|
12084
12309
|
const recorded = claim(links, desired.name);
|
|
12085
12310
|
const state2 = actual ? recorded ? lexicalTarget(path, actual.raw ?? "") === desired.target ? "unchanged" : "update" : "shadowed" : "create";
|
|
@@ -12096,7 +12321,7 @@ async function buildWork(profile, state, options) {
|
|
|
12096
12321
|
}
|
|
12097
12322
|
for (const name of Object.keys(links).sort())
|
|
12098
12323
|
if (!desiredNames.has(name))
|
|
12099
|
-
changes.push({ action: "prune", name, path:
|
|
12324
|
+
changes.push({ action: "prune", name, path: join32(profile.skillsRoot, name) });
|
|
12100
12325
|
const next = {
|
|
12101
12326
|
version: 1,
|
|
12102
12327
|
root: observed.root ? identity(observed.root) : null,
|
|
@@ -12134,6 +12359,7 @@ async function inspectProfile(profile, options = {}) {
|
|
|
12134
12359
|
}
|
|
12135
12360
|
try {
|
|
12136
12361
|
preserved = (await observations(profile, state)).local;
|
|
12362
|
+
await assertStrictProfile(profile, options, preserved);
|
|
12137
12363
|
} catch (error) {
|
|
12138
12364
|
const failure6 = asFailure(error);
|
|
12139
12365
|
findings.push(...failure6.findings);
|
|
@@ -12185,9 +12411,9 @@ async function inspectProfile(profile, options = {}) {
|
|
|
12185
12411
|
}
|
|
12186
12412
|
async function flush3(path, expected) {
|
|
12187
12413
|
await requireIdentity(path, expected);
|
|
12188
|
-
const handle = await
|
|
12414
|
+
const handle = await open14(
|
|
12189
12415
|
path,
|
|
12190
|
-
|
|
12416
|
+
constants11.O_RDONLY | constants11.O_DIRECTORY | constants11.O_NOFOLLOW
|
|
12191
12417
|
);
|
|
12192
12418
|
try {
|
|
12193
12419
|
if (!matches2({ path, info: await handle.stat({ bigint: true }) }, expected))
|
|
@@ -12232,7 +12458,7 @@ async function recoverProfile(profile, state, options, mark, findings) {
|
|
|
12232
12458
|
if (!pending || !root) return state;
|
|
12233
12459
|
checkProfileSignal(options);
|
|
12234
12460
|
await rootGuard(profile, root);
|
|
12235
|
-
const path =
|
|
12461
|
+
const path = join32(profile.skillsRoot, pending.name);
|
|
12236
12462
|
let current = await entry2(path);
|
|
12237
12463
|
const parked = await entry2(pending.parked);
|
|
12238
12464
|
const staged = await entry2(pending.stage);
|
|
@@ -12358,8 +12584,8 @@ async function apply2(work, mark, begin) {
|
|
|
12358
12584
|
}
|
|
12359
12585
|
}
|
|
12360
12586
|
const id = randomUUID9();
|
|
12361
|
-
const stage2 =
|
|
12362
|
-
const parked =
|
|
12587
|
+
const stage2 = join32(profile.skillsRoot, `.skillex-tmp-profile-${id}-new`);
|
|
12588
|
+
const parked = join32(profile.skillsRoot, `.skillex-tmp-profile-${id}-old`);
|
|
12363
12589
|
await absent(stage2);
|
|
12364
12590
|
await absent(parked);
|
|
12365
12591
|
let next;
|
|
@@ -12504,7 +12730,7 @@ async function runProfileSync(name, options) {
|
|
|
12504
12730
|
{ findings, exit: readExit(findings, state.data.pending !== void 0) }
|
|
12505
12731
|
);
|
|
12506
12732
|
}
|
|
12507
|
-
if (work && !work.result.changes.length)
|
|
12733
|
+
if (work && !work.result.changes.length && !await strictProfile(profile, options))
|
|
12508
12734
|
return makeResult(
|
|
12509
12735
|
"profile sync",
|
|
12510
12736
|
{ ...work.result, project: work.projection.project, dryRun: false, applied },
|
|
@@ -12532,6 +12758,8 @@ async function runProfileSync(name, options) {
|
|
|
12532
12758
|
});
|
|
12533
12759
|
}
|
|
12534
12760
|
await assertProfileIdentity(selected);
|
|
12761
|
+
if (await assertStrictProfile(selected, options, (await observations(selected, state)).local))
|
|
12762
|
+
await publishStrictPolicy(selected);
|
|
12535
12763
|
const currentProfile = await discoverProfile(name, options);
|
|
12536
12764
|
if (currentProfile.root !== selected.root || currentProfile.skillsRoot !== selected.skillsRoot)
|
|
12537
12765
|
refused3(
|
|
@@ -12569,21 +12797,21 @@ async function runProfileSync(name, options) {
|
|
|
12569
12797
|
const latest = await readProfileState(profile, receiptOptions(profile, options));
|
|
12570
12798
|
const pending = latest.data.pending;
|
|
12571
12799
|
if (pending) {
|
|
12572
|
-
const current = await entry2(
|
|
12800
|
+
const current = await entry2(join32(profile.skillsRoot, pending.name));
|
|
12573
12801
|
if (pending.next && matches2(current, pending.next) && !applied.some((change2) => change2.path === current?.path))
|
|
12574
12802
|
applied.push({
|
|
12575
12803
|
action: pending.previous ? "update" : "create",
|
|
12576
12804
|
name: pending.name,
|
|
12577
|
-
path:
|
|
12805
|
+
path: join32(profile.skillsRoot, pending.name),
|
|
12578
12806
|
target: pending.next.raw
|
|
12579
12807
|
});
|
|
12580
12808
|
if (!pending.next && !current && pending.previous && matches2(await entry2(pending.parked), pending.previous) && !applied.some(
|
|
12581
|
-
(change2) => change2.path ===
|
|
12809
|
+
(change2) => change2.path === join32(profile.skillsRoot, pending.name)
|
|
12582
12810
|
))
|
|
12583
12811
|
applied.push({
|
|
12584
12812
|
action: "prune",
|
|
12585
12813
|
name: pending.name,
|
|
12586
|
-
path:
|
|
12814
|
+
path: join32(profile.skillsRoot, pending.name)
|
|
12587
12815
|
});
|
|
12588
12816
|
}
|
|
12589
12817
|
state = latest;
|
|
@@ -12725,13 +12953,13 @@ async function syncProfile(name, options) {
|
|
|
12725
12953
|
|
|
12726
12954
|
// src/core/selection-commands.ts
|
|
12727
12955
|
import { realpath as realpath24, stat as stat5 } from "node:fs/promises";
|
|
12728
|
-
import { dirname as dirname21, join as
|
|
12956
|
+
import { dirname as dirname21, join as join34, parse as parse4, resolve as resolve19 } from "node:path";
|
|
12729
12957
|
|
|
12730
12958
|
// src/core/selection-manifest.ts
|
|
12731
12959
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
12732
|
-
import { constants as
|
|
12733
|
-
import { link as link7, lstat as lstat19, mkdir as mkdir11, open as
|
|
12734
|
-
import { dirname as dirname20, join as
|
|
12960
|
+
import { constants as constants12 } from "node:fs";
|
|
12961
|
+
import { link as link7, lstat as lstat19, mkdir as mkdir11, open as open15, realpath as realpath23, rename as rename9, rmdir as rmdir6, unlink as unlink10 } from "node:fs/promises";
|
|
12962
|
+
import { dirname as dirname20, join as join33, resolve as resolve18 } from "node:path";
|
|
12735
12963
|
import { isDeepStrictEqual as isDeepStrictEqual7 } from "node:util";
|
|
12736
12964
|
var SelectionManifestWriteError = class extends SkillexError {
|
|
12737
12965
|
constructor(error, published) {
|
|
@@ -12823,13 +13051,13 @@ async function contextFor3(root) {
|
|
|
12823
13051
|
unsafe2(path, "A scope ancestor is missing or is not a real directory.");
|
|
12824
13052
|
parents2.set(path, info2);
|
|
12825
13053
|
}
|
|
12826
|
-
const agents =
|
|
13054
|
+
const agents = join33(canonical2, ".agents");
|
|
12827
13055
|
const info = await inspect5(agents);
|
|
12828
13056
|
if (info) {
|
|
12829
13057
|
if (!info.isDirectory()) unsafe2(agents, "The scope .agents path must be a real directory.");
|
|
12830
13058
|
parents2.set(agents, info);
|
|
12831
13059
|
}
|
|
12832
|
-
return { root: canonical2, agents, path:
|
|
13060
|
+
return { root: canonical2, agents, path: join33(agents, "skills.json"), parents: parents2 };
|
|
12833
13061
|
}
|
|
12834
13062
|
async function assertParents4(context2) {
|
|
12835
13063
|
for (const [path, expected] of context2.parents) {
|
|
@@ -12846,7 +13074,7 @@ async function readStored2(path) {
|
|
|
12846
13074
|
const first = await inspect5(path);
|
|
12847
13075
|
if (!first) return void 0;
|
|
12848
13076
|
if (!first.isFile()) unsafe2(path, "The selection declaration must be a real regular file.");
|
|
12849
|
-
const handle = await
|
|
13077
|
+
const handle = await open15(path, constants12.O_RDONLY | constants12.O_NOFOLLOW);
|
|
12850
13078
|
try {
|
|
12851
13079
|
const before = await handle.stat({ bigint: true });
|
|
12852
13080
|
if (!before.isFile() || !sameObject2(first, before))
|
|
@@ -12918,9 +13146,9 @@ async function assertPrevious2(context2, previous) {
|
|
|
12918
13146
|
return current;
|
|
12919
13147
|
}
|
|
12920
13148
|
async function flushDirectory2(path, identity4) {
|
|
12921
|
-
const handle = await
|
|
13149
|
+
const handle = await open15(
|
|
12922
13150
|
path,
|
|
12923
|
-
|
|
13151
|
+
constants12.O_RDONLY | constants12.O_DIRECTORY | constants12.O_NOFOLLOW
|
|
12924
13152
|
);
|
|
12925
13153
|
try {
|
|
12926
13154
|
if (!sameObject2(identity4, await handle.stat({ bigint: true })))
|
|
@@ -12988,11 +13216,11 @@ async function writeSelectionManifest(previous, raw) {
|
|
|
12988
13216
|
await flushDirectory2(context2.root, rootIdentity);
|
|
12989
13217
|
}
|
|
12990
13218
|
await assertPrevious2(context2, prior);
|
|
12991
|
-
const path =
|
|
13219
|
+
const path = join33(context2.agents, `.skillex-tmp-${randomUUID10()}`);
|
|
12992
13220
|
const mode = prior.file ? Number(prior.file.identity.mode & 0o7777n) : 420;
|
|
12993
|
-
const file = await
|
|
13221
|
+
const file = await open15(
|
|
12994
13222
|
path,
|
|
12995
|
-
|
|
13223
|
+
constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW,
|
|
12996
13224
|
mode
|
|
12997
13225
|
);
|
|
12998
13226
|
try {
|
|
@@ -13104,7 +13332,7 @@ async function directory(value, cwd, home) {
|
|
|
13104
13332
|
fail("E_PROJECT_ROOT", "A project or working directory must be a nonempty path.", {
|
|
13105
13333
|
fix: "Pass an existing directory as --project or cwd."
|
|
13106
13334
|
});
|
|
13107
|
-
const expanded = value === "~" ? home : value.startsWith("~/") ?
|
|
13335
|
+
const expanded = value === "~" ? home : value.startsWith("~/") ? join34(home, value.slice(2)) : value;
|
|
13108
13336
|
const path = resolve19(cwd, expanded);
|
|
13109
13337
|
const info = await inspectPath(path);
|
|
13110
13338
|
if (!info || !(await stat5(path)).isDirectory())
|
|
@@ -13117,7 +13345,7 @@ async function directory(value, cwd, home) {
|
|
|
13117
13345
|
async function nearestGitRoot(cwd, home) {
|
|
13118
13346
|
let current = cwd;
|
|
13119
13347
|
while (current !== home && current !== parse4(current).root) {
|
|
13120
|
-
if (await inspectPath(
|
|
13348
|
+
if (await inspectPath(join34(current, ".git"))) return current;
|
|
13121
13349
|
current = dirname21(current);
|
|
13122
13350
|
}
|
|
13123
13351
|
return void 0;
|
|
@@ -13284,7 +13512,7 @@ async function prepare3(options, edit) {
|
|
|
13284
13512
|
...options,
|
|
13285
13513
|
home: target2.home,
|
|
13286
13514
|
forbiddenRoots: [
|
|
13287
|
-
|
|
13515
|
+
join34(target2.root, ".agents"),
|
|
13288
13516
|
...target2.scope === "project" ? [target2.root] : []
|
|
13289
13517
|
]
|
|
13290
13518
|
});
|
|
@@ -13438,8 +13666,8 @@ async function setInheritance(enabled, options = {}) {
|
|
|
13438
13666
|
}
|
|
13439
13667
|
|
|
13440
13668
|
// src/core/sets.ts
|
|
13441
|
-
import { readdir as
|
|
13442
|
-
import { join as
|
|
13669
|
+
import { readdir as readdir22 } from "node:fs/promises";
|
|
13670
|
+
import { join as join35 } from "node:path";
|
|
13443
13671
|
function validateName(name) {
|
|
13444
13672
|
parseManifest({ sets: [{ name }] }, "set name");
|
|
13445
13673
|
}
|
|
@@ -13456,12 +13684,12 @@ async function listSets(options = {}) {
|
|
|
13456
13684
|
const command = "set list";
|
|
13457
13685
|
try {
|
|
13458
13686
|
const registry = await discoverRegistry(options);
|
|
13459
|
-
const path =
|
|
13687
|
+
const path = join35(registry.root, "sets");
|
|
13460
13688
|
const sets = [];
|
|
13461
13689
|
const failures = [];
|
|
13462
13690
|
if (await inspectPath(path)) {
|
|
13463
13691
|
await requireDirectory(path, "E_SET_MISSING");
|
|
13464
|
-
for (const entry3 of (await
|
|
13692
|
+
for (const entry3 of (await readdir22(path, { withFileTypes: true })).sort(
|
|
13465
13693
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
13466
13694
|
)) {
|
|
13467
13695
|
if (entry3.name.startsWith(".") || entry3.name.startsWith("_") || !entry3.isDirectory() && !entry3.isSymbolicLink())
|
|
@@ -13469,7 +13697,7 @@ async function listSets(options = {}) {
|
|
|
13469
13697
|
try {
|
|
13470
13698
|
sets.push(await readSet(registry.root, entry3.name));
|
|
13471
13699
|
} catch (error) {
|
|
13472
|
-
failures.push(collectedError(error,
|
|
13700
|
+
failures.push(collectedError(error, join35(path, entry3.name)));
|
|
13473
13701
|
}
|
|
13474
13702
|
}
|
|
13475
13703
|
}
|
|
@@ -13491,7 +13719,7 @@ async function showSet(name, options = {}) {
|
|
|
13491
13719
|
async function prepareSet(name, names2, operation, options) {
|
|
13492
13720
|
validateName(name);
|
|
13493
13721
|
const registry = await discoverRegistry(options);
|
|
13494
|
-
const path =
|
|
13722
|
+
const path = join35(registry.root, "sets", name);
|
|
13495
13723
|
const exists = await inspectPath(path);
|
|
13496
13724
|
const previous = operation === "create" && !exists ? { kind: "set", name, path, skills: [] } : await readSet(registry.root, name);
|
|
13497
13725
|
const requested = await requestedSkills(registry.root, names2, path);
|
|
@@ -13507,10 +13735,10 @@ async function prepareSet(name, names2, operation, options) {
|
|
|
13507
13735
|
const plan2 = await newPlan(registry, composition2, options.dryRun === true);
|
|
13508
13736
|
await planDirectory(plan2, path);
|
|
13509
13737
|
for (const skill of previous.skills)
|
|
13510
|
-
await inspectCanonicalLink(plan2,
|
|
13738
|
+
await inspectCanonicalLink(plan2, join35(path, skill.name), skill.path);
|
|
13511
13739
|
for (const [member, target2] of requested) {
|
|
13512
|
-
if (operation === "add") await planLink(plan2,
|
|
13513
|
-
else if (operation === "remove") await planUnlink(plan2,
|
|
13740
|
+
if (operation === "add") await planLink(plan2, join35(path, member), target2);
|
|
13741
|
+
else if (operation === "remove") await planUnlink(plan2, join35(path, member), target2);
|
|
13514
13742
|
}
|
|
13515
13743
|
return plan2;
|
|
13516
13744
|
}
|
|
@@ -13525,15 +13753,15 @@ async function removeSetSkills(name, names2, options = {}) {
|
|
|
13525
13753
|
}
|
|
13526
13754
|
|
|
13527
13755
|
// src/core/vendor-inspect.ts
|
|
13528
|
-
import { readdir as
|
|
13529
|
-
import { join as
|
|
13756
|
+
import { readdir as readdir23 } from "node:fs/promises";
|
|
13757
|
+
import { join as join36 } from "node:path";
|
|
13530
13758
|
|
|
13531
13759
|
// src/core/vendor-git.ts
|
|
13532
|
-
import { execFile as
|
|
13760
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
13533
13761
|
import { realpath as realpath25 } from "node:fs/promises";
|
|
13534
13762
|
import { posix } from "node:path";
|
|
13535
|
-
import { promisify as
|
|
13536
|
-
var
|
|
13763
|
+
import { promisify as promisify3 } from "node:util";
|
|
13764
|
+
var execute3 = promisify3(execFile4);
|
|
13537
13765
|
var oidPattern = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
13538
13766
|
function vendorInterrupted(options) {
|
|
13539
13767
|
if (options.signal?.aborted) {
|
|
@@ -13563,7 +13791,7 @@ function gitEnvironment(options) {
|
|
|
13563
13791
|
async function git(root, args, options, missingRef = false, allowAbsent = false) {
|
|
13564
13792
|
vendorInterrupted(options);
|
|
13565
13793
|
try {
|
|
13566
|
-
const result = await
|
|
13794
|
+
const result = await execute3(
|
|
13567
13795
|
"git",
|
|
13568
13796
|
["--no-lazy-fetch", "-c", "protocol.allow=never", "-C", root, ...args],
|
|
13569
13797
|
{
|
|
@@ -13888,19 +14116,19 @@ var Observation = class {
|
|
|
13888
14116
|
async function recordedInventory(manifest, observation, options) {
|
|
13889
14117
|
const recorded = /* @__PURE__ */ new Map();
|
|
13890
14118
|
const failed2 = /* @__PURE__ */ new Set();
|
|
13891
|
-
const catalog =
|
|
13892
|
-
for (const entry3 of (await
|
|
14119
|
+
const catalog = join36(manifest.registry.root, "all-skills");
|
|
14120
|
+
for (const entry3 of (await readdir23(catalog, { withFileTypes: true })).sort(
|
|
13893
14121
|
(a, b) => a.name.localeCompare(b.name)
|
|
13894
14122
|
)) {
|
|
13895
14123
|
checkVendorSignal(options);
|
|
13896
14124
|
if (!isSkillName(entry3.name) || !entry3.isDirectory()) continue;
|
|
13897
|
-
const path =
|
|
14125
|
+
const path = join36(catalog, entry3.name);
|
|
13898
14126
|
try {
|
|
13899
14127
|
const receipt2 = await readVendorProvenance(path);
|
|
13900
14128
|
if (receipt2) recorded.set(entry3.name, receipt2);
|
|
13901
14129
|
} catch (error) {
|
|
13902
14130
|
failed2.add(entry3.name);
|
|
13903
|
-
observation.capture(error,
|
|
14131
|
+
observation.capture(error, join36(path, ".source.yaml"));
|
|
13904
14132
|
}
|
|
13905
14133
|
}
|
|
13906
14134
|
return { recorded, failed: failed2 };
|
|
@@ -13917,7 +14145,7 @@ function emptySkill(source, manifest, name, upstreamCommit) {
|
|
|
13917
14145
|
return {
|
|
13918
14146
|
name,
|
|
13919
14147
|
source: source.name,
|
|
13920
|
-
path:
|
|
14148
|
+
path: join36(manifest.registry.root, "all-skills", name),
|
|
13921
14149
|
state: "unknown",
|
|
13922
14150
|
recordedCommit: null,
|
|
13923
14151
|
upstreamCommit,
|
|
@@ -14298,9 +14526,9 @@ async function inspectVendorStatus(options = {}) {
|
|
|
14298
14526
|
|
|
14299
14527
|
// src/core/vendor-sync.ts
|
|
14300
14528
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
14301
|
-
import { chmod as chmod3, lstat as lstat20, mkdir as mkdir12, open as
|
|
14302
|
-
import { dirname as dirname22, join as
|
|
14303
|
-
function
|
|
14529
|
+
import { chmod as chmod3, lstat as lstat20, mkdir as mkdir12, open as open16, readdir as readdir24, rename as rename10 } from "node:fs/promises";
|
|
14530
|
+
import { dirname as dirname22, join as join37 } from "node:path";
|
|
14531
|
+
function refuse4(code, message, path, fix) {
|
|
14304
14532
|
fail(code, message, { path, fix }, ExitCode.REFUSED);
|
|
14305
14533
|
}
|
|
14306
14534
|
function attributed(provenance, source) {
|
|
@@ -14310,21 +14538,21 @@ function baseline(provenance) {
|
|
|
14310
14538
|
return provenance.digest !== null && (provenance.digestFormat === null || ["skillex-tree-v1", "skillex-tree-v1+symlinks"].includes(provenance.digestFormat));
|
|
14311
14539
|
}
|
|
14312
14540
|
async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
14313
|
-
const path =
|
|
14541
|
+
const path = join37(catalog, skill.name);
|
|
14314
14542
|
const info = await vendorLstat(path);
|
|
14315
14543
|
let before = null;
|
|
14316
14544
|
let provenance = null;
|
|
14317
14545
|
let action = "create";
|
|
14318
14546
|
if (info) {
|
|
14319
14547
|
if (info.isSymbolicLink())
|
|
14320
|
-
|
|
14548
|
+
refuse4(
|
|
14321
14549
|
"E_VENDOR_DESTINATION_LINK",
|
|
14322
14550
|
"Vendor sync will not replace a linked canonical destination.",
|
|
14323
14551
|
path,
|
|
14324
14552
|
"Use the explicit migrate workflow to account for this link and its consumers before vendoring."
|
|
14325
14553
|
);
|
|
14326
14554
|
if (!info.isDirectory())
|
|
14327
|
-
|
|
14555
|
+
refuse4(
|
|
14328
14556
|
"E_VENDOR_DESTINATION_TYPE",
|
|
14329
14557
|
"The canonical destination is not a real skill directory.",
|
|
14330
14558
|
path,
|
|
@@ -14334,21 +14562,21 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14334
14562
|
const digest = await digestVendorTree(path);
|
|
14335
14563
|
if (provenance?.type === "vendored") {
|
|
14336
14564
|
if (!attributed(provenance, source))
|
|
14337
|
-
|
|
14565
|
+
refuse4(
|
|
14338
14566
|
"E_VENDOR_FOREIGN_SOURCE",
|
|
14339
14567
|
"The canonical skill is attributed to a different source.",
|
|
14340
14568
|
path,
|
|
14341
14569
|
"Correct the declaration or migrate the existing provenance explicitly; adopt does not transfer another vendor's ownership."
|
|
14342
14570
|
);
|
|
14343
14571
|
if (!baseline(provenance) && !options.discardLocalEdits)
|
|
14344
|
-
|
|
14572
|
+
refuse4(
|
|
14345
14573
|
"E_VENDOR_BASELINE_MISSING",
|
|
14346
14574
|
"The managed skill has no supported baseline digest proving unchanged content.",
|
|
14347
14575
|
path,
|
|
14348
14576
|
"Preserve or commit local content, then explicitly use --discard-local-edits to replace this attributed skill; it cannot be implicitly pruned."
|
|
14349
14577
|
);
|
|
14350
14578
|
if (baseline(provenance) && (digest !== provenance.digest || provenance.modifiedLocally) && !options.discardLocalEdits)
|
|
14351
|
-
|
|
14579
|
+
refuse4(
|
|
14352
14580
|
"E_VENDOR_LOCAL_EDITS",
|
|
14353
14581
|
"The vendored skill has local content or executable-mode edits.",
|
|
14354
14582
|
path,
|
|
@@ -14357,14 +14585,14 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14357
14585
|
action = digest === skill.digest && !provenance.modifiedLocally && provenance.digest === skill.digest && provenance.upstreamCommit === commit && provenance.upstreamTree === skill.tree && provenance.upstreamPath === skill.upstreamPath && provenance.upstreamVersion === source.version && provenance.extractedAt !== null && baseline(provenance) ? "unchanged" : "update";
|
|
14358
14586
|
} else {
|
|
14359
14587
|
if (!options.adopt)
|
|
14360
|
-
|
|
14588
|
+
refuse4(
|
|
14361
14589
|
"E_VENDOR_UNMANAGED",
|
|
14362
14590
|
"The canonical skill is not managed by this vendor source.",
|
|
14363
14591
|
path,
|
|
14364
14592
|
"Use --adopt to record ownership; if bytes or modes differ from upstream, also use --discard-local-edits after preserving them."
|
|
14365
14593
|
);
|
|
14366
14594
|
if (digest !== skill.digest && !options.discardLocalEdits)
|
|
14367
|
-
|
|
14595
|
+
refuse4(
|
|
14368
14596
|
"E_VENDOR_LOCAL_EDITS",
|
|
14369
14597
|
"Adopting this skill would replace local bytes or executable modes.",
|
|
14370
14598
|
path,
|
|
@@ -14374,14 +14602,14 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14374
14602
|
}
|
|
14375
14603
|
before = await captureVendorTree(path);
|
|
14376
14604
|
if (before.root.dev !== String((await lstat20(catalog, { bigint: true })).dev))
|
|
14377
|
-
|
|
14605
|
+
refuse4(
|
|
14378
14606
|
"E_VENDOR_CROSS_DEVICE",
|
|
14379
14607
|
"The existing skill is mounted on a different filesystem from catalog staging.",
|
|
14380
14608
|
path,
|
|
14381
14609
|
"Move the skill into the catalog filesystem before updating it; vendor replacement requires same-filesystem renames."
|
|
14382
14610
|
);
|
|
14383
14611
|
if (before.root.dev !== String(info.dev) || before.root.ino !== String(info.ino))
|
|
14384
|
-
|
|
14612
|
+
refuse4(
|
|
14385
14613
|
"E_VENDOR_CHANGED",
|
|
14386
14614
|
"The destination changed during preflight.",
|
|
14387
14615
|
path,
|
|
@@ -14420,7 +14648,7 @@ async function planSkill(source, skill, commit, extractedAt, catalog, options) {
|
|
|
14420
14648
|
async function prepare4(options, extractedAt) {
|
|
14421
14649
|
vendorInterrupted(options);
|
|
14422
14650
|
const manifest = await readVendorSources(options);
|
|
14423
|
-
const catalog =
|
|
14651
|
+
const catalog = join37(manifest.registry.root, "all-skills");
|
|
14424
14652
|
const journal = await readVendorJournal(manifest.registry, options);
|
|
14425
14653
|
const selected = selectVendorSources(manifest, options.sources);
|
|
14426
14654
|
const sources = [];
|
|
@@ -14432,7 +14660,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14432
14660
|
const checkout = await resolveVendorCheckout(source, options);
|
|
14433
14661
|
if (!checkout.root) {
|
|
14434
14662
|
if (!source.optional)
|
|
14435
|
-
|
|
14663
|
+
refuse4(
|
|
14436
14664
|
"E_SOURCE_CHECKOUT_MISSING",
|
|
14437
14665
|
`Local checkout ${source.checkout} is unavailable.`,
|
|
14438
14666
|
manifest.path,
|
|
@@ -14485,7 +14713,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14485
14713
|
});
|
|
14486
14714
|
for (const skill of upstream.skills) {
|
|
14487
14715
|
if (names2.has(skill.name))
|
|
14488
|
-
|
|
14716
|
+
refuse4(
|
|
14489
14717
|
"E_VENDOR_DUPLICATE_SKILL",
|
|
14490
14718
|
`Sources ${names2.get(skill.name)} and ${source.name} select the same canonical skill.`,
|
|
14491
14719
|
manifest.path,
|
|
@@ -14496,9 +14724,9 @@ async function prepare4(options, extractedAt) {
|
|
|
14496
14724
|
}
|
|
14497
14725
|
}
|
|
14498
14726
|
if (options.prune) {
|
|
14499
|
-
for (const name of (await
|
|
14727
|
+
for (const name of (await readdir24(catalog)).filter(isSkillName).sort()) {
|
|
14500
14728
|
if (names2.has(name)) continue;
|
|
14501
|
-
const path =
|
|
14729
|
+
const path = join37(catalog, name);
|
|
14502
14730
|
if (!(await vendorLstat(path))?.isDirectory()) continue;
|
|
14503
14731
|
let provenance;
|
|
14504
14732
|
try {
|
|
@@ -14518,14 +14746,14 @@ async function prepare4(options, extractedAt) {
|
|
|
14518
14746
|
const source = provenance?.source ? enumerated.get(provenance.source) : void 0;
|
|
14519
14747
|
if (!provenance || !source || !attributed(provenance, source)) continue;
|
|
14520
14748
|
if (!baseline(provenance))
|
|
14521
|
-
|
|
14749
|
+
refuse4(
|
|
14522
14750
|
"E_VENDOR_BASELINE_MISSING",
|
|
14523
14751
|
"An orphaned managed skill has no supported baseline proving it is safe to prune.",
|
|
14524
14752
|
path,
|
|
14525
14753
|
"Restore its verified provenance or remove it explicitly after inspection; --discard-local-edits never authorizes pruning unknown content."
|
|
14526
14754
|
);
|
|
14527
14755
|
if (provenance.modifiedLocally || await digestVendorTree(path) !== provenance.digest)
|
|
14528
|
-
|
|
14756
|
+
refuse4(
|
|
14529
14757
|
"E_VENDOR_LOCAL_EDITS",
|
|
14530
14758
|
"An orphaned managed skill has local edits and cannot be pruned.",
|
|
14531
14759
|
path,
|
|
@@ -14559,7 +14787,7 @@ async function prepare4(options, extractedAt) {
|
|
|
14559
14787
|
}
|
|
14560
14788
|
async function absent2(path) {
|
|
14561
14789
|
if (await vendorLstat(path))
|
|
14562
|
-
|
|
14790
|
+
refuse4(
|
|
14563
14791
|
"E_VENDOR_CHANGED",
|
|
14564
14792
|
"A destination or recovery path appeared after preflight.",
|
|
14565
14793
|
path,
|
|
@@ -14571,7 +14799,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14571
14799
|
const selected = selectVendorSources(current, options.sources);
|
|
14572
14800
|
const expected = selectVendorSources(prepared.manifest, options.sources);
|
|
14573
14801
|
if (current.registry.root !== prepared.manifest.registry.root || JSON.stringify(selected) !== JSON.stringify(expected))
|
|
14574
|
-
|
|
14802
|
+
refuse4(
|
|
14575
14803
|
"E_VENDOR_INTENT_CHANGED",
|
|
14576
14804
|
"The selected vendor declarations changed during staging.",
|
|
14577
14805
|
prepared.manifest.path,
|
|
@@ -14581,7 +14809,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14581
14809
|
const checkout = await resolveVendorCheckout(source, options);
|
|
14582
14810
|
const previous = prepared.result.sources.find((entry3) => entry3.name === source.name);
|
|
14583
14811
|
if (!previous || checkout.root !== previous.checkout)
|
|
14584
|
-
|
|
14812
|
+
refuse4(
|
|
14585
14813
|
"E_VENDOR_INTENT_CHANGED",
|
|
14586
14814
|
"A selected local checkout mapping changed during staging.",
|
|
14587
14815
|
prepared.manifest.path,
|
|
@@ -14590,7 +14818,7 @@ async function assertCurrentIntent(prepared, options) {
|
|
|
14590
14818
|
}
|
|
14591
14819
|
}
|
|
14592
14820
|
async function syncDirectory2(path) {
|
|
14593
|
-
const handle = await
|
|
14821
|
+
const handle = await open16(path, "r");
|
|
14594
14822
|
try {
|
|
14595
14823
|
await handle.sync();
|
|
14596
14824
|
} finally {
|
|
@@ -14605,12 +14833,12 @@ async function stage(plan2, path, catalog, options) {
|
|
|
14605
14833
|
for (const entry3 of plan2.entries) {
|
|
14606
14834
|
vendorInterrupted(options);
|
|
14607
14835
|
await assertVendorIdentity(path, root);
|
|
14608
|
-
const target2 =
|
|
14836
|
+
const target2 = join37(path, entry3.path);
|
|
14609
14837
|
let parent = dirname22(entry3.path);
|
|
14610
14838
|
while (parent !== ".") {
|
|
14611
14839
|
const identity4 = parents2.get(parent);
|
|
14612
14840
|
if (!identity4) throw new Error(`Unprepared vendor content parent: ${parent}`);
|
|
14613
|
-
await assertVendorIdentity(
|
|
14841
|
+
await assertVendorIdentity(join37(path, parent), identity4);
|
|
14614
14842
|
parent = dirname22(parent);
|
|
14615
14843
|
}
|
|
14616
14844
|
if (entry3.kind === "directory") {
|
|
@@ -14618,7 +14846,7 @@ async function stage(plan2, path, catalog, options) {
|
|
|
14618
14846
|
await chmod3(target2, 493);
|
|
14619
14847
|
parents2.set(entry3.path, vendorIdentity(await lstat20(target2, { bigint: true })));
|
|
14620
14848
|
} else if (entry3.kind === "file") {
|
|
14621
|
-
const handle = await
|
|
14849
|
+
const handle = await open16(target2, "wx", entry3.mode);
|
|
14622
14850
|
try {
|
|
14623
14851
|
await handle.writeFile(entry3.bytes);
|
|
14624
14852
|
await handle.chmod(entry3.mode);
|
|
@@ -14667,7 +14895,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14667
14895
|
if (published) {
|
|
14668
14896
|
await assertVendorTree(operation.change.path, operation.after);
|
|
14669
14897
|
if (stageInfo)
|
|
14670
|
-
|
|
14898
|
+
refuse4(
|
|
14671
14899
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14672
14900
|
"Both the staged and published paths exist for one prepared skill.",
|
|
14673
14901
|
operation.change.path,
|
|
@@ -14675,7 +14903,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14675
14903
|
);
|
|
14676
14904
|
} else if (destination) {
|
|
14677
14905
|
if (!operation.before)
|
|
14678
|
-
|
|
14906
|
+
refuse4(
|
|
14679
14907
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14680
14908
|
"A foreign destination blocks vendor recovery.",
|
|
14681
14909
|
operation.change.path,
|
|
@@ -14683,7 +14911,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14683
14911
|
);
|
|
14684
14912
|
await assertVendorTree(operation.change.path, operation.before);
|
|
14685
14913
|
if (parked)
|
|
14686
|
-
|
|
14914
|
+
refuse4(
|
|
14687
14915
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14688
14916
|
"Both the old destination and its parked path exist.",
|
|
14689
14917
|
operation.change.path,
|
|
@@ -14691,7 +14919,7 @@ async function recover2(snapshot3, findings, options, onWrite) {
|
|
|
14691
14919
|
);
|
|
14692
14920
|
} else if (!parked && operation.before) {
|
|
14693
14921
|
if (operation.change.action !== "prune")
|
|
14694
|
-
|
|
14922
|
+
refuse4(
|
|
14695
14923
|
"E_VENDOR_RECOVERY_CONFLICT",
|
|
14696
14924
|
"The old and new skill directories are both missing.",
|
|
14697
14925
|
operation.change.path,
|
|
@@ -14764,7 +14992,7 @@ async function syncVendorSources(options = {}) {
|
|
|
14764
14992
|
initiallyPending = initialJournal.document !== null;
|
|
14765
14993
|
fallback = {
|
|
14766
14994
|
registry,
|
|
14767
|
-
manifest:
|
|
14995
|
+
manifest: join37(registry.root, "all-skills", "sources.toml"),
|
|
14768
14996
|
dryRun: options.dryRun === true,
|
|
14769
14997
|
sources: [],
|
|
14770
14998
|
changes: [],
|
|
@@ -14811,7 +15039,7 @@ async function syncVendorSources(options = {}) {
|
|
|
14811
15039
|
}
|
|
14812
15040
|
prepared = await prepare4(options, extractedAt);
|
|
14813
15041
|
if (prepared.manifest.registry.root !== initialRoot)
|
|
14814
|
-
|
|
15042
|
+
refuse4(
|
|
14815
15043
|
"E_CATALOG_CHANGED",
|
|
14816
15044
|
"Registry discovery changed while waiting for its catalog lock.",
|
|
14817
15045
|
initialRoot,
|
|
@@ -14833,8 +15061,8 @@ async function syncVendorSources(options = {}) {
|
|
|
14833
15061
|
change: plan2.change,
|
|
14834
15062
|
before: plan2.before,
|
|
14835
15063
|
after: null,
|
|
14836
|
-
stage: plan2.change.action === "prune" ? null :
|
|
14837
|
-
parked: plan2.before ?
|
|
15064
|
+
stage: plan2.change.action === "prune" ? null : join37(journal.catalog, `.skillex-tmp-vendor-${id}-new`),
|
|
15065
|
+
parked: plan2.before ? join37(journal.catalog, `.skillex-tmp-vendor-${id}-old`) : null
|
|
14838
15066
|
};
|
|
14839
15067
|
});
|
|
14840
15068
|
vendorInterrupted(options);
|