@rhize/skill-forge 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cli.js +354 -249
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
|
|
7
7
|
// src/commands/add.ts
|
|
8
|
-
import { join as
|
|
8
|
+
import { join as join20 } from "path";
|
|
9
9
|
|
|
10
10
|
// src/config.ts
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
@@ -143,6 +143,9 @@ function looksHighEntropy(value) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// src/preferences.ts
|
|
146
|
+
function markAccepted(value) {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
146
149
|
var PREFERENCE_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
147
150
|
var MAX_PREFERENCE_VALUE_LENGTH = 500;
|
|
148
151
|
var MAX_PENDING_PROPOSALS = 50;
|
|
@@ -208,7 +211,7 @@ function setPreference(config, key, value, options = {}) {
|
|
|
208
211
|
validateProvenanceText("origin", options.origin);
|
|
209
212
|
validateProvenanceText("note", options.note);
|
|
210
213
|
const entry = {
|
|
211
|
-
value,
|
|
214
|
+
value: markAccepted(value),
|
|
212
215
|
source: options.source ?? "user",
|
|
213
216
|
updatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
214
217
|
origin: options.origin,
|
|
@@ -285,7 +288,9 @@ function normalizePreferences(raw) {
|
|
|
285
288
|
continue;
|
|
286
289
|
}
|
|
287
290
|
out[key] = {
|
|
288
|
-
|
|
291
|
+
// Re-minted only after `validatePreference` passed above: a hand-edited config.json cannot
|
|
292
|
+
// smuggle in a value that would fail today's rules.
|
|
293
|
+
value: markAccepted(entry.value),
|
|
289
294
|
source: entry.source === "agent" ? "agent" : "user",
|
|
290
295
|
updatedAt: typeof entry.updatedAt === "string" ? entry.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
291
296
|
origin,
|
|
@@ -625,19 +630,117 @@ function defaultExec(command, args, options) {
|
|
|
625
630
|
|
|
626
631
|
// src/install/promote.ts
|
|
627
632
|
import { cpSync as cpSync2, existsSync as existsSync4, mkdirSync as mkdirSync4, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
628
|
-
import { basename as basename3, join as
|
|
633
|
+
import { basename as basename3, join as join6 } from "path";
|
|
634
|
+
|
|
635
|
+
// src/install/fingerprint.ts
|
|
636
|
+
import { createHash } from "crypto";
|
|
637
|
+
import { lstatSync, readFileSync as readFileSync3, readdirSync as readdirSync3, readlinkSync, realpathSync } from "fs";
|
|
638
|
+
import { isAbsolute, join as join5, relative, sep } from "path";
|
|
639
|
+
var SKIP_DIRS3 = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
640
|
+
function isInside(child, root) {
|
|
641
|
+
const rel = relative(root, child);
|
|
642
|
+
return rel === "" || !rel.startsWith(".." + sep) && rel !== ".." && !isAbsolute(rel);
|
|
643
|
+
}
|
|
644
|
+
function fingerprintTree(root) {
|
|
645
|
+
const hash = createHash("sha256");
|
|
646
|
+
const symlinks = [];
|
|
647
|
+
const escaping = [];
|
|
648
|
+
const realRoot = realpathSync(root);
|
|
649
|
+
function walk2(dir) {
|
|
650
|
+
const entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
651
|
+
for (const entry of entries) {
|
|
652
|
+
const full = join5(dir, entry.name);
|
|
653
|
+
const rel = relative(root, full);
|
|
654
|
+
if (entry.isSymbolicLink()) {
|
|
655
|
+
const target = readlinkSync(full);
|
|
656
|
+
symlinks.push(`${rel} -> ${target}`);
|
|
657
|
+
hash.update(`L ${rel} ${target}
|
|
658
|
+
`);
|
|
659
|
+
try {
|
|
660
|
+
if (!isInside(realpathSync(full), realRoot)) escaping.push(rel);
|
|
661
|
+
} catch {
|
|
662
|
+
escaping.push(rel);
|
|
663
|
+
}
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (entry.isDirectory()) {
|
|
667
|
+
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
668
|
+
hash.update(`D ${rel}
|
|
669
|
+
`);
|
|
670
|
+
walk2(full);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (entry.isFile()) {
|
|
674
|
+
const contentHash = createHash("sha256").update(readFileSync3(full)).digest("hex");
|
|
675
|
+
hash.update(`F ${rel} ${contentHash}
|
|
676
|
+
`);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
hash.update(`O ${rel}
|
|
680
|
+
`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
walk2(root);
|
|
684
|
+
return { digest: hash.digest("hex"), symlinks, escaping };
|
|
685
|
+
}
|
|
686
|
+
function fingerprintDirs(dirs) {
|
|
687
|
+
const hash = createHash("sha256");
|
|
688
|
+
const symlinks = [];
|
|
689
|
+
const escaping = [];
|
|
690
|
+
for (const dir of [...dirs].sort()) {
|
|
691
|
+
let result;
|
|
692
|
+
try {
|
|
693
|
+
result = fingerprintTree(dir);
|
|
694
|
+
} catch (err) {
|
|
695
|
+
hash.update(`MISSING ${dir} ${err.message}
|
|
696
|
+
`);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
hash.update(`DIR ${dir} ${result.digest}
|
|
700
|
+
`);
|
|
701
|
+
symlinks.push(...result.symlinks.map((s) => `${dir}: ${s}`));
|
|
702
|
+
escaping.push(...result.escaping.map((s) => `${dir}: ${s}`));
|
|
703
|
+
}
|
|
704
|
+
return { digest: hash.digest("hex"), symlinks, escaping };
|
|
705
|
+
}
|
|
706
|
+
function isRealDirectory(path) {
|
|
707
|
+
try {
|
|
708
|
+
return lstatSync(path).isDirectory();
|
|
709
|
+
} catch {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/install/promote.ts
|
|
629
715
|
function promote(entry, targetSkillsRoot, options = {}) {
|
|
630
716
|
const force = options.force ?? false;
|
|
631
717
|
for (const skillDir of entry.skillDirs) {
|
|
632
|
-
|
|
718
|
+
if (!isRealDirectory(skillDir)) {
|
|
719
|
+
throw new UserError(
|
|
720
|
+
`refusing to promote ${skillDir}: it is no longer a real directory (a symlink or a deleted path) \u2014 re-run the gate on this candidate`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const dest = join6(targetSkillsRoot, basename3(skillDir));
|
|
633
724
|
if (existsSync4(dest) && !force) {
|
|
634
725
|
throw new Error(`Refusing to overwrite existing skill dir: ${dest} (pass force to override)`);
|
|
635
726
|
}
|
|
636
727
|
}
|
|
728
|
+
const fingerprint = fingerprintDirs(entry.skillDirs);
|
|
729
|
+
if (fingerprint.escaping.length > 0) {
|
|
730
|
+
throw new UserError(
|
|
731
|
+
`refusing to promote ${entry.id}: ${fingerprint.escaping.length} symlink(s) inside the candidate resolve outside the quarantine sandbox, and would land in your skills root still pointing there:
|
|
732
|
+
` + fingerprint.escaping.join("\n ")
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (options.expectedFingerprint !== void 0 && options.expectedFingerprint !== fingerprint.digest) {
|
|
736
|
+
throw new UserError(
|
|
737
|
+
`refusing to promote ${entry.id}: the candidate's contents changed after it was gated \u2014 what was scanned is not what would be installed. Re-run \`skill-forge scan\` on the source.`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
637
740
|
mkdirSync4(targetSkillsRoot, { recursive: true });
|
|
638
741
|
const promoted = [];
|
|
639
742
|
for (const skillDir of entry.skillDirs) {
|
|
640
|
-
const dest =
|
|
743
|
+
const dest = join6(targetSkillsRoot, basename3(skillDir));
|
|
641
744
|
if (existsSync4(dest)) {
|
|
642
745
|
rmSync2(dest, { recursive: true, force: true });
|
|
643
746
|
}
|
|
@@ -666,7 +769,7 @@ function moveDir(src, dest) {
|
|
|
666
769
|
}
|
|
667
770
|
|
|
668
771
|
// src/install/promoteMcp.ts
|
|
669
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as
|
|
772
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
670
773
|
import { dirname as dirname2 } from "path";
|
|
671
774
|
function promoteMcp(_entry, profile, targetConfigPath, options = {}) {
|
|
672
775
|
const force = options.force ?? false;
|
|
@@ -675,7 +778,7 @@ function promoteMcp(_entry, profile, targetConfigPath, options = {}) {
|
|
|
675
778
|
let config = {};
|
|
676
779
|
let rawExisting;
|
|
677
780
|
if (existsSync5(targetConfigPath)) {
|
|
678
|
-
rawExisting =
|
|
781
|
+
rawExisting = readFileSync4(targetConfigPath, "utf-8");
|
|
679
782
|
try {
|
|
680
783
|
config = JSON.parse(rawExisting);
|
|
681
784
|
} catch (err) {
|
|
@@ -759,11 +862,11 @@ function deriveServerEntry(profile) {
|
|
|
759
862
|
}
|
|
760
863
|
|
|
761
864
|
// src/install/mcpCandidate.ts
|
|
762
|
-
import { readFileSync as
|
|
763
|
-
import { dirname as dirname3, join as
|
|
865
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
866
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
764
867
|
function readPackageInfo(pkgPath) {
|
|
765
868
|
try {
|
|
766
|
-
const pkg2 = JSON.parse(
|
|
869
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
767
870
|
return {
|
|
768
871
|
name: typeof pkg2.name === "string" ? pkg2.name : void 0,
|
|
769
872
|
version: typeof pkg2.version === "string" ? pkg2.version : void 0
|
|
@@ -774,7 +877,7 @@ function readPackageInfo(pkgPath) {
|
|
|
774
877
|
}
|
|
775
878
|
function readConfigEntry(configPath, name) {
|
|
776
879
|
try {
|
|
777
|
-
const cfg = JSON.parse(
|
|
880
|
+
const cfg = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
778
881
|
const servers = cfg.mcpServers ?? cfg.servers ?? {};
|
|
779
882
|
const raw = servers[name] ?? {};
|
|
780
883
|
return {
|
|
@@ -792,7 +895,7 @@ function resolveMcpCandidateDetail(profile) {
|
|
|
792
895
|
return { name, version: readPackageInfo(profile.config).version };
|
|
793
896
|
}
|
|
794
897
|
const entry = readConfigEntry(profile.config, name);
|
|
795
|
-
const sidecar = readPackageInfo(
|
|
898
|
+
const sidecar = readPackageInfo(join7(dirname3(profile.config), "package.json"));
|
|
796
899
|
return {
|
|
797
900
|
name,
|
|
798
901
|
command: entry?.command,
|
|
@@ -804,8 +907,8 @@ function resolveMcpCandidateDetail(profile) {
|
|
|
804
907
|
}
|
|
805
908
|
|
|
806
909
|
// src/provenance.ts
|
|
807
|
-
import { appendFileSync, existsSync as existsSync6, readFileSync as
|
|
808
|
-
import { join as
|
|
910
|
+
import { appendFileSync, existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
911
|
+
import { join as join8 } from "path";
|
|
809
912
|
var FORGE_VERBS = ["DEFER", "ABSORB", "FORK", "REJECT", "WATCH"];
|
|
810
913
|
var LEDGER_HEADER = "# Skill Forge \u2014 Provenance Ledger\n\nOne entry per external-skill ingestion decision.\n";
|
|
811
914
|
function todayLocalIsoDate() {
|
|
@@ -836,7 +939,7 @@ function appendProvenance(entry, skillsRoot) {
|
|
|
836
939
|
- **Drift check:** \`${driftCheck}\`
|
|
837
940
|
- **Notes:** ${entry.notes ?? ""}
|
|
838
941
|
`;
|
|
839
|
-
const ledgerPath =
|
|
942
|
+
const ledgerPath = join8(skillsRoot, "SOURCES.md");
|
|
840
943
|
if (!existsSync6(ledgerPath)) {
|
|
841
944
|
writeFileSync4(ledgerPath, LEDGER_HEADER, "utf-8");
|
|
842
945
|
}
|
|
@@ -884,41 +987,41 @@ function parseProvenanceLedger(text) {
|
|
|
884
987
|
return { entries, warnings };
|
|
885
988
|
}
|
|
886
989
|
function readProvenanceLedger(skillsRoot) {
|
|
887
|
-
const ledgerPath =
|
|
990
|
+
const ledgerPath = join8(skillsRoot, "SOURCES.md");
|
|
888
991
|
if (!existsSync6(ledgerPath)) {
|
|
889
992
|
return { ledgerPath, exists: false, entries: [], warnings: [] };
|
|
890
993
|
}
|
|
891
|
-
const text =
|
|
994
|
+
const text = readFileSync6(ledgerPath, "utf-8");
|
|
892
995
|
const parsed = parseProvenanceLedger(text);
|
|
893
996
|
return { ledgerPath, exists: true, ...parsed };
|
|
894
997
|
}
|
|
895
998
|
|
|
896
999
|
// src/commands/gatePipeline.ts
|
|
897
|
-
import { readdirSync as
|
|
1000
|
+
import { readdirSync as readdirSync7 } from "fs";
|
|
898
1001
|
|
|
899
1002
|
// src/auditState.ts
|
|
900
|
-
import { existsSync as existsSync9, readFileSync as
|
|
901
|
-
import { basename as basename7, dirname as dirname5, join as
|
|
1003
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1004
|
+
import { basename as basename7, dirname as dirname5, join as join12 } from "path";
|
|
902
1005
|
|
|
903
1006
|
// src/audit.ts
|
|
904
1007
|
import {
|
|
905
1008
|
existsSync as existsSync8,
|
|
906
|
-
lstatSync,
|
|
1009
|
+
lstatSync as lstatSync2,
|
|
907
1010
|
mkdtempSync,
|
|
908
|
-
readFileSync as
|
|
909
|
-
readdirSync as
|
|
910
|
-
realpathSync,
|
|
1011
|
+
readFileSync as readFileSync8,
|
|
1012
|
+
readdirSync as readdirSync5,
|
|
1013
|
+
realpathSync as realpathSync2,
|
|
911
1014
|
renameSync as renameSync3,
|
|
912
1015
|
rmSync as rmSync3,
|
|
913
1016
|
statSync as statSync6,
|
|
914
1017
|
writeFileSync as writeFileSync5
|
|
915
1018
|
} from "fs";
|
|
916
|
-
import { basename as basename6, delimiter as delimiter3, extname as extname3, join as
|
|
1019
|
+
import { basename as basename6, delimiter as delimiter3, extname as extname3, join as join11, resolve as resolvePath2 } from "path";
|
|
917
1020
|
|
|
918
1021
|
// src/gate/safety.ts
|
|
919
1022
|
import { statSync as statSync4 } from "fs";
|
|
920
1023
|
import { execFile } from "child_process";
|
|
921
|
-
import { delimiter as delimiter2, join as
|
|
1024
|
+
import { delimiter as delimiter2, join as join9 } from "path";
|
|
922
1025
|
import { promisify } from "util";
|
|
923
1026
|
var execFileAsync = promisify(execFile);
|
|
924
1027
|
var DENY_RULES = [
|
|
@@ -1017,7 +1120,7 @@ function which(cmd) {
|
|
|
1017
1120
|
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
1018
1121
|
for (const dir of dirs) {
|
|
1019
1122
|
for (const ext of exts) {
|
|
1020
|
-
const candidate =
|
|
1123
|
+
const candidate = join9(dir, cmd + ext);
|
|
1021
1124
|
try {
|
|
1022
1125
|
if (statSync4(candidate).isFile()) return candidate;
|
|
1023
1126
|
} catch {
|
|
@@ -1243,8 +1346,8 @@ async function mcpScan(targetPath, strictness = DEFAULT_STRICTNESS) {
|
|
|
1243
1346
|
}
|
|
1244
1347
|
|
|
1245
1348
|
// src/gate/overlap.ts
|
|
1246
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1247
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
1349
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
|
|
1350
|
+
import { basename as basename5, dirname as dirname4, join as join10, resolve as resolve2 } from "path";
|
|
1248
1351
|
var STOPWORDS = new Set(
|
|
1249
1352
|
`
|
|
1250
1353
|
a an the and or of to for in on with use used using when whether this that these those is are be
|
|
@@ -1253,20 +1356,20 @@ skill skills user claude code based comprehensive enhanced practical including i
|
|
|
1253
1356
|
`.split(/\s+/).filter(Boolean)
|
|
1254
1357
|
);
|
|
1255
1358
|
var DEFAULT_SET_THRESHOLD = 0.45;
|
|
1256
|
-
function splitLimited(text,
|
|
1359
|
+
function splitLimited(text, sep5, maxSplit) {
|
|
1257
1360
|
const parts = [];
|
|
1258
1361
|
let rest = text;
|
|
1259
1362
|
for (let i = 0; i < maxSplit; i++) {
|
|
1260
|
-
const idx = rest.indexOf(
|
|
1363
|
+
const idx = rest.indexOf(sep5);
|
|
1261
1364
|
if (idx === -1) break;
|
|
1262
1365
|
parts.push(rest.slice(0, idx));
|
|
1263
|
-
rest = rest.slice(idx +
|
|
1366
|
+
rest = rest.slice(idx + sep5.length);
|
|
1264
1367
|
}
|
|
1265
1368
|
parts.push(rest);
|
|
1266
1369
|
return parts;
|
|
1267
1370
|
}
|
|
1268
1371
|
function readFrontmatter(skillMdPath) {
|
|
1269
|
-
const text =
|
|
1372
|
+
const text = readFileSync7(skillMdPath, "utf-8");
|
|
1270
1373
|
const meta = { name: basename5(dirname4(skillMdPath)), description: "" };
|
|
1271
1374
|
if (text.startsWith("---\n")) {
|
|
1272
1375
|
const block = splitLimited(text, "---", 2)[1] ?? "";
|
|
@@ -1364,25 +1467,25 @@ function listSubdirs(dir) {
|
|
|
1364
1467
|
if (!existsSync7(dir)) return [];
|
|
1365
1468
|
let entries;
|
|
1366
1469
|
try {
|
|
1367
|
-
entries =
|
|
1470
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
1368
1471
|
} catch {
|
|
1369
1472
|
return [];
|
|
1370
1473
|
}
|
|
1371
|
-
return entries.filter((e) => e.isDirectory()).map((e) =>
|
|
1474
|
+
return entries.filter((e) => e.isDirectory()).map((e) => join10(dir, e.name));
|
|
1372
1475
|
}
|
|
1373
1476
|
function findSkillMdFiles(root) {
|
|
1374
1477
|
const found = /* @__PURE__ */ new Set();
|
|
1375
1478
|
for (const dir of listSubdirs(root)) {
|
|
1376
|
-
const candidate =
|
|
1479
|
+
const candidate = join10(dir, "SKILL.md");
|
|
1377
1480
|
if (existsSync7(candidate)) found.add(resolve2(candidate));
|
|
1378
1481
|
}
|
|
1379
|
-
for (const dir of listSubdirs(
|
|
1380
|
-
const candidate =
|
|
1482
|
+
for (const dir of listSubdirs(join10(root, "skills"))) {
|
|
1483
|
+
const candidate = join10(dir, "SKILL.md");
|
|
1381
1484
|
if (existsSync7(candidate)) found.add(resolve2(candidate));
|
|
1382
1485
|
}
|
|
1383
1486
|
for (const topDir of listSubdirs(root)) {
|
|
1384
|
-
for (const dir of listSubdirs(
|
|
1385
|
-
const candidate =
|
|
1487
|
+
for (const dir of listSubdirs(join10(topDir, "skills"))) {
|
|
1488
|
+
const candidate = join10(dir, "SKILL.md");
|
|
1386
1489
|
if (existsSync7(candidate)) found.add(resolve2(candidate));
|
|
1387
1490
|
}
|
|
1388
1491
|
}
|
|
@@ -1395,7 +1498,7 @@ function scanOverlap(candidatePath, skillsRoot) {
|
|
|
1395
1498
|
}
|
|
1396
1499
|
let cpath = resolve2(expandHome(candidatePath));
|
|
1397
1500
|
if (isDir(cpath)) {
|
|
1398
|
-
cpath =
|
|
1501
|
+
cpath = join10(cpath, "SKILL.md");
|
|
1399
1502
|
}
|
|
1400
1503
|
if (!existsSync7(cpath)) {
|
|
1401
1504
|
throw new Error(`candidate SKILL.md not found: ${cpath}`);
|
|
@@ -1547,13 +1650,13 @@ function isDirFollowingSymlink(p) {
|
|
|
1547
1650
|
function listSubdirsFollowingSymlinks(dir) {
|
|
1548
1651
|
let entries;
|
|
1549
1652
|
try {
|
|
1550
|
-
entries =
|
|
1653
|
+
entries = readdirSync5(dir, { withFileTypes: true });
|
|
1551
1654
|
} catch {
|
|
1552
1655
|
return [];
|
|
1553
1656
|
}
|
|
1554
1657
|
const out = [];
|
|
1555
1658
|
for (const entry of entries) {
|
|
1556
|
-
const full =
|
|
1659
|
+
const full = join11(dir, entry.name);
|
|
1557
1660
|
if (entry.isDirectory() || entry.isSymbolicLink() && isDirFollowingSymlink(full)) {
|
|
1558
1661
|
out.push(full);
|
|
1559
1662
|
}
|
|
@@ -1563,24 +1666,24 @@ function listSubdirsFollowingSymlinks(dir) {
|
|
|
1563
1666
|
function listSubdirsNoSymlinks(dir) {
|
|
1564
1667
|
let entries;
|
|
1565
1668
|
try {
|
|
1566
|
-
entries =
|
|
1669
|
+
entries = readdirSync5(dir, { withFileTypes: true });
|
|
1567
1670
|
} catch {
|
|
1568
1671
|
return [];
|
|
1569
1672
|
}
|
|
1570
|
-
return entries.filter((e) => e.isDirectory()).map((e) =>
|
|
1673
|
+
return entries.filter((e) => e.isDirectory()).map((e) => join11(dir, e.name));
|
|
1571
1674
|
}
|
|
1572
1675
|
function findSkillDirsUnderRoot(root) {
|
|
1573
1676
|
const found = /* @__PURE__ */ new Set();
|
|
1574
1677
|
const rootSubdirs = listSubdirsFollowingSymlinks(root);
|
|
1575
1678
|
for (const dir of rootSubdirs) {
|
|
1576
|
-
if (existsSync8(
|
|
1679
|
+
if (existsSync8(join11(dir, "SKILL.md"))) found.add(dir);
|
|
1577
1680
|
}
|
|
1578
|
-
for (const dir of listSubdirsFollowingSymlinks(
|
|
1579
|
-
if (existsSync8(
|
|
1681
|
+
for (const dir of listSubdirsFollowingSymlinks(join11(root, "skills"))) {
|
|
1682
|
+
if (existsSync8(join11(dir, "SKILL.md"))) found.add(dir);
|
|
1580
1683
|
}
|
|
1581
1684
|
for (const topDir of rootSubdirs) {
|
|
1582
|
-
for (const dir of listSubdirsNoSymlinks(
|
|
1583
|
-
if (existsSync8(
|
|
1685
|
+
for (const dir of listSubdirsNoSymlinks(join11(topDir, "skills"))) {
|
|
1686
|
+
if (existsSync8(join11(dir, "SKILL.md"))) found.add(dir);
|
|
1584
1687
|
}
|
|
1585
1688
|
}
|
|
1586
1689
|
return Array.from(found).filter((p) => !p.includes("/.git/"));
|
|
@@ -1594,7 +1697,7 @@ function collectSkillCandidates(roots) {
|
|
|
1594
1697
|
let realPath;
|
|
1595
1698
|
if (exists) {
|
|
1596
1699
|
try {
|
|
1597
|
-
realPath =
|
|
1700
|
+
realPath = realpathSync2(resolvedRoot);
|
|
1598
1701
|
} catch {
|
|
1599
1702
|
realPath = void 0;
|
|
1600
1703
|
}
|
|
@@ -1613,7 +1716,7 @@ function dedupeSkillCandidates(candidates, findings) {
|
|
|
1613
1716
|
for (const candidate of candidates) {
|
|
1614
1717
|
let realPath;
|
|
1615
1718
|
try {
|
|
1616
|
-
realPath =
|
|
1719
|
+
realPath = realpathSync2(candidate.configuredPath);
|
|
1617
1720
|
} catch (err) {
|
|
1618
1721
|
findings.push({
|
|
1619
1722
|
severity: "MEDIUM",
|
|
@@ -1676,7 +1779,7 @@ function estimateTokens(text) {
|
|
|
1676
1779
|
return Math.ceil(Buffer.byteLength(text, "utf-8") / 4);
|
|
1677
1780
|
}
|
|
1678
1781
|
function profileAuditedSkill(group, findings, unreadableSkillMdPaths) {
|
|
1679
|
-
const skillMdPath =
|
|
1782
|
+
const skillMdPath = join11(group.canonicalPath, "SKILL.md");
|
|
1680
1783
|
const base = {
|
|
1681
1784
|
path: group.canonicalPath,
|
|
1682
1785
|
skillMdPath,
|
|
@@ -1687,7 +1790,7 @@ function profileAuditedSkill(group, findings, unreadableSkillMdPaths) {
|
|
|
1687
1790
|
let sizeBytes;
|
|
1688
1791
|
try {
|
|
1689
1792
|
sizeBytes = statSync6(skillMdPath).size;
|
|
1690
|
-
text =
|
|
1793
|
+
text = readFileSync8(skillMdPath, "utf-8");
|
|
1691
1794
|
} catch (err) {
|
|
1692
1795
|
findings.push({
|
|
1693
1796
|
severity: "MEDIUM",
|
|
@@ -1765,7 +1868,7 @@ function auditMcpTarget(targetPath, notices) {
|
|
|
1765
1868
|
}
|
|
1766
1869
|
if (isToml) {
|
|
1767
1870
|
try {
|
|
1768
|
-
|
|
1871
|
+
readFileSync8(targetPath, "utf-8");
|
|
1769
1872
|
} catch (err) {
|
|
1770
1873
|
findings.push({
|
|
1771
1874
|
severity: "MEDIUM",
|
|
@@ -1780,7 +1883,7 @@ function auditMcpTarget(targetPath, notices) {
|
|
|
1780
1883
|
}
|
|
1781
1884
|
let raw;
|
|
1782
1885
|
try {
|
|
1783
|
-
raw =
|
|
1886
|
+
raw = readFileSync8(targetPath, "utf-8");
|
|
1784
1887
|
} catch (err) {
|
|
1785
1888
|
findings.push({
|
|
1786
1889
|
severity: "MEDIUM",
|
|
@@ -1856,7 +1959,7 @@ function defaultWhich(cmd) {
|
|
|
1856
1959
|
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
1857
1960
|
for (const dir of dirs) {
|
|
1858
1961
|
for (const ext of exts) {
|
|
1859
|
-
const candidate =
|
|
1962
|
+
const candidate = join11(dir, cmd + ext);
|
|
1860
1963
|
try {
|
|
1861
1964
|
if (statSync6(candidate).isFile()) return candidate;
|
|
1862
1965
|
} catch {
|
|
@@ -2208,10 +2311,10 @@ async function scaffoldFoundationSkill(config, profile, targetRoot) {
|
|
|
2208
2311
|
if (!isDir(resolvedRoot)) {
|
|
2209
2312
|
throw new UserError(`skills root does not exist: ${resolvedRoot}`);
|
|
2210
2313
|
}
|
|
2211
|
-
const destDir =
|
|
2314
|
+
const destDir = join11(resolvedRoot, "business-foundation");
|
|
2212
2315
|
let destLstat;
|
|
2213
2316
|
try {
|
|
2214
|
-
destLstat =
|
|
2317
|
+
destLstat = lstatSync2(destDir);
|
|
2215
2318
|
} catch {
|
|
2216
2319
|
destLstat = void 0;
|
|
2217
2320
|
}
|
|
@@ -2222,9 +2325,9 @@ async function scaffoldFoundationSkill(config, profile, targetRoot) {
|
|
|
2222
2325
|
throw new UserError(`refusing to scaffold a foundation skill at ${destDir}: already exists`);
|
|
2223
2326
|
}
|
|
2224
2327
|
const content = renderFoundationSkillMd(profile);
|
|
2225
|
-
const tempDir = mkdtempSync(
|
|
2328
|
+
const tempDir = mkdtempSync(join11(resolvedRoot, ".business-foundation-"));
|
|
2226
2329
|
try {
|
|
2227
|
-
writeFileSync5(
|
|
2330
|
+
writeFileSync5(join11(tempDir, "SKILL.md"), content, { flag: "wx", mode: 384 });
|
|
2228
2331
|
const safety = await scanSafety(tempDir, config.strictness);
|
|
2229
2332
|
if (safety.verdict === "block") {
|
|
2230
2333
|
throw new UserError(
|
|
@@ -2258,7 +2361,7 @@ async function scaffoldFoundationSkill(config, profile, targetRoot) {
|
|
|
2258
2361
|
|
|
2259
2362
|
// src/auditState.ts
|
|
2260
2363
|
function getAuditStatePath() {
|
|
2261
|
-
return
|
|
2364
|
+
return join12(getHomeDir(), "audit-state.json");
|
|
2262
2365
|
}
|
|
2263
2366
|
function emptyCounts() {
|
|
2264
2367
|
return { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
@@ -2315,7 +2418,7 @@ function readAuditState() {
|
|
|
2315
2418
|
const path = getAuditStatePath();
|
|
2316
2419
|
if (!existsSync9(path)) return void 0;
|
|
2317
2420
|
try {
|
|
2318
|
-
const parsed = JSON.parse(
|
|
2421
|
+
const parsed = JSON.parse(readFileSync9(path, "utf-8"));
|
|
2319
2422
|
if (parsed.schemaVersion !== 1 || typeof parsed.lastRunAt !== "string") return void 0;
|
|
2320
2423
|
return {
|
|
2321
2424
|
schemaVersion: 1,
|
|
@@ -2376,7 +2479,7 @@ function formatAuditAdvisory(state, options = {}) {
|
|
|
2376
2479
|
}
|
|
2377
2480
|
|
|
2378
2481
|
// src/commands/gatePipeline.ts
|
|
2379
|
-
import { join as
|
|
2482
|
+
import { join as join15 } from "path";
|
|
2380
2483
|
|
|
2381
2484
|
// src/gate/profile.ts
|
|
2382
2485
|
import { execFileSync } from "child_process";
|
|
@@ -2385,23 +2488,23 @@ import {
|
|
|
2385
2488
|
existsSync as existsSync11,
|
|
2386
2489
|
mkdtempSync as mkdtempSync2,
|
|
2387
2490
|
openSync,
|
|
2388
|
-
readFileSync as
|
|
2389
|
-
readdirSync as
|
|
2491
|
+
readFileSync as readFileSync11,
|
|
2492
|
+
readdirSync as readdirSync6,
|
|
2390
2493
|
readSync,
|
|
2391
2494
|
rmSync as rmSync4,
|
|
2392
2495
|
statSync as statSync7
|
|
2393
2496
|
} from "fs";
|
|
2394
2497
|
import { tmpdir } from "os";
|
|
2395
|
-
import { basename as basename8, dirname as dirname6, extname as extname5, join as
|
|
2498
|
+
import { basename as basename8, dirname as dirname6, extname as extname5, join as join14, resolve as resolve3 } from "path";
|
|
2396
2499
|
|
|
2397
2500
|
// src/gate/mcpCapabilities.ts
|
|
2398
|
-
import { existsSync as existsSync10, readFileSync as
|
|
2399
|
-
import { extname as extname4, join as
|
|
2501
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
2502
|
+
import { extname as extname4, join as join13 } from "path";
|
|
2400
2503
|
var SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
2401
2504
|
var MAX_ITEMS_PER_CATEGORY = 200;
|
|
2402
2505
|
function readJsonSafe(file) {
|
|
2403
2506
|
try {
|
|
2404
|
-
return JSON.parse(
|
|
2507
|
+
return JSON.parse(readFileSync10(file, "utf-8"));
|
|
2405
2508
|
} catch {
|
|
2406
2509
|
return void 0;
|
|
2407
2510
|
}
|
|
@@ -2502,7 +2605,7 @@ var MCP_SDK_IMPORT_RE = /@modelcontextprotocol\/sdk/;
|
|
|
2502
2605
|
function extractMcpCapabilities(packageDir) {
|
|
2503
2606
|
const buckets = { tools: /* @__PURE__ */ new Set(), resources: /* @__PURE__ */ new Set(), prompts: /* @__PURE__ */ new Set() };
|
|
2504
2607
|
let hasPackageJsonMarker = false;
|
|
2505
|
-
const pkgPath =
|
|
2608
|
+
const pkgPath = join13(packageDir, "package.json");
|
|
2506
2609
|
if (existsSync10(pkgPath)) {
|
|
2507
2610
|
const pkg2 = readJsonSafe(pkgPath);
|
|
2508
2611
|
if (pkg2 && typeof pkg2 === "object" && !Array.isArray(pkg2)) {
|
|
@@ -2510,7 +2613,7 @@ function extractMcpCapabilities(packageDir) {
|
|
|
2510
2613
|
}
|
|
2511
2614
|
}
|
|
2512
2615
|
for (const manifestName of [".mcp.json", "mcp.json"]) {
|
|
2513
|
-
const manifestPath =
|
|
2616
|
+
const manifestPath = join13(packageDir, manifestName);
|
|
2514
2617
|
if (!existsSync10(manifestPath)) continue;
|
|
2515
2618
|
const parsed = readJsonSafe(manifestPath);
|
|
2516
2619
|
if (parsed !== void 0) {
|
|
@@ -2618,7 +2721,7 @@ function asProfile(candidatePath, artifact) {
|
|
|
2618
2721
|
return asSkillProfile(profile, `at ${candidatePath}`);
|
|
2619
2722
|
}
|
|
2620
2723
|
function classifyInput(p) {
|
|
2621
|
-
if (existsSync11(
|
|
2724
|
+
if (existsSync11(join14(p, ".claude-plugin", "plugin.json"))) return "plugin";
|
|
2622
2725
|
if (detectArtifactType(p) === "mcp") return "mcp";
|
|
2623
2726
|
return "skill";
|
|
2624
2727
|
}
|
|
@@ -2626,10 +2729,10 @@ var MCP_SDK_DEP2 = "@modelcontextprotocol/sdk";
|
|
|
2626
2729
|
var MCP_KEYWORD2 = "mcp-server";
|
|
2627
2730
|
var MCP_BIN_PREFIX2 = /^mcp-/i;
|
|
2628
2731
|
function readPackageJsonSafe(root) {
|
|
2629
|
-
const pkgPath =
|
|
2732
|
+
const pkgPath = join14(root, "package.json");
|
|
2630
2733
|
if (!existsSync11(pkgPath)) return void 0;
|
|
2631
2734
|
try {
|
|
2632
|
-
return JSON.parse(
|
|
2735
|
+
return JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
2633
2736
|
} catch {
|
|
2634
2737
|
return void 0;
|
|
2635
2738
|
}
|
|
@@ -2650,7 +2753,7 @@ function hasMcpPackageMarkers(pkg2) {
|
|
|
2650
2753
|
return false;
|
|
2651
2754
|
}
|
|
2652
2755
|
function detectArtifactType(root) {
|
|
2653
|
-
if (existsSync11(
|
|
2756
|
+
if (existsSync11(join14(root, ".mcp.json")) || existsSync11(join14(root, "mcp.json"))) return "mcp";
|
|
2654
2757
|
const pkg2 = readPackageJsonSafe(root);
|
|
2655
2758
|
if (pkg2 && hasMcpPackageMarkers(pkg2)) return "mcp";
|
|
2656
2759
|
return "skill";
|
|
@@ -2660,7 +2763,7 @@ function resolveSkillMd(inputPath) {
|
|
|
2660
2763
|
let tmpDir;
|
|
2661
2764
|
const initialStat = statSync7(currentPath);
|
|
2662
2765
|
if (extname5(currentPath) === ".skill" || initialStat.isFile() && isZipFile(currentPath)) {
|
|
2663
|
-
tmpDir = mkdtempSync2(
|
|
2766
|
+
tmpDir = mkdtempSync2(join14(tmpdir(), "forge_"));
|
|
2664
2767
|
extractZip(currentPath, tmpDir);
|
|
2665
2768
|
currentPath = tmpDir;
|
|
2666
2769
|
}
|
|
@@ -2669,7 +2772,7 @@ function resolveSkillMd(inputPath) {
|
|
|
2669
2772
|
return { skillMd: currentPath, root: dirname6(currentPath), tmpDir };
|
|
2670
2773
|
}
|
|
2671
2774
|
if (stat.isDirectory()) {
|
|
2672
|
-
const direct =
|
|
2775
|
+
const direct = join14(currentPath, "SKILL.md");
|
|
2673
2776
|
if (existsSync11(direct)) {
|
|
2674
2777
|
return { skillMd: direct, root: currentPath, tmpDir };
|
|
2675
2778
|
}
|
|
@@ -2693,17 +2796,17 @@ function cleanupTmp(tmpDir) {
|
|
|
2693
2796
|
}
|
|
2694
2797
|
function findSkillMdOneLevelDown(dir) {
|
|
2695
2798
|
const matches = /* @__PURE__ */ new Set();
|
|
2696
|
-
for (const entry of
|
|
2799
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
2697
2800
|
if (entry.isDirectory()) {
|
|
2698
|
-
const candidate =
|
|
2801
|
+
const candidate = join14(dir, entry.name, "SKILL.md");
|
|
2699
2802
|
if (existsSync11(candidate)) matches.add(candidate);
|
|
2700
2803
|
}
|
|
2701
2804
|
}
|
|
2702
|
-
const skillsDir =
|
|
2805
|
+
const skillsDir = join14(dir, "skills");
|
|
2703
2806
|
if (isDir(skillsDir)) {
|
|
2704
|
-
for (const entry of
|
|
2807
|
+
for (const entry of readdirSync6(skillsDir, { withFileTypes: true })) {
|
|
2705
2808
|
if (entry.isDirectory()) {
|
|
2706
|
-
const candidate =
|
|
2809
|
+
const candidate = join14(skillsDir, entry.name, "SKILL.md");
|
|
2707
2810
|
if (existsSync11(candidate)) matches.add(candidate);
|
|
2708
2811
|
}
|
|
2709
2812
|
}
|
|
@@ -2787,9 +2890,9 @@ function detectLicense(root, fm) {
|
|
|
2787
2890
|
if (fm.license) return fm.license;
|
|
2788
2891
|
const names = ["LICENSE", "LICENSE.md", "LICENSE.txt", "COPYING", "license"];
|
|
2789
2892
|
for (const name of names) {
|
|
2790
|
-
const p =
|
|
2893
|
+
const p = join14(root, name);
|
|
2791
2894
|
if (existsSync11(p) && statSync7(p).isFile()) {
|
|
2792
|
-
const head =
|
|
2895
|
+
const head = readFileSync11(p, "utf-8").slice(0, 2e3).toLowerCase();
|
|
2793
2896
|
for (const [hint, label] of Object.entries(SPDX_HINTS)) {
|
|
2794
2897
|
if (head.includes(hint)) return label;
|
|
2795
2898
|
}
|
|
@@ -2800,8 +2903,8 @@ function detectLicense(root, fm) {
|
|
|
2800
2903
|
}
|
|
2801
2904
|
function walkFiles(dir) {
|
|
2802
2905
|
const out = [];
|
|
2803
|
-
for (const entry of
|
|
2804
|
-
const full =
|
|
2906
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
2907
|
+
const full = join14(dir, entry.name);
|
|
2805
2908
|
if (entry.isDirectory()) {
|
|
2806
2909
|
out.push(...walkFiles(full));
|
|
2807
2910
|
} else if (entry.isFile()) {
|
|
@@ -2813,7 +2916,7 @@ function walkFiles(dir) {
|
|
|
2813
2916
|
function countResources(root) {
|
|
2814
2917
|
const resources = {};
|
|
2815
2918
|
for (const d of RESOURCE_DIRS) {
|
|
2816
|
-
const dp =
|
|
2919
|
+
const dp = join14(root, d);
|
|
2817
2920
|
if (isDir(dp)) {
|
|
2818
2921
|
resources[d] = walkFiles(dp).length;
|
|
2819
2922
|
}
|
|
@@ -2824,11 +2927,11 @@ function scanMcpAndImports(text, root) {
|
|
|
2824
2927
|
const mcpRefRe = /mcp__[a-zA-Z0-9_]+/g;
|
|
2825
2928
|
const mcpRefs = new Set(text.match(mcpRefRe) ?? []);
|
|
2826
2929
|
const imports = /* @__PURE__ */ new Set();
|
|
2827
|
-
const scriptsDir =
|
|
2930
|
+
const scriptsDir = join14(root, "scripts");
|
|
2828
2931
|
if (isDir(scriptsDir)) {
|
|
2829
2932
|
for (const f of walkFiles(scriptsDir)) {
|
|
2830
2933
|
if (!f.endsWith(".py")) continue;
|
|
2831
|
-
const ft =
|
|
2934
|
+
const ft = readFileSync11(f, "utf-8");
|
|
2832
2935
|
for (const m2 of ft.match(mcpRefRe) ?? []) mcpRefs.add(m2);
|
|
2833
2936
|
const importRe = /^\s*(?:import|from)\s+([a-zA-Z0-9_]+)/gm;
|
|
2834
2937
|
let m;
|
|
@@ -2845,7 +2948,7 @@ function scanMcpAndImports(text, root) {
|
|
|
2845
2948
|
function profileSkillDir(inputPath) {
|
|
2846
2949
|
const { skillMd, root, tmpDir } = resolveSkillMd(inputPath);
|
|
2847
2950
|
try {
|
|
2848
|
-
const text =
|
|
2951
|
+
const text = readFileSync11(skillMd, "utf-8");
|
|
2849
2952
|
const fm = parseFrontmatter(text);
|
|
2850
2953
|
const bodyLines = (text.match(/\n/g)?.length ?? 0) + 1;
|
|
2851
2954
|
const headers = Array.from(text.matchAll(/^#{1,3}\s+(.+)$/gm)).map((m) => m[1]);
|
|
@@ -2873,20 +2976,20 @@ function profileSkillDir(inputPath) {
|
|
|
2873
2976
|
}
|
|
2874
2977
|
}
|
|
2875
2978
|
function profilePlugin(p) {
|
|
2876
|
-
const manifestPath =
|
|
2979
|
+
const manifestPath = join14(p, ".claude-plugin", "plugin.json");
|
|
2877
2980
|
let manifest;
|
|
2878
2981
|
try {
|
|
2879
|
-
manifest = JSON.parse(
|
|
2982
|
+
manifest = JSON.parse(readFileSync11(manifestPath, "utf-8"));
|
|
2880
2983
|
} catch (err) {
|
|
2881
2984
|
throw new Error(`could not parse plugin.json under ${p}: ${err.message}`);
|
|
2882
2985
|
}
|
|
2883
|
-
const skillsDir =
|
|
2986
|
+
const skillsDir = join14(p, "skills");
|
|
2884
2987
|
let skills = [];
|
|
2885
2988
|
if (isDir(skillsDir)) {
|
|
2886
|
-
skills =
|
|
2989
|
+
skills = readdirSync6(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && existsSync11(join14(skillsDir, d.name, "SKILL.md"))).map((d) => d.name).sort();
|
|
2887
2990
|
}
|
|
2888
|
-
const commandsDir =
|
|
2889
|
-
const commands = isDir(commandsDir) ?
|
|
2991
|
+
const commandsDir = join14(p, "commands");
|
|
2992
|
+
const commands = isDir(commandsDir) ? readdirSync6(commandsDir).filter((f) => f.endsWith(".md")).length : 0;
|
|
2890
2993
|
const mc = manifest.mcpServers ?? manifest.mcp_servers ?? {};
|
|
2891
2994
|
const mcpServers = typeof mc === "object" && mc !== null ? Object.keys(mc).sort() : [];
|
|
2892
2995
|
return {
|
|
@@ -2903,11 +3006,11 @@ function profilePlugin(p) {
|
|
|
2903
3006
|
};
|
|
2904
3007
|
}
|
|
2905
3008
|
function profileMcp(p) {
|
|
2906
|
-
const explicitConfig = existsSync11(
|
|
3009
|
+
const explicitConfig = existsSync11(join14(p, ".mcp.json")) ? join14(p, ".mcp.json") : existsSync11(join14(p, "mcp.json")) ? join14(p, "mcp.json") : void 0;
|
|
2907
3010
|
if (explicitConfig) {
|
|
2908
3011
|
let cfg;
|
|
2909
3012
|
try {
|
|
2910
|
-
cfg = JSON.parse(
|
|
3013
|
+
cfg = JSON.parse(readFileSync11(explicitConfig, "utf-8"));
|
|
2911
3014
|
} catch (err) {
|
|
2912
3015
|
throw new Error(`could not parse MCP config ${explicitConfig}: ${err.message}`);
|
|
2913
3016
|
}
|
|
@@ -2923,10 +3026,10 @@ function profileMcp(p) {
|
|
|
2923
3026
|
capabilities: extractMcpCapabilities(p)
|
|
2924
3027
|
};
|
|
2925
3028
|
}
|
|
2926
|
-
const pkgPath =
|
|
3029
|
+
const pkgPath = join14(p, "package.json");
|
|
2927
3030
|
let pkg2;
|
|
2928
3031
|
try {
|
|
2929
|
-
pkg2 = JSON.parse(
|
|
3032
|
+
pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
2930
3033
|
} catch (err) {
|
|
2931
3034
|
throw new Error(`could not parse package.json ${pkgPath}: ${err.message}`);
|
|
2932
3035
|
}
|
|
@@ -2943,14 +3046,14 @@ function profileMcp(p) {
|
|
|
2943
3046
|
}
|
|
2944
3047
|
|
|
2945
3048
|
// src/gate/mcpOverlap.ts
|
|
2946
|
-
import { existsSync as existsSync12, readFileSync as
|
|
3049
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
2947
3050
|
import { resolve as resolve4 } from "path";
|
|
2948
3051
|
function readTargetServers(targetPath) {
|
|
2949
3052
|
const path = resolve4(expandHome(targetPath));
|
|
2950
3053
|
if (!existsSync12(path)) return [];
|
|
2951
3054
|
let cfg;
|
|
2952
3055
|
try {
|
|
2953
|
-
cfg = JSON.parse(
|
|
3056
|
+
cfg = JSON.parse(readFileSync12(path, "utf-8"));
|
|
2954
3057
|
} catch {
|
|
2955
3058
|
return [];
|
|
2956
3059
|
}
|
|
@@ -3151,10 +3254,10 @@ function resolveMcpCandidatePath(entry) {
|
|
|
3151
3254
|
if (entry.packageDir) return entry.packageDir;
|
|
3152
3255
|
let children = [];
|
|
3153
3256
|
try {
|
|
3154
|
-
children =
|
|
3257
|
+
children = readdirSync7(entry.quarantinePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
3155
3258
|
} catch {
|
|
3156
3259
|
}
|
|
3157
|
-
return children.length === 1 ?
|
|
3260
|
+
return children.length === 1 ? join15(entry.quarantinePath, children[0]) : entry.quarantinePath;
|
|
3158
3261
|
}
|
|
3159
3262
|
async function runGate(candidatePath, skillsRoot, {
|
|
3160
3263
|
proFeatureLabel = "overlap analysis",
|
|
@@ -3186,17 +3289,17 @@ async function runGate(candidatePath, skillsRoot, {
|
|
|
3186
3289
|
|
|
3187
3290
|
// src/queue.ts
|
|
3188
3291
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3189
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as
|
|
3190
|
-
import { join as
|
|
3292
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3293
|
+
import { join as join16 } from "path";
|
|
3191
3294
|
function getQueuePath() {
|
|
3192
|
-
return
|
|
3295
|
+
return join16(getHomeDir(), "queue.json");
|
|
3193
3296
|
}
|
|
3194
3297
|
function readQueue() {
|
|
3195
3298
|
const path = getQueuePath();
|
|
3196
3299
|
if (!existsSync13(path)) {
|
|
3197
3300
|
return { version: 1, entries: [] };
|
|
3198
3301
|
}
|
|
3199
|
-
const raw =
|
|
3302
|
+
const raw = readFileSync13(path, "utf-8");
|
|
3200
3303
|
const parsed = JSON.parse(raw);
|
|
3201
3304
|
return { version: 1, entries: parsed.entries ?? [] };
|
|
3202
3305
|
}
|
|
@@ -3204,7 +3307,7 @@ function writeQueue(queue2) {
|
|
|
3204
3307
|
const home = getHomeDir();
|
|
3205
3308
|
mkdirSync6(home, { recursive: true });
|
|
3206
3309
|
const finalPath = getQueuePath();
|
|
3207
|
-
const tempPath =
|
|
3310
|
+
const tempPath = join16(home, `.queue.json.tmp-${randomUUID3()}`);
|
|
3208
3311
|
try {
|
|
3209
3312
|
writeFileSync6(tempPath, JSON.stringify(queue2, null, 2) + "\n", "utf-8");
|
|
3210
3313
|
renameSync4(tempPath, finalPath);
|
|
@@ -3254,15 +3357,15 @@ function generateId(existingIds) {
|
|
|
3254
3357
|
// src/handoff.ts
|
|
3255
3358
|
import { spawnSync } from "child_process";
|
|
3256
3359
|
import { existsSync as existsSync14 } from "fs";
|
|
3257
|
-
import { dirname as dirname7, join as
|
|
3360
|
+
import { dirname as dirname7, join as join17 } from "path";
|
|
3258
3361
|
import { fileURLToPath } from "url";
|
|
3259
3362
|
var KNOWN_AGENT_BINARIES = ["claude", "codex", "cursor-agent", "windsurf", "opencode", "gemini"];
|
|
3260
3363
|
var HANDOFF_INSTRUCTION = "Read {prompt} and follow its instructions for the skill installed at {path}";
|
|
3261
3364
|
function resolvePromptPath(assetName = "ingest-prompt.md") {
|
|
3262
3365
|
const here = dirname7(fileURLToPath(import.meta.url));
|
|
3263
|
-
const builtCandidate =
|
|
3366
|
+
const builtCandidate = join17(here, assetName);
|
|
3264
3367
|
if (existsSync14(builtCandidate)) return builtCandidate;
|
|
3265
|
-
return
|
|
3368
|
+
return join17(here, "..", "assets", assetName);
|
|
3266
3369
|
}
|
|
3267
3370
|
function defaultIsOnPath(bin) {
|
|
3268
3371
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
@@ -3325,7 +3428,7 @@ import { createInterface as createInterface3 } from "readline/promises";
|
|
|
3325
3428
|
// src/agents.ts
|
|
3326
3429
|
import { statSync as statSync8 } from "fs";
|
|
3327
3430
|
import { homedir as homedir3 } from "os";
|
|
3328
|
-
import { join as
|
|
3431
|
+
import { join as join18, resolve as resolve5 } from "path";
|
|
3329
3432
|
function isFile(p) {
|
|
3330
3433
|
try {
|
|
3331
3434
|
return statSync8(p).isFile();
|
|
@@ -3339,7 +3442,7 @@ function projectPath(root, cwd) {
|
|
|
3339
3442
|
function globalPath(root, home) {
|
|
3340
3443
|
if (!root) return void 0;
|
|
3341
3444
|
if (root === "~") return home;
|
|
3342
|
-
if (root.startsWith("~/")) return
|
|
3445
|
+
if (root.startsWith("~/")) return join18(home, root.slice(2));
|
|
3343
3446
|
return root;
|
|
3344
3447
|
}
|
|
3345
3448
|
function makeAgent(spec) {
|
|
@@ -3570,7 +3673,7 @@ var AGENTS = [
|
|
|
3570
3673
|
|
|
3571
3674
|
// src/commands/audit.ts
|
|
3572
3675
|
import { existsSync as existsSync15 } from "fs";
|
|
3573
|
-
import { join as
|
|
3676
|
+
import { join as join19, resolve as resolvePath3 } from "path";
|
|
3574
3677
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
3575
3678
|
|
|
3576
3679
|
// src/decision.ts
|
|
@@ -3638,7 +3741,7 @@ async function captureBusinessProfile(rl, existing) {
|
|
|
3638
3741
|
function resolveReportPath(reportOption) {
|
|
3639
3742
|
if (reportOption) return resolvePath3(reportOption);
|
|
3640
3743
|
const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3641
|
-
return
|
|
3744
|
+
return join19(getHomeDir(), "reports", `audit-${iso}.md`);
|
|
3642
3745
|
}
|
|
3643
3746
|
function printTerminalSummary(report, reportPath) {
|
|
3644
3747
|
const { summary } = report;
|
|
@@ -4063,6 +4166,7 @@ async function add(source, options = {}) {
|
|
|
4063
4166
|
}
|
|
4064
4167
|
const resolved = resolve(source, artifact);
|
|
4065
4168
|
const entry = await quarantine(resolved, config.quarantineDir);
|
|
4169
|
+
const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;
|
|
4066
4170
|
if (entry.skillDirs.length === 0) {
|
|
4067
4171
|
console.error(`[skill-forge] no SKILL.md found in installed source: ${source}`);
|
|
4068
4172
|
reject(entry);
|
|
@@ -4088,7 +4192,7 @@ async function add(source, options = {}) {
|
|
|
4088
4192
|
const decision = options.yes || options.json ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, "this skill");
|
|
4089
4193
|
let installedPath = null;
|
|
4090
4194
|
if (decision === "promote") {
|
|
4091
|
-
const result = promote(entry, targetRoot);
|
|
4195
|
+
const result = promote(entry, targetRoot, { expectedFingerprint: gatedFingerprint });
|
|
4092
4196
|
logOutcome(`[skill-forge] promoted -> ${result.promoted.join(", ")}`, options.json);
|
|
4093
4197
|
if (proEnabled) {
|
|
4094
4198
|
try {
|
|
@@ -4179,7 +4283,7 @@ async function addMcp(source, config, options) {
|
|
|
4179
4283
|
envVars: detail.env ? Object.keys(detail.env) : void 0,
|
|
4180
4284
|
candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
|
|
4181
4285
|
};
|
|
4182
|
-
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ??
|
|
4286
|
+
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join20(process.cwd(), ".mcp.json");
|
|
4183
4287
|
const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
|
|
4184
4288
|
reject(entry);
|
|
4185
4289
|
logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
|
|
@@ -4274,7 +4378,7 @@ async function scan(source, options = {}) {
|
|
|
4274
4378
|
}
|
|
4275
4379
|
|
|
4276
4380
|
// src/commands/list.ts
|
|
4277
|
-
import { join as
|
|
4381
|
+
import { join as join21 } from "path";
|
|
4278
4382
|
async function list() {
|
|
4279
4383
|
await maybeOfferInit();
|
|
4280
4384
|
const config = loadConfig();
|
|
@@ -4287,14 +4391,14 @@ async function list() {
|
|
|
4287
4391
|
}
|
|
4288
4392
|
console.log(`[skill-forge] ${ids.length} held in quarantine (${quarantineDir}):`);
|
|
4289
4393
|
for (const id of ids) {
|
|
4290
|
-
const skillDirs = findSkillDirs(
|
|
4394
|
+
const skillDirs = findSkillDirs(join21(quarantineDir, id));
|
|
4291
4395
|
if (skillDirs.length === 0) {
|
|
4292
4396
|
console.log(` ${id} (no SKILL.md found)`);
|
|
4293
4397
|
continue;
|
|
4294
4398
|
}
|
|
4295
4399
|
const names = skillDirs.map((dir) => {
|
|
4296
4400
|
try {
|
|
4297
|
-
return readFrontmatter(
|
|
4401
|
+
return readFrontmatter(join21(dir, "SKILL.md")).name;
|
|
4298
4402
|
} catch {
|
|
4299
4403
|
return dir;
|
|
4300
4404
|
}
|
|
@@ -4307,20 +4411,20 @@ async function list() {
|
|
|
4307
4411
|
import { existsSync as existsSync18 } from "fs";
|
|
4308
4412
|
|
|
4309
4413
|
// src/commands/held.ts
|
|
4310
|
-
import { existsSync as existsSync17, realpathSync as
|
|
4311
|
-
import { basename as basename9, join as
|
|
4414
|
+
import { existsSync as existsSync17, realpathSync as realpathSync3 } from "fs";
|
|
4415
|
+
import { basename as basename9, join as join22, resolve as resolvePath4, sep as sep2 } from "path";
|
|
4312
4416
|
var PRO_FEATURE_LABEL2 = "overlap analysis, the provenance ledger, and the pending-ingestion queue";
|
|
4313
4417
|
function resolveHeldPath(quarantineDir, id) {
|
|
4314
4418
|
if (id === "" || id !== basename9(id) || id === "." || id === "..") {
|
|
4315
4419
|
throw new UserError(`invalid quarantine id "${id}" \u2014 pass a single id exactly as printed by \`skill-forge list\``);
|
|
4316
4420
|
}
|
|
4317
|
-
const candidate =
|
|
4421
|
+
const candidate = join22(quarantineDir, id);
|
|
4318
4422
|
if (!existsSync17(candidate)) {
|
|
4319
4423
|
throw new UserError(`no held entry with id "${id}" \u2014 run \`skill-forge list\` to see what is in quarantine`);
|
|
4320
4424
|
}
|
|
4321
|
-
const realRoot =
|
|
4322
|
-
const realCandidate =
|
|
4323
|
-
if (!realCandidate.startsWith(realRoot +
|
|
4425
|
+
const realRoot = realpathSync3(resolvePath4(quarantineDir));
|
|
4426
|
+
const realCandidate = realpathSync3(candidate);
|
|
4427
|
+
if (!realCandidate.startsWith(realRoot + sep2)) {
|
|
4324
4428
|
throw new UserError(`refusing to act on "${id}": it resolves outside the quarantine dir (${realRoot})`);
|
|
4325
4429
|
}
|
|
4326
4430
|
return realCandidate;
|
|
@@ -4343,7 +4447,7 @@ function resolveTargetRoot(config, target) {
|
|
|
4343
4447
|
function heldSkillNames(skillDirs) {
|
|
4344
4448
|
return skillDirs.map((dir) => {
|
|
4345
4449
|
try {
|
|
4346
|
-
return readFrontmatter(
|
|
4450
|
+
return readFrontmatter(join22(dir, "SKILL.md")).name;
|
|
4347
4451
|
} catch {
|
|
4348
4452
|
return basename9(dir);
|
|
4349
4453
|
}
|
|
@@ -4351,7 +4455,7 @@ function heldSkillNames(skillDirs) {
|
|
|
4351
4455
|
}
|
|
4352
4456
|
function looksLikeMcpSandbox(quarantinePath) {
|
|
4353
4457
|
const candidate = resolveMcpCandidatePath({ quarantinePath });
|
|
4354
|
-
return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync17(
|
|
4458
|
+
return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync17(join22(candidate, f)));
|
|
4355
4459
|
}
|
|
4356
4460
|
async function promoteHeldMcp(id, entry, config, options) {
|
|
4357
4461
|
const candidatePath = resolveMcpCandidatePath(entry);
|
|
@@ -4385,7 +4489,7 @@ async function promoteHeldMcp(id, entry, config, options) {
|
|
|
4385
4489
|
envVars: detail.env ? Object.keys(detail.env) : void 0,
|
|
4386
4490
|
candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
|
|
4387
4491
|
};
|
|
4388
|
-
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ??
|
|
4492
|
+
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join22(process.cwd(), ".mcp.json");
|
|
4389
4493
|
const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
|
|
4390
4494
|
reject(entry);
|
|
4391
4495
|
logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
|
|
@@ -4430,6 +4534,7 @@ async function promoteHeld(id, options = {}) {
|
|
|
4430
4534
|
}
|
|
4431
4535
|
const targetRoot = resolveTargetRoot(config, options.target);
|
|
4432
4536
|
const candidatePath = entry.skillDirs[0];
|
|
4537
|
+
const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;
|
|
4433
4538
|
logOutcome(`[skill-forge] re-gating held entry ${id} (${heldSkillNames(entry.skillDirs).join(", ")})`, options.json);
|
|
4434
4539
|
const { safety, gateResult, proEnabled } = await runGate(candidatePath, targetRoot, {
|
|
4435
4540
|
proFeatureLabel: PRO_FEATURE_LABEL2,
|
|
@@ -4450,7 +4555,7 @@ async function promoteHeld(id, options = {}) {
|
|
|
4450
4555
|
logOutcome(`[skill-forge] rejected \u2014 removed ${quarantinePath}`, options.json);
|
|
4451
4556
|
return;
|
|
4452
4557
|
}
|
|
4453
|
-
const result = promote(entry, targetRoot, { force: options.force });
|
|
4558
|
+
const result = promote(entry, targetRoot, { force: options.force, expectedFingerprint: gatedFingerprint });
|
|
4454
4559
|
logOutcome(`[skill-forge] promoted ${result.promoted.length} skill dir(s):`, options.json);
|
|
4455
4560
|
for (const promotedPath of result.promoted) logOutcome(` ${promotedPath}`, options.json);
|
|
4456
4561
|
if (!proEnabled) return;
|
|
@@ -4858,17 +4963,17 @@ async function status() {
|
|
|
4858
4963
|
|
|
4859
4964
|
// src/evolve.ts
|
|
4860
4965
|
import { createInterface as createInterface5 } from "readline/promises";
|
|
4861
|
-
import { createHash } from "crypto";
|
|
4862
|
-
import { cpSync as cpSync3, existsSync as existsSync19, lstatSync as
|
|
4966
|
+
import { createHash as createHash2 } from "crypto";
|
|
4967
|
+
import { cpSync as cpSync3, existsSync as existsSync19, lstatSync as lstatSync3, mkdtempSync as mkdtempSync3, readFileSync as readFileSync14, rmSync as rmSync6 } from "fs";
|
|
4863
4968
|
import { tmpdir as tmpdir2 } from "os";
|
|
4864
|
-
import { basename as basename10, dirname as dirname9, join as
|
|
4969
|
+
import { basename as basename10, dirname as dirname9, join as join23, resolve as resolvePath5, sep as sep3 } from "path";
|
|
4865
4970
|
var SKILLOPT_BIN = "skillopt-sleep";
|
|
4866
4971
|
var PRO_FEATURE_LABEL3 = "self-evolution via SkillOpt-Sleep (evolve) \u2014 re-gating, provenance, and the pending-ingestion queue";
|
|
4867
4972
|
async function evolve(targetSkillDir, options = {}, deps = {}) {
|
|
4868
4973
|
const exec = deps.exec ?? defaultExec;
|
|
4869
4974
|
const json = options.json;
|
|
4870
4975
|
const resolvedTargetDir = resolvePath5(targetSkillDir);
|
|
4871
|
-
const skillMdPath =
|
|
4976
|
+
const skillMdPath = join23(resolvedTargetDir, "SKILL.md");
|
|
4872
4977
|
if (!existsSync19(skillMdPath)) {
|
|
4873
4978
|
throw new UserError(`no SKILL.md found at ${skillMdPath} \u2014 evolve requires an existing, installed skill directory`);
|
|
4874
4979
|
}
|
|
@@ -4927,16 +5032,16 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
|
|
|
4927
5032
|
}
|
|
4928
5033
|
return;
|
|
4929
5034
|
}
|
|
4930
|
-
const stagingRoot =
|
|
5035
|
+
const stagingRoot = join23(project, ".skillopt-sleep", "staging");
|
|
4931
5036
|
const stagingDir = resolvePath5(parsed.staging_dir);
|
|
4932
|
-
if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot +
|
|
5037
|
+
if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep3)) {
|
|
4933
5038
|
throw new UserError(
|
|
4934
5039
|
`skillopt-sleep reported a staging_dir outside the expected sandbox (${stagingRoot}): ${stagingDir} \u2014 refusing to use it`
|
|
4935
5040
|
);
|
|
4936
5041
|
}
|
|
4937
|
-
const proposedSkillMd =
|
|
4938
|
-
const proposedClaudeMd =
|
|
4939
|
-
const reportMdPath =
|
|
5042
|
+
const proposedSkillMd = join23(stagingDir, "proposed_SKILL.md");
|
|
5043
|
+
const proposedClaudeMd = join23(stagingDir, "proposed_CLAUDE.md");
|
|
5044
|
+
const reportMdPath = join23(stagingDir, "report.md");
|
|
4940
5045
|
if (!existsSync19(proposedSkillMd)) {
|
|
4941
5046
|
throw new UserError(`skillopt-sleep reported an accepted proposal but ${proposedSkillMd} is missing`);
|
|
4942
5047
|
}
|
|
@@ -4947,13 +5052,13 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
|
|
|
4947
5052
|
assertNotSymlink(proposedClaudeMd, "proposed_CLAUDE.md");
|
|
4948
5053
|
const scannedSkillHash = sha256File(proposedSkillMd);
|
|
4949
5054
|
const scannedClaudeHash = sha256File(proposedClaudeMd);
|
|
4950
|
-
const tempDir = mkdtempSync3(
|
|
5055
|
+
const tempDir = mkdtempSync3(join23(tmpdir2(), "skill-forge-evolve-"));
|
|
4951
5056
|
let profile;
|
|
4952
5057
|
let safety;
|
|
4953
5058
|
try {
|
|
4954
|
-
cpSync3(proposedSkillMd,
|
|
5059
|
+
cpSync3(proposedSkillMd, join23(tempDir, "SKILL.md"));
|
|
4955
5060
|
if (existsSync19(proposedClaudeMd)) {
|
|
4956
|
-
cpSync3(proposedClaudeMd,
|
|
5061
|
+
cpSync3(proposedClaudeMd, join23(tempDir, "CLAUDE.md"));
|
|
4957
5062
|
}
|
|
4958
5063
|
profile = asSkillProfile(profileSkill(tempDir), "for the evolved proposal");
|
|
4959
5064
|
const strictness = loadConfig().strictness;
|
|
@@ -5094,10 +5199,10 @@ function validateRunOutput(raw) {
|
|
|
5094
5199
|
return { accepted: Boolean(obj.accepted), staging_dir: stagingDirRaw ?? null };
|
|
5095
5200
|
}
|
|
5096
5201
|
function readManifest(stagingDir) {
|
|
5097
|
-
const manifestPath =
|
|
5202
|
+
const manifestPath = join23(stagingDir, "manifest.json");
|
|
5098
5203
|
let raw;
|
|
5099
5204
|
try {
|
|
5100
|
-
raw =
|
|
5205
|
+
raw = readFileSync14(manifestPath, "utf-8");
|
|
5101
5206
|
} catch (err) {
|
|
5102
5207
|
throw new UserError(`could not read staging manifest ${manifestPath}: ${err.message}`);
|
|
5103
5208
|
}
|
|
@@ -5132,11 +5237,11 @@ function validateManifest(raw, manifestPath) {
|
|
|
5132
5237
|
}
|
|
5133
5238
|
function filesIdentical(a, b) {
|
|
5134
5239
|
if (!existsSync19(a) || !existsSync19(b)) return false;
|
|
5135
|
-
return
|
|
5240
|
+
return readFileSync14(a).equals(readFileSync14(b));
|
|
5136
5241
|
}
|
|
5137
5242
|
function isAlreadyAdopted(stagingDir, manifest) {
|
|
5138
|
-
const proposedSkillMd =
|
|
5139
|
-
const proposedClaudeMd =
|
|
5243
|
+
const proposedSkillMd = join23(stagingDir, "proposed_SKILL.md");
|
|
5244
|
+
const proposedClaudeMd = join23(stagingDir, "proposed_CLAUDE.md");
|
|
5140
5245
|
let sawComparison = false;
|
|
5141
5246
|
if (existsSync19(proposedSkillMd)) {
|
|
5142
5247
|
sawComparison = true;
|
|
@@ -5151,7 +5256,7 @@ function isAlreadyAdopted(stagingDir, manifest) {
|
|
|
5151
5256
|
function assertNotSymlink(path, label) {
|
|
5152
5257
|
let stat;
|
|
5153
5258
|
try {
|
|
5154
|
-
stat =
|
|
5259
|
+
stat = lstatSync3(path);
|
|
5155
5260
|
} catch {
|
|
5156
5261
|
return;
|
|
5157
5262
|
}
|
|
@@ -5161,7 +5266,7 @@ function assertNotSymlink(path, label) {
|
|
|
5161
5266
|
}
|
|
5162
5267
|
function sha256File(path) {
|
|
5163
5268
|
if (!existsSync19(path)) return null;
|
|
5164
|
-
return
|
|
5269
|
+
return createHash2("sha256").update(readFileSync14(path)).digest("hex");
|
|
5165
5270
|
}
|
|
5166
5271
|
|
|
5167
5272
|
// src/commands/evolve.ts
|
|
@@ -5173,21 +5278,21 @@ async function evolveCommand(targetSkillDir, options = {}) {
|
|
|
5173
5278
|
import { resolve as resolvePath7 } from "path";
|
|
5174
5279
|
|
|
5175
5280
|
// src/organize.ts
|
|
5176
|
-
import { existsSync as existsSync20, readFileSync as
|
|
5177
|
-
import { basename as basename11, join as
|
|
5281
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, readdirSync as readdirSync8, realpathSync as realpathSync4, statSync as statSync9 } from "fs";
|
|
5282
|
+
import { basename as basename11, join as join24, resolve as resolvePath6 } from "path";
|
|
5178
5283
|
var PRO_FEATURE_LABEL4 = "set-level organizer reports";
|
|
5179
5284
|
var TIERS = /* @__PURE__ */ new Set(["resource", "custom"]);
|
|
5180
5285
|
var MATURITIES = /* @__PURE__ */ new Set(["seedling", "stable", "deprecated"]);
|
|
5181
5286
|
function listSubdirs2(dir) {
|
|
5182
5287
|
let entries;
|
|
5183
5288
|
try {
|
|
5184
|
-
entries =
|
|
5289
|
+
entries = readdirSync8(dir, { withFileTypes: true });
|
|
5185
5290
|
} catch {
|
|
5186
5291
|
return [];
|
|
5187
5292
|
}
|
|
5188
5293
|
const out = [];
|
|
5189
5294
|
for (const entry of entries) {
|
|
5190
|
-
const full =
|
|
5295
|
+
const full = join24(dir, entry.name);
|
|
5191
5296
|
if (entry.isDirectory()) {
|
|
5192
5297
|
out.push(full);
|
|
5193
5298
|
continue;
|
|
@@ -5205,14 +5310,14 @@ function findSkillDirsUnderRoot2(root) {
|
|
|
5205
5310
|
const found = /* @__PURE__ */ new Set();
|
|
5206
5311
|
const topDirs = listSubdirs2(root);
|
|
5207
5312
|
for (const dir of topDirs) {
|
|
5208
|
-
if (existsSync20(
|
|
5313
|
+
if (existsSync20(join24(dir, "SKILL.md"))) found.add(dir);
|
|
5209
5314
|
}
|
|
5210
|
-
for (const dir of listSubdirs2(
|
|
5211
|
-
if (existsSync20(
|
|
5315
|
+
for (const dir of listSubdirs2(join24(root, "skills"))) {
|
|
5316
|
+
if (existsSync20(join24(dir, "SKILL.md"))) found.add(dir);
|
|
5212
5317
|
}
|
|
5213
5318
|
for (const topDir of topDirs) {
|
|
5214
|
-
for (const dir of listSubdirs2(
|
|
5215
|
-
if (existsSync20(
|
|
5319
|
+
for (const dir of listSubdirs2(join24(topDir, "skills"))) {
|
|
5320
|
+
if (existsSync20(join24(dir, "SKILL.md"))) found.add(dir);
|
|
5216
5321
|
}
|
|
5217
5322
|
}
|
|
5218
5323
|
return Array.from(found).filter((p) => !p.includes("/.git/"));
|
|
@@ -5281,7 +5386,7 @@ function loadUsageSnapshot(snapshotPath) {
|
|
|
5281
5386
|
const resolvedPath = resolvePath6(expandHome(snapshotPath));
|
|
5282
5387
|
let raw;
|
|
5283
5388
|
try {
|
|
5284
|
-
raw =
|
|
5389
|
+
raw = readFileSync15(resolvedPath, "utf-8");
|
|
5285
5390
|
} catch (err) {
|
|
5286
5391
|
return { usage: /* @__PURE__ */ new Map(), warning: `could not read usage snapshot (${err.message}); proceeding without usage` };
|
|
5287
5392
|
}
|
|
@@ -5380,17 +5485,17 @@ function buildRegistryEntries(candidates, warnings) {
|
|
|
5380
5485
|
for (const candidate of candidates) {
|
|
5381
5486
|
let realPath;
|
|
5382
5487
|
try {
|
|
5383
|
-
realPath =
|
|
5488
|
+
realPath = realpathSync4(candidate.dirPath);
|
|
5384
5489
|
} catch (err) {
|
|
5385
5490
|
warnings.push(`could not resolve skill directory ${candidate.dirPath}: ${err.message}`);
|
|
5386
5491
|
continue;
|
|
5387
5492
|
}
|
|
5388
5493
|
if (seenRealPaths.has(realPath)) continue;
|
|
5389
5494
|
seenRealPaths.add(realPath);
|
|
5390
|
-
const skillMdPath =
|
|
5495
|
+
const skillMdPath = join24(realPath, "SKILL.md");
|
|
5391
5496
|
let text;
|
|
5392
5497
|
try {
|
|
5393
|
-
text =
|
|
5498
|
+
text = readFileSync15(skillMdPath, "utf-8");
|
|
5394
5499
|
} catch (err) {
|
|
5395
5500
|
warnings.push(`could not read ${skillMdPath}: ${err.message}`);
|
|
5396
5501
|
continue;
|
|
@@ -5795,8 +5900,8 @@ async function find(query, options = {}, deps = {}) {
|
|
|
5795
5900
|
}
|
|
5796
5901
|
|
|
5797
5902
|
// src/commands/watch.ts
|
|
5798
|
-
import { realpathSync as
|
|
5799
|
-
import { join as
|
|
5903
|
+
import { realpathSync as realpathSync5 } from "fs";
|
|
5904
|
+
import { join as join25, resolve as resolvePath8 } from "path";
|
|
5800
5905
|
var PRO_FEATURE_LABEL5 = "provenance drift watch (watch)";
|
|
5801
5906
|
function isGitUrl(source) {
|
|
5802
5907
|
const s = source.trim();
|
|
@@ -5846,7 +5951,7 @@ async function runWatch(config, options = {}, deps = {}) {
|
|
|
5846
5951
|
let realPath = resolvedRoot;
|
|
5847
5952
|
if (exists) {
|
|
5848
5953
|
try {
|
|
5849
|
-
realPath =
|
|
5954
|
+
realPath = realpathSync5(resolvedRoot);
|
|
5850
5955
|
} catch {
|
|
5851
5956
|
realPath = resolvedRoot;
|
|
5852
5957
|
}
|
|
@@ -5858,7 +5963,7 @@ async function runWatch(config, options = {}, deps = {}) {
|
|
|
5858
5963
|
ledger = readProvenanceLedger(resolvedRoot);
|
|
5859
5964
|
} catch (err) {
|
|
5860
5965
|
const message = err instanceof Error ? err.message : String(err);
|
|
5861
|
-
const ledgerPath =
|
|
5966
|
+
const ledgerPath = join25(resolvedRoot, "SOURCES.md");
|
|
5862
5967
|
roots.push({ root, realPath, exists, ledgerPath, ledgerExists: false });
|
|
5863
5968
|
warnings.push(`${ledgerPath}: could not read ledger: ${message}`);
|
|
5864
5969
|
continue;
|
|
@@ -5950,7 +6055,7 @@ async function watchCommand(options = {}) {
|
|
|
5950
6055
|
|
|
5951
6056
|
// src/commands/routine.ts
|
|
5952
6057
|
import { existsSync as existsSync21, rmSync as rmSync7, statSync as statSync10 } from "fs";
|
|
5953
|
-
import { join as
|
|
6058
|
+
import { join as join26 } from "path";
|
|
5954
6059
|
var PRO_FEATURE_LABEL6 = "the scheduled maintenance routine";
|
|
5955
6060
|
var SEVERITY_ORDER = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
|
|
5956
6061
|
function parseFailOnFloor(failOn) {
|
|
@@ -5971,7 +6076,7 @@ function exceedsFloor(audit2, failOn) {
|
|
|
5971
6076
|
}
|
|
5972
6077
|
function resolveReportPath2(override, generatedAt) {
|
|
5973
6078
|
if (override) return override;
|
|
5974
|
-
return
|
|
6079
|
+
return join26(getHomeDir(), "reports", `routine-${generatedAt.replace(/[:.]/g, "-")}.md`);
|
|
5975
6080
|
}
|
|
5976
6081
|
function runHousekeeping(config, options) {
|
|
5977
6082
|
const actions = [];
|
|
@@ -5980,7 +6085,7 @@ function runHousekeeping(config, options) {
|
|
|
5980
6085
|
const pruneDays = options.pruneDays ?? 30;
|
|
5981
6086
|
const cutoff = Date.now() - pruneDays * 864e5;
|
|
5982
6087
|
for (const id of listQuarantineIds(config.quarantineDir)) {
|
|
5983
|
-
const path =
|
|
6088
|
+
const path = join26(config.quarantineDir, id);
|
|
5984
6089
|
let mtime;
|
|
5985
6090
|
try {
|
|
5986
6091
|
mtime = statSync10(path).mtimeMs;
|
|
@@ -6192,8 +6297,8 @@ async function routineCommand(options = {}) {
|
|
|
6192
6297
|
}
|
|
6193
6298
|
|
|
6194
6299
|
// src/commands/ingest.ts
|
|
6195
|
-
import { realpathSync as
|
|
6196
|
-
import { dirname as dirname11, isAbsolute, relative, resolve as resolvePath9 } from "path";
|
|
6300
|
+
import { realpathSync as realpathSync6 } from "fs";
|
|
6301
|
+
import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative2, resolve as resolvePath9 } from "path";
|
|
6197
6302
|
var PRO_FEATURE_LABEL7 = "the pending-ingestion queue";
|
|
6198
6303
|
var VALID_STATUSES = ["ingested", "dismissed"];
|
|
6199
6304
|
function requireProFeature() {
|
|
@@ -6234,14 +6339,14 @@ function printPendingList(pending, json) {
|
|
|
6234
6339
|
}
|
|
6235
6340
|
function realpathOrNull(p) {
|
|
6236
6341
|
try {
|
|
6237
|
-
return
|
|
6342
|
+
return realpathSync6(p);
|
|
6238
6343
|
} catch {
|
|
6239
6344
|
return null;
|
|
6240
6345
|
}
|
|
6241
6346
|
}
|
|
6242
6347
|
function isUnderRoot(candidate, root) {
|
|
6243
|
-
const rel =
|
|
6244
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
6348
|
+
const rel = relative2(root, candidate);
|
|
6349
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
6245
6350
|
}
|
|
6246
6351
|
function validatePendingEntries(entries, config) {
|
|
6247
6352
|
const quarantineRoot = realpathOrNull(resolvePath9(config.quarantineDir));
|
|
@@ -6318,14 +6423,14 @@ async function queueClose(id, options = {}) {
|
|
|
6318
6423
|
}
|
|
6319
6424
|
|
|
6320
6425
|
// src/commands/refine.ts
|
|
6321
|
-
import { createHash as
|
|
6322
|
-
import { chmodSync, existsSync as existsSync24, lstatSync as
|
|
6323
|
-
import { dirname as dirname13, isAbsolute as
|
|
6426
|
+
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
6427
|
+
import { chmodSync, existsSync as existsSync24, lstatSync as lstatSync4, mkdirSync as mkdirSync9, readFileSync as readFileSync17, realpathSync as realpathSync9, renameSync as renameSync6, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
6428
|
+
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join29, relative as relative4, resolve as resolvePath12, sep as sep4 } from "path";
|
|
6324
6429
|
import { createInterface as createInterface6 } from "readline/promises";
|
|
6325
6430
|
|
|
6326
6431
|
// src/refine/context.ts
|
|
6327
|
-
import { existsSync as existsSync23, realpathSync as
|
|
6328
|
-
import { isAbsolute as
|
|
6432
|
+
import { existsSync as existsSync23, realpathSync as realpathSync8, statSync as statSync12 } from "fs";
|
|
6433
|
+
import { isAbsolute as isAbsolute3, join as join28, relative as relative3, resolve as resolvePath11 } from "path";
|
|
6329
6434
|
|
|
6330
6435
|
// src/refine/models.ts
|
|
6331
6436
|
import { randomBytes } from "crypto";
|
|
@@ -6394,27 +6499,27 @@ function contentSimilarity(a, b) {
|
|
|
6394
6499
|
}
|
|
6395
6500
|
|
|
6396
6501
|
// src/refine/store.ts
|
|
6397
|
-
import { createHash as
|
|
6398
|
-
import { existsSync as existsSync22, readFileSync as
|
|
6399
|
-
import { join as
|
|
6502
|
+
import { createHash as createHash3 } from "crypto";
|
|
6503
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16, statSync as statSync11, realpathSync as realpathSync7 } from "fs";
|
|
6504
|
+
import { join as join27, resolve as resolvePath10 } from "path";
|
|
6400
6505
|
var SIMILARITY_JOIN_THRESHOLD = 0.7;
|
|
6401
6506
|
var READY_THRESHOLD = 2;
|
|
6402
6507
|
function getRefinementsDir() {
|
|
6403
|
-
return
|
|
6508
|
+
return join27(getHomeDir(), "refinements");
|
|
6404
6509
|
}
|
|
6405
6510
|
function getRefinementsPath() {
|
|
6406
|
-
return
|
|
6511
|
+
return join27(getRefinementsDir(), "refinements.json");
|
|
6407
6512
|
}
|
|
6408
6513
|
function getPatternsPath() {
|
|
6409
|
-
return
|
|
6514
|
+
return join27(getRefinementsDir(), "patterns.json");
|
|
6410
6515
|
}
|
|
6411
6516
|
function getBackupsDir() {
|
|
6412
|
-
return
|
|
6517
|
+
return join27(getRefinementsDir(), "backups");
|
|
6413
6518
|
}
|
|
6414
6519
|
function readJsonFile(path, label) {
|
|
6415
6520
|
let raw;
|
|
6416
6521
|
try {
|
|
6417
|
-
raw =
|
|
6522
|
+
raw = readFileSync16(path, "utf-8");
|
|
6418
6523
|
} catch (err) {
|
|
6419
6524
|
throw new UserError(`unable to read ${label} at ${path}: ${err.message}`);
|
|
6420
6525
|
}
|
|
@@ -6517,7 +6622,7 @@ function writePatternsStore(store) {
|
|
|
6517
6622
|
function resolveProjectRealpath(projectRoot) {
|
|
6518
6623
|
const resolved = resolvePath10(projectRoot);
|
|
6519
6624
|
try {
|
|
6520
|
-
return
|
|
6625
|
+
return realpathSync7(resolved);
|
|
6521
6626
|
} catch {
|
|
6522
6627
|
return resolved;
|
|
6523
6628
|
}
|
|
@@ -6652,13 +6757,13 @@ function createBackup(filePaths, deps = {}) {
|
|
|
6652
6757
|
if (!existsSync22(path)) {
|
|
6653
6758
|
return { path, existed: false, sha256: null, mode: null, contentBase64: null };
|
|
6654
6759
|
}
|
|
6655
|
-
const bytes =
|
|
6760
|
+
const bytes = readFileSync16(path);
|
|
6656
6761
|
const mode = statSync11(path).mode & 511;
|
|
6657
|
-
const sha256 =
|
|
6762
|
+
const sha256 = createHash3("sha256").update(bytes).digest("hex");
|
|
6658
6763
|
return { path, existed: true, sha256, mode, contentBase64: bytes.toString("base64") };
|
|
6659
6764
|
});
|
|
6660
6765
|
const manifest = { id, createdAt: now.toISOString(), files };
|
|
6661
|
-
writeJsonAtomic(
|
|
6766
|
+
writeJsonAtomic(join27(getBackupsDir(), id, "manifest.json"), manifest);
|
|
6662
6767
|
return manifest;
|
|
6663
6768
|
}
|
|
6664
6769
|
function validateBackupManifestShape(value, path) {
|
|
@@ -6707,7 +6812,7 @@ function readBackupManifest(id) {
|
|
|
6707
6812
|
if (!isValidBackupId(id)) {
|
|
6708
6813
|
throw new UserError(`invalid backup id "${id}" \u2014 expected the format produced by \`skill-forge refine promote\` (e.g. 20260720T142305Z-a1b2)`);
|
|
6709
6814
|
}
|
|
6710
|
-
const manifestPath =
|
|
6815
|
+
const manifestPath = join27(getBackupsDir(), id, "manifest.json");
|
|
6711
6816
|
if (!existsSync22(manifestPath)) {
|
|
6712
6817
|
throw new UserError(`no backup with id: ${id}`);
|
|
6713
6818
|
}
|
|
@@ -6730,20 +6835,20 @@ function assertPlainSkillName(name) {
|
|
|
6730
6835
|
}
|
|
6731
6836
|
function realpathOrNull2(p) {
|
|
6732
6837
|
try {
|
|
6733
|
-
return
|
|
6838
|
+
return realpathSync8(p);
|
|
6734
6839
|
} catch {
|
|
6735
6840
|
return null;
|
|
6736
6841
|
}
|
|
6737
6842
|
}
|
|
6738
6843
|
function isUnderRoot2(candidate, root) {
|
|
6739
|
-
const rel =
|
|
6740
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
6844
|
+
const rel = relative3(root, candidate);
|
|
6845
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
6741
6846
|
}
|
|
6742
6847
|
function resolveScopes(config, cwd, skill, userRootFlag) {
|
|
6743
6848
|
assertPlainSkillName(skill);
|
|
6744
6849
|
const resolvedCwd = resolvePath11(cwd);
|
|
6745
|
-
const localDir =
|
|
6746
|
-
const sharedDir =
|
|
6850
|
+
const localDir = join28(resolvedCwd, ".claude", "skills", skill);
|
|
6851
|
+
const sharedDir = join28(resolvedCwd, "skills", skill);
|
|
6747
6852
|
const local = { scope: "local", dir: localDir, exists: existsSync23(localDir) };
|
|
6748
6853
|
const shared = { scope: "shared", dir: sharedDir, exists: existsSync23(sharedDir) };
|
|
6749
6854
|
const user = resolveUserScope(config, resolvedCwd, skill, userRootFlag);
|
|
@@ -6752,10 +6857,10 @@ function resolveScopes(config, cwd, skill, userRootFlag) {
|
|
|
6752
6857
|
function resolveUserScope(config, resolvedCwd, skill, userRootFlag) {
|
|
6753
6858
|
if (userRootFlag) {
|
|
6754
6859
|
const root = resolvePath11(userRootFlag);
|
|
6755
|
-
return { resolved: true, dir:
|
|
6860
|
+
return { resolved: true, dir: join28(root, skill), root: userRootFlag, reason: "flag" };
|
|
6756
6861
|
}
|
|
6757
6862
|
const cwdReal = realpathOrNull2(resolvedCwd) ?? resolvedCwd;
|
|
6758
|
-
const candidates = config.skillsRoots.map((root) => ({ root, dir:
|
|
6863
|
+
const candidates = config.skillsRoots.map((root) => ({ root, dir: join28(resolveConfiguredRoot2(root), skill) }));
|
|
6759
6864
|
for (const candidate of candidates) {
|
|
6760
6865
|
const skillReal = realpathOrNull2(candidate.dir);
|
|
6761
6866
|
if (skillReal) {
|
|
@@ -6790,12 +6895,12 @@ function inventoryScopeDir(scope, dir) {
|
|
|
6790
6895
|
scope,
|
|
6791
6896
|
dir,
|
|
6792
6897
|
dirExists: isDirectory(dir),
|
|
6793
|
-
skillMd: isFile2(
|
|
6794
|
-
patchMd: isFile2(
|
|
6795
|
-
extendMd: isFile2(
|
|
6796
|
-
configJson: isFile2(
|
|
6797
|
-
hooksDir: isDirectory(
|
|
6798
|
-
scriptsDir: isDirectory(
|
|
6898
|
+
skillMd: isFile2(join28(dir, "SKILL.md")),
|
|
6899
|
+
patchMd: isFile2(join28(dir, "SKILL.patch.md")),
|
|
6900
|
+
extendMd: isFile2(join28(dir, "SKILL.extend.md")),
|
|
6901
|
+
configJson: isFile2(join28(dir, "skill-config.json")),
|
|
6902
|
+
hooksDir: isDirectory(join28(dir, "hooks")),
|
|
6903
|
+
scriptsDir: isDirectory(join28(dir, "scripts"))
|
|
6799
6904
|
};
|
|
6800
6905
|
}
|
|
6801
6906
|
function inventoryAllScopes(resolution) {
|
|
@@ -7067,15 +7172,15 @@ function resolveConfiguredRoot3(root) {
|
|
|
7067
7172
|
return resolvePath12(expandHome(root));
|
|
7068
7173
|
}
|
|
7069
7174
|
function isUnderRoot3(candidate, root) {
|
|
7070
|
-
const rel =
|
|
7071
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
7175
|
+
const rel = relative4(root, candidate);
|
|
7176
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
|
|
7072
7177
|
}
|
|
7073
7178
|
function sanitizeDiffForConsole(diff) {
|
|
7074
7179
|
return diff.split("\n").map((line) => sanitizeForMarkdown(line)).join("\n");
|
|
7075
7180
|
}
|
|
7076
7181
|
function resolveContentOption(options) {
|
|
7077
7182
|
if (options.content !== void 0) return options.content;
|
|
7078
|
-
if (options.contentFile) return
|
|
7183
|
+
if (options.contentFile) return readFileSync17(resolvePath12(options.contentFile), "utf-8");
|
|
7079
7184
|
return void 0;
|
|
7080
7185
|
}
|
|
7081
7186
|
function validateCaptureOptions(options) {
|
|
@@ -7201,7 +7306,7 @@ ${rendered}` : rendered;
|
|
|
7201
7306
|
function ensureScopeDirSafe(scopeDir, resolvedCwd) {
|
|
7202
7307
|
let lst;
|
|
7203
7308
|
try {
|
|
7204
|
-
lst =
|
|
7309
|
+
lst = lstatSync4(scopeDir);
|
|
7205
7310
|
} catch {
|
|
7206
7311
|
lst = void 0;
|
|
7207
7312
|
}
|
|
@@ -7213,7 +7318,7 @@ function ensureScopeDirSafe(scopeDir, resolvedCwd) {
|
|
|
7213
7318
|
const lexical = resolvePath12(scopeDir);
|
|
7214
7319
|
let realResolved;
|
|
7215
7320
|
try {
|
|
7216
|
-
realResolved =
|
|
7321
|
+
realResolved = realpathSync9(scopeDir);
|
|
7217
7322
|
} catch {
|
|
7218
7323
|
realResolved = real;
|
|
7219
7324
|
}
|
|
@@ -7227,16 +7332,16 @@ function ensureScopeDirSafe(scopeDir, resolvedCwd) {
|
|
|
7227
7332
|
}
|
|
7228
7333
|
function writeOverrideFile(scopeDir, relPath, content, resolvedCwd) {
|
|
7229
7334
|
const realScopeDir = ensureScopeDirSafe(scopeDir, resolvedCwd);
|
|
7230
|
-
const destPath =
|
|
7335
|
+
const destPath = join29(scopeDir, relPath);
|
|
7231
7336
|
const destDir = dirname13(destPath);
|
|
7232
7337
|
mkdirSync9(destDir, { recursive: true });
|
|
7233
|
-
const realDestDir =
|
|
7338
|
+
const realDestDir = realpathSync9(destDir);
|
|
7234
7339
|
if (!isUnderRoot3(realDestDir, realScopeDir)) {
|
|
7235
7340
|
throw new UserError(`refusing to write ${destPath}: resolves outside scope directory ${scopeDir}`);
|
|
7236
7341
|
}
|
|
7237
7342
|
let destLst;
|
|
7238
7343
|
try {
|
|
7239
|
-
destLst =
|
|
7344
|
+
destLst = lstatSync4(destPath);
|
|
7240
7345
|
} catch {
|
|
7241
7346
|
destLst = void 0;
|
|
7242
7347
|
}
|
|
@@ -7323,12 +7428,12 @@ Write ${preview.destPath}? [y/N] `);
|
|
|
7323
7428
|
}
|
|
7324
7429
|
function renderCaptureArtifactPreview(opts, content, refinementId, generatedAt, scopeDir) {
|
|
7325
7430
|
const probeRelPath = opts.overrideType === "patch" ? "SKILL.patch.md" : opts.overrideType === "extend" || opts.overrideType === "new" ? "SKILL.extend.md" : void 0;
|
|
7326
|
-
const probePath = probeRelPath ?
|
|
7327
|
-
const existingContent = probePath && existsSync24(probePath) ?
|
|
7431
|
+
const probePath = probeRelPath ? join29(scopeDir, probeRelPath) : void 0;
|
|
7432
|
+
const existingContent = probePath && existsSync24(probePath) ? readFileSync17(probePath, "utf-8") : "";
|
|
7328
7433
|
const rendered = renderCaptureArtifact(opts, content, refinementId, generatedAt, existingContent);
|
|
7329
7434
|
const relPath = rendered.relPath;
|
|
7330
|
-
const destPath =
|
|
7331
|
-
const before = existsSync24(destPath) ?
|
|
7435
|
+
const destPath = join29(scopeDir, relPath);
|
|
7436
|
+
const before = existsSync24(destPath) ? readFileSync17(destPath, "utf-8") : "";
|
|
7332
7437
|
const diff = previewDiff(before, rendered.content, destPath);
|
|
7333
7438
|
return { relPath, content: rendered.content, destPath, diff };
|
|
7334
7439
|
}
|
|
@@ -7425,16 +7530,16 @@ async function refineWhichCommand(skill, options = {}) {
|
|
|
7425
7530
|
}
|
|
7426
7531
|
function writePostWriteSidecar(backupId, files) {
|
|
7427
7532
|
const sidecar = { schemaVersion: 1, files };
|
|
7428
|
-
const dir =
|
|
7533
|
+
const dir = join29(getBackupsDir(), backupId);
|
|
7429
7534
|
mkdirSync9(dir, { recursive: true });
|
|
7430
|
-
writeFileSync9(
|
|
7535
|
+
writeFileSync9(join29(dir, "post.json"), JSON.stringify(sidecar, null, 2) + "\n", { mode: 384 });
|
|
7431
7536
|
}
|
|
7432
7537
|
function readPostWriteSidecar(backupId) {
|
|
7433
|
-
const path =
|
|
7538
|
+
const path = join29(getBackupsDir(), backupId, "post.json");
|
|
7434
7539
|
if (!existsSync24(path)) return { hashes: /* @__PURE__ */ new Map(), status: "missing" };
|
|
7435
7540
|
let raw;
|
|
7436
7541
|
try {
|
|
7437
|
-
raw =
|
|
7542
|
+
raw = readFileSync17(path, "utf-8");
|
|
7438
7543
|
} catch {
|
|
7439
7544
|
return { hashes: /* @__PURE__ */ new Map(), status: "unreadable" };
|
|
7440
7545
|
}
|
|
@@ -7479,20 +7584,20 @@ async function refinePromoteCommand(patternId, options = {}) {
|
|
|
7479
7584
|
const resolvedUserRoot = resolveConfiguredRoot3(scopes.user.root);
|
|
7480
7585
|
let realUserRoot;
|
|
7481
7586
|
try {
|
|
7482
|
-
realUserRoot =
|
|
7587
|
+
realUserRoot = realpathSync9(resolvedUserRoot);
|
|
7483
7588
|
} catch {
|
|
7484
7589
|
realUserRoot = resolvedUserRoot;
|
|
7485
7590
|
}
|
|
7486
7591
|
let realSkillDir;
|
|
7487
7592
|
try {
|
|
7488
|
-
realSkillDir =
|
|
7593
|
+
realSkillDir = realpathSync9(skillDir);
|
|
7489
7594
|
} catch {
|
|
7490
7595
|
realSkillDir = resolvePath12(skillDir);
|
|
7491
7596
|
}
|
|
7492
7597
|
if (!isUnderRoot3(realSkillDir, realUserRoot)) {
|
|
7493
7598
|
throw new UserError(`refusing to promote: resolved skill directory ${skillDir} is outside the configured user scope root ${scopes.user.root}`);
|
|
7494
7599
|
}
|
|
7495
|
-
const skillMdPath =
|
|
7600
|
+
const skillMdPath = join29(skillDir, "SKILL.md");
|
|
7496
7601
|
if (!existsSync24(skillMdPath)) {
|
|
7497
7602
|
throw new UserError(`user-scope SKILL.md not found at ${skillMdPath} \u2014 refusing to promote against a missing base skill.`);
|
|
7498
7603
|
}
|
|
@@ -7510,7 +7615,7 @@ async function refinePromoteCommand(patternId, options = {}) {
|
|
|
7510
7615
|
`pattern ${patternId}'s representative refinement is a '${representative.overrideType}' override \u2014 promote currently only materializes patch/extend/new-type patterns against the user-scope SKILL.md; other override types must be merged manually.`
|
|
7511
7616
|
);
|
|
7512
7617
|
}
|
|
7513
|
-
const before =
|
|
7618
|
+
const before = readFileSync17(skillMdPath, "utf-8");
|
|
7514
7619
|
const after = applyPatchBlocks(before, [block]);
|
|
7515
7620
|
const diff = previewDiff(before, after, skillMdPath);
|
|
7516
7621
|
if (options.dryRun) {
|
|
@@ -7542,7 +7647,7 @@ Promote pattern ${patternId} into ${skillMdPath}? [y/N] `);
|
|
|
7542
7647
|
throw err;
|
|
7543
7648
|
}
|
|
7544
7649
|
try {
|
|
7545
|
-
const postSha256 =
|
|
7650
|
+
const postSha256 = createHash4("sha256").update(readFileSync17(skillMdPath)).digest("hex");
|
|
7546
7651
|
writePostWriteSidecar(backup.id, [{ path: skillMdPath, sha256: postSha256 }]);
|
|
7547
7652
|
markGeneralized(patternId, backup.id);
|
|
7548
7653
|
const skillsRoot = resolveConfiguredRoot3(scopes.user.root);
|
|
@@ -7569,10 +7674,10 @@ Promote pattern ${patternId} into ${skillMdPath}? [y/N] `);
|
|
|
7569
7674
|
function assertBackupDirContained(backupId) {
|
|
7570
7675
|
const backupsDir = getBackupsDir();
|
|
7571
7676
|
if (!existsSync24(backupsDir)) return;
|
|
7572
|
-
const realBackupsDir =
|
|
7573
|
-
const backupDir =
|
|
7677
|
+
const realBackupsDir = realpathSync9(backupsDir);
|
|
7678
|
+
const backupDir = join29(backupsDir, backupId);
|
|
7574
7679
|
if (!existsSync24(backupDir)) return;
|
|
7575
|
-
const realBackupDir =
|
|
7680
|
+
const realBackupDir = realpathSync9(backupDir);
|
|
7576
7681
|
if (!isUnderRoot3(realBackupDir, realBackupsDir)) {
|
|
7577
7682
|
throw new UserError(`refusing rollback: backup id "${backupId}" resolves outside the backups directory`);
|
|
7578
7683
|
}
|
|
@@ -7581,9 +7686,9 @@ function resolveTargetRealPath(path) {
|
|
|
7581
7686
|
let lexicalAncestor = resolvePath12(path);
|
|
7582
7687
|
while (true) {
|
|
7583
7688
|
try {
|
|
7584
|
-
const real =
|
|
7585
|
-
const remainder =
|
|
7586
|
-
return remainder === "" ? real :
|
|
7689
|
+
const real = realpathSync9(lexicalAncestor);
|
|
7690
|
+
const remainder = relative4(lexicalAncestor, resolvePath12(path));
|
|
7691
|
+
return remainder === "" ? real : join29(real, remainder);
|
|
7587
7692
|
} catch {
|
|
7588
7693
|
const parent = dirname13(lexicalAncestor);
|
|
7589
7694
|
if (parent === lexicalAncestor) return resolvePath12(path);
|
|
@@ -7598,7 +7703,7 @@ function assertManifestPathsWithinRoots(manifest, config) {
|
|
|
7598
7703
|
const roots = config.skillsRoots.map((root) => {
|
|
7599
7704
|
const resolved = resolveConfiguredRoot3(root);
|
|
7600
7705
|
try {
|
|
7601
|
-
return
|
|
7706
|
+
return realpathSync9(resolved);
|
|
7602
7707
|
} catch {
|
|
7603
7708
|
return resolved;
|
|
7604
7709
|
}
|
|
@@ -7615,7 +7720,7 @@ function assertNoSymlinkLeaves(manifest) {
|
|
|
7615
7720
|
for (const file of manifest.files) {
|
|
7616
7721
|
let lst;
|
|
7617
7722
|
try {
|
|
7618
|
-
lst =
|
|
7723
|
+
lst = lstatSync4(file.path);
|
|
7619
7724
|
} catch {
|
|
7620
7725
|
lst = void 0;
|
|
7621
7726
|
}
|
|
@@ -7628,11 +7733,11 @@ function findLexicalRoot(filePath, config) {
|
|
|
7628
7733
|
const lexicalTarget = resolvePath12(filePath);
|
|
7629
7734
|
for (const root of config.skillsRoots) {
|
|
7630
7735
|
const rootLexical = resolveConfiguredRoot3(root);
|
|
7631
|
-
const rel =
|
|
7632
|
-
if (rel !== "" && (rel === ".." || rel.startsWith(`..${
|
|
7736
|
+
const rel = relative4(rootLexical, lexicalTarget);
|
|
7737
|
+
if (rel !== "" && (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute4(rel))) continue;
|
|
7633
7738
|
let rootReal;
|
|
7634
7739
|
try {
|
|
7635
|
-
rootReal =
|
|
7740
|
+
rootReal = realpathSync9(rootLexical);
|
|
7636
7741
|
} catch {
|
|
7637
7742
|
rootReal = rootLexical;
|
|
7638
7743
|
}
|
|
@@ -7642,8 +7747,8 @@ function findLexicalRoot(filePath, config) {
|
|
|
7642
7747
|
}
|
|
7643
7748
|
function walkRealComponents(filePath, config, createMissing) {
|
|
7644
7749
|
const { rootLexical, rootReal } = findLexicalRoot(filePath, config);
|
|
7645
|
-
const relPath =
|
|
7646
|
-
const components = relPath.split(
|
|
7750
|
+
const relPath = relative4(rootLexical, resolvePath12(filePath));
|
|
7751
|
+
const components = relPath.split(sep4).filter((c) => c.length > 0);
|
|
7647
7752
|
if (components.length === 0) {
|
|
7648
7753
|
throw new UserError(`refusing to operate on ${filePath}: resolves to the skills root itself`);
|
|
7649
7754
|
}
|
|
@@ -7652,10 +7757,10 @@ function walkRealComponents(filePath, config, createMissing) {
|
|
|
7652
7757
|
let current = rootReal;
|
|
7653
7758
|
for (const component of dirComponents) {
|
|
7654
7759
|
if (current === void 0) break;
|
|
7655
|
-
const next =
|
|
7760
|
+
const next = join29(current, component);
|
|
7656
7761
|
let lst;
|
|
7657
7762
|
try {
|
|
7658
|
-
lst =
|
|
7763
|
+
lst = lstatSync4(next);
|
|
7659
7764
|
} catch {
|
|
7660
7765
|
lst = void 0;
|
|
7661
7766
|
}
|
|
@@ -7683,13 +7788,13 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
|
|
|
7683
7788
|
if (parentReal === void 0) {
|
|
7684
7789
|
throw new UserError(`refusing to restore ${filePath}: could not establish a real parent directory`);
|
|
7685
7790
|
}
|
|
7686
|
-
const finalPath =
|
|
7791
|
+
const finalPath = join29(parentReal, leafComponent);
|
|
7687
7792
|
if (!isUnderRoot3(finalPath, rootReal)) {
|
|
7688
7793
|
throw new UserError(`refusing to restore ${filePath}: resolves outside root ${rootReal}`);
|
|
7689
7794
|
}
|
|
7690
7795
|
let leafLst;
|
|
7691
7796
|
try {
|
|
7692
|
-
leafLst =
|
|
7797
|
+
leafLst = lstatSync4(finalPath);
|
|
7693
7798
|
} catch {
|
|
7694
7799
|
leafLst = void 0;
|
|
7695
7800
|
}
|
|
@@ -7709,13 +7814,13 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
|
|
|
7709
7814
|
function safeDeleteTombstone(filePath, config) {
|
|
7710
7815
|
const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, false);
|
|
7711
7816
|
if (parentReal === void 0) return;
|
|
7712
|
-
const finalPath =
|
|
7817
|
+
const finalPath = join29(parentReal, leafComponent);
|
|
7713
7818
|
if (!isUnderRoot3(finalPath, rootReal)) {
|
|
7714
7819
|
throw new UserError(`refusing to delete ${filePath}: resolves outside root ${rootReal}`);
|
|
7715
7820
|
}
|
|
7716
7821
|
let leafLst;
|
|
7717
7822
|
try {
|
|
7718
|
-
leafLst =
|
|
7823
|
+
leafLst = lstatSync4(finalPath);
|
|
7719
7824
|
} catch {
|
|
7720
7825
|
leafLst = void 0;
|
|
7721
7826
|
}
|
|
@@ -7751,7 +7856,7 @@ async function refineRollbackCommand(backupId, options = {}) {
|
|
|
7751
7856
|
for (const file of manifest.files) {
|
|
7752
7857
|
const expected = sidecar.hashes.get(file.path);
|
|
7753
7858
|
if (expected === void 0) continue;
|
|
7754
|
-
const currentSha256 = existsSync24(file.path) ?
|
|
7859
|
+
const currentSha256 = existsSync24(file.path) ? createHash4("sha256").update(readFileSync17(file.path)).digest("hex") : null;
|
|
7755
7860
|
if (currentSha256 !== expected) mismatches.push(file.path);
|
|
7756
7861
|
}
|
|
7757
7862
|
if (mismatches.length > 0 && !options.force) {
|