@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/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 join19 } from "path";
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
- value: entry.value,
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 join5 } from "path";
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
- const dest = join5(targetSkillsRoot, basename3(skillDir));
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 = join5(targetSkillsRoot, basename3(skillDir));
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 readFileSync3, writeFileSync as writeFileSync3 } from "fs";
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 = readFileSync3(targetConfigPath, "utf-8");
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 readFileSync4 } from "fs";
763
- import { dirname as dirname3, join as join6 } from "path";
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(readFileSync4(pkgPath, "utf-8"));
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(readFileSync4(configPath, "utf-8"));
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(join6(dirname3(profile.config), "package.json"));
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 readFileSync5, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
808
- import { join as join7 } from "path";
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 = join7(skillsRoot, "SOURCES.md");
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 = join7(skillsRoot, "SOURCES.md");
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 = readFileSync5(ledgerPath, "utf-8");
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 readdirSync6 } from "fs";
1000
+ import { readdirSync as readdirSync7 } from "fs";
898
1001
 
899
1002
  // src/auditState.ts
900
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
901
- import { basename as basename7, dirname as dirname5, join as join11 } from "path";
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 readFileSync7,
909
- readdirSync as readdirSync4,
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 join10, resolve as resolvePath2 } from "path";
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 join8 } from "path";
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 = join8(dir, cmd + ext);
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 readFileSync6, readdirSync as readdirSync3 } from "fs";
1247
- import { basename as basename5, dirname as dirname4, join as join9, resolve as resolve2 } from "path";
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, sep4, maxSplit) {
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(sep4);
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 + sep4.length);
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 = readFileSync6(skillMdPath, "utf-8");
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 = readdirSync3(dir, { withFileTypes: true });
1470
+ entries = readdirSync4(dir, { withFileTypes: true });
1368
1471
  } catch {
1369
1472
  return [];
1370
1473
  }
1371
- return entries.filter((e) => e.isDirectory()).map((e) => join9(dir, e.name));
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 = join9(dir, "SKILL.md");
1479
+ const candidate = join10(dir, "SKILL.md");
1377
1480
  if (existsSync7(candidate)) found.add(resolve2(candidate));
1378
1481
  }
1379
- for (const dir of listSubdirs(join9(root, "skills"))) {
1380
- const candidate = join9(dir, "SKILL.md");
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(join9(topDir, "skills"))) {
1385
- const candidate = join9(dir, "SKILL.md");
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 = join9(cpath, "SKILL.md");
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 = readdirSync4(dir, { withFileTypes: true });
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 = join10(dir, entry.name);
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 = readdirSync4(dir, { withFileTypes: true });
1669
+ entries = readdirSync5(dir, { withFileTypes: true });
1567
1670
  } catch {
1568
1671
  return [];
1569
1672
  }
1570
- return entries.filter((e) => e.isDirectory()).map((e) => join10(dir, e.name));
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(join10(dir, "SKILL.md"))) found.add(dir);
1679
+ if (existsSync8(join11(dir, "SKILL.md"))) found.add(dir);
1577
1680
  }
1578
- for (const dir of listSubdirsFollowingSymlinks(join10(root, "skills"))) {
1579
- if (existsSync8(join10(dir, "SKILL.md"))) found.add(dir);
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(join10(topDir, "skills"))) {
1583
- if (existsSync8(join10(dir, "SKILL.md"))) found.add(dir);
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 = realpathSync(resolvedRoot);
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 = realpathSync(candidate.configuredPath);
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 = join10(group.canonicalPath, "SKILL.md");
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 = readFileSync7(skillMdPath, "utf-8");
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
- readFileSync7(targetPath, "utf-8");
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 = readFileSync7(targetPath, "utf-8");
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 = join10(dir, cmd + ext);
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 = join10(resolvedRoot, "business-foundation");
2314
+ const destDir = join11(resolvedRoot, "business-foundation");
2212
2315
  let destLstat;
2213
2316
  try {
2214
- destLstat = lstatSync(destDir);
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(join10(resolvedRoot, ".business-foundation-"));
2328
+ const tempDir = mkdtempSync(join11(resolvedRoot, ".business-foundation-"));
2226
2329
  try {
2227
- writeFileSync5(join10(tempDir, "SKILL.md"), content, { flag: "wx", mode: 384 });
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 join11(getHomeDir(), "audit-state.json");
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(readFileSync8(path, "utf-8"));
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 join14 } from "path";
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 readFileSync10,
2389
- readdirSync as readdirSync5,
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 join13, resolve as resolve3 } from "path";
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 readFileSync9 } from "fs";
2399
- import { extname as extname4, join as join12 } from "path";
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(readFileSync9(file, "utf-8"));
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 = join12(packageDir, "package.json");
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 = join12(packageDir, manifestName);
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(join13(p, ".claude-plugin", "plugin.json"))) return "plugin";
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 = join13(root, "package.json");
2732
+ const pkgPath = join14(root, "package.json");
2630
2733
  if (!existsSync11(pkgPath)) return void 0;
2631
2734
  try {
2632
- return JSON.parse(readFileSync10(pkgPath, "utf-8"));
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(join13(root, ".mcp.json")) || existsSync11(join13(root, "mcp.json"))) return "mcp";
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(join13(tmpdir(), "forge_"));
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 = join13(currentPath, "SKILL.md");
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 readdirSync5(dir, { withFileTypes: true })) {
2799
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2697
2800
  if (entry.isDirectory()) {
2698
- const candidate = join13(dir, entry.name, "SKILL.md");
2801
+ const candidate = join14(dir, entry.name, "SKILL.md");
2699
2802
  if (existsSync11(candidate)) matches.add(candidate);
2700
2803
  }
2701
2804
  }
2702
- const skillsDir = join13(dir, "skills");
2805
+ const skillsDir = join14(dir, "skills");
2703
2806
  if (isDir(skillsDir)) {
2704
- for (const entry of readdirSync5(skillsDir, { withFileTypes: true })) {
2807
+ for (const entry of readdirSync6(skillsDir, { withFileTypes: true })) {
2705
2808
  if (entry.isDirectory()) {
2706
- const candidate = join13(skillsDir, entry.name, "SKILL.md");
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 = join13(root, name);
2893
+ const p = join14(root, name);
2791
2894
  if (existsSync11(p) && statSync7(p).isFile()) {
2792
- const head = readFileSync10(p, "utf-8").slice(0, 2e3).toLowerCase();
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 readdirSync5(dir, { withFileTypes: true })) {
2804
- const full = join13(dir, entry.name);
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 = join13(root, d);
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 = join13(root, "scripts");
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 = readFileSync10(f, "utf-8");
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 = readFileSync10(skillMd, "utf-8");
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 = join13(p, ".claude-plugin", "plugin.json");
2979
+ const manifestPath = join14(p, ".claude-plugin", "plugin.json");
2877
2980
  let manifest;
2878
2981
  try {
2879
- manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
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 = join13(p, "skills");
2986
+ const skillsDir = join14(p, "skills");
2884
2987
  let skills = [];
2885
2988
  if (isDir(skillsDir)) {
2886
- skills = readdirSync5(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && existsSync11(join13(skillsDir, d.name, "SKILL.md"))).map((d) => d.name).sort();
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 = join13(p, "commands");
2889
- const commands = isDir(commandsDir) ? readdirSync5(commandsDir).filter((f) => f.endsWith(".md")).length : 0;
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(join13(p, ".mcp.json")) ? join13(p, ".mcp.json") : existsSync11(join13(p, "mcp.json")) ? join13(p, "mcp.json") : void 0;
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(readFileSync10(explicitConfig, "utf-8"));
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 = join13(p, "package.json");
3029
+ const pkgPath = join14(p, "package.json");
2927
3030
  let pkg2;
2928
3031
  try {
2929
- pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
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 readFileSync11 } from "fs";
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(readFileSync11(path, "utf-8"));
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 = readdirSync6(entry.quarantinePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
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 ? join14(entry.quarantinePath, children[0]) : entry.quarantinePath;
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 readFileSync12, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
3190
- import { join as join15 } from "path";
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 join15(getHomeDir(), "queue.json");
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 = readFileSync12(path, "utf-8");
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 = join15(home, `.queue.json.tmp-${randomUUID3()}`);
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 join16 } from "path";
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 = join16(here, assetName);
3366
+ const builtCandidate = join17(here, assetName);
3264
3367
  if (existsSync14(builtCandidate)) return builtCandidate;
3265
- return join16(here, "..", "assets", assetName);
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 join17, resolve as resolve5 } from "path";
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 join17(home, root.slice(2));
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 join18, resolve as resolvePath3 } from "path";
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 join18(getHomeDir(), "reports", `audit-${iso}.md`);
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] ?? join19(process.cwd(), ".mcp.json");
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 join20 } from "path";
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(join20(quarantineDir, id));
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(join20(dir, "SKILL.md")).name;
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 realpathSync2 } from "fs";
4311
- import { basename as basename9, join as join21, resolve as resolvePath4, sep } from "path";
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 = join21(quarantineDir, id);
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 = realpathSync2(resolvePath4(quarantineDir));
4322
- const realCandidate = realpathSync2(candidate);
4323
- if (!realCandidate.startsWith(realRoot + sep)) {
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(join21(dir, "SKILL.md")).name;
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(join21(candidate, f)));
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] ?? join21(process.cwd(), ".mcp.json");
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 lstatSync2, mkdtempSync as mkdtempSync3, readFileSync as readFileSync13, rmSync as rmSync6 } from "fs";
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 join22, resolve as resolvePath5, sep as sep2 } from "path";
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 = join22(resolvedTargetDir, "SKILL.md");
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 = join22(project, ".skillopt-sleep", "staging");
5035
+ const stagingRoot = join23(project, ".skillopt-sleep", "staging");
4931
5036
  const stagingDir = resolvePath5(parsed.staging_dir);
4932
- if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep2)) {
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 = join22(stagingDir, "proposed_SKILL.md");
4938
- const proposedClaudeMd = join22(stagingDir, "proposed_CLAUDE.md");
4939
- const reportMdPath = join22(stagingDir, "report.md");
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(join22(tmpdir2(), "skill-forge-evolve-"));
5055
+ const tempDir = mkdtempSync3(join23(tmpdir2(), "skill-forge-evolve-"));
4951
5056
  let profile;
4952
5057
  let safety;
4953
5058
  try {
4954
- cpSync3(proposedSkillMd, join22(tempDir, "SKILL.md"));
5059
+ cpSync3(proposedSkillMd, join23(tempDir, "SKILL.md"));
4955
5060
  if (existsSync19(proposedClaudeMd)) {
4956
- cpSync3(proposedClaudeMd, join22(tempDir, "CLAUDE.md"));
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 = join22(stagingDir, "manifest.json");
5202
+ const manifestPath = join23(stagingDir, "manifest.json");
5098
5203
  let raw;
5099
5204
  try {
5100
- raw = readFileSync13(manifestPath, "utf-8");
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 readFileSync13(a).equals(readFileSync13(b));
5240
+ return readFileSync14(a).equals(readFileSync14(b));
5136
5241
  }
5137
5242
  function isAlreadyAdopted(stagingDir, manifest) {
5138
- const proposedSkillMd = join22(stagingDir, "proposed_SKILL.md");
5139
- const proposedClaudeMd = join22(stagingDir, "proposed_CLAUDE.md");
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 = lstatSync2(path);
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 createHash("sha256").update(readFileSync13(path)).digest("hex");
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 readFileSync14, readdirSync as readdirSync7, realpathSync as realpathSync3, statSync as statSync9 } from "fs";
5177
- import { basename as basename11, join as join23, resolve as resolvePath6 } from "path";
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 = readdirSync7(dir, { withFileTypes: true });
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 = join23(dir, entry.name);
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(join23(dir, "SKILL.md"))) found.add(dir);
5313
+ if (existsSync20(join24(dir, "SKILL.md"))) found.add(dir);
5209
5314
  }
5210
- for (const dir of listSubdirs2(join23(root, "skills"))) {
5211
- if (existsSync20(join23(dir, "SKILL.md"))) found.add(dir);
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(join23(topDir, "skills"))) {
5215
- if (existsSync20(join23(dir, "SKILL.md"))) found.add(dir);
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 = readFileSync14(resolvedPath, "utf-8");
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 = realpathSync3(candidate.dirPath);
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 = join23(realPath, "SKILL.md");
5495
+ const skillMdPath = join24(realPath, "SKILL.md");
5391
5496
  let text;
5392
5497
  try {
5393
- text = readFileSync14(skillMdPath, "utf-8");
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 realpathSync4 } from "fs";
5799
- import { join as join24, resolve as resolvePath8 } from "path";
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 = realpathSync4(resolvedRoot);
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 = join24(resolvedRoot, "SOURCES.md");
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 join25 } from "path";
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 join25(getHomeDir(), "reports", `routine-${generatedAt.replace(/[:.]/g, "-")}.md`);
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 = join25(config.quarantineDir, id);
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 realpathSync5 } from "fs";
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 realpathSync5(p);
6342
+ return realpathSync6(p);
6238
6343
  } catch {
6239
6344
  return null;
6240
6345
  }
6241
6346
  }
6242
6347
  function isUnderRoot(candidate, root) {
6243
- const rel = relative(root, candidate);
6244
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
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 createHash3, randomUUID as randomUUID5 } from "crypto";
6322
- import { chmodSync, existsSync as existsSync24, lstatSync as lstatSync3, mkdirSync as mkdirSync9, readFileSync as readFileSync16, realpathSync as realpathSync8, renameSync as renameSync6, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
6323
- import { dirname as dirname13, isAbsolute as isAbsolute3, join as join28, relative as relative3, resolve as resolvePath12, sep as sep3 } from "path";
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 realpathSync7, statSync as statSync12 } from "fs";
6328
- import { isAbsolute as isAbsolute2, join as join27, relative as relative2, resolve as resolvePath11 } from "path";
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 createHash2 } from "crypto";
6398
- import { existsSync as existsSync22, readFileSync as readFileSync15, statSync as statSync11, realpathSync as realpathSync6 } from "fs";
6399
- import { join as join26, resolve as resolvePath10 } from "path";
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 join26(getHomeDir(), "refinements");
6508
+ return join27(getHomeDir(), "refinements");
6404
6509
  }
6405
6510
  function getRefinementsPath() {
6406
- return join26(getRefinementsDir(), "refinements.json");
6511
+ return join27(getRefinementsDir(), "refinements.json");
6407
6512
  }
6408
6513
  function getPatternsPath() {
6409
- return join26(getRefinementsDir(), "patterns.json");
6514
+ return join27(getRefinementsDir(), "patterns.json");
6410
6515
  }
6411
6516
  function getBackupsDir() {
6412
- return join26(getRefinementsDir(), "backups");
6517
+ return join27(getRefinementsDir(), "backups");
6413
6518
  }
6414
6519
  function readJsonFile(path, label) {
6415
6520
  let raw;
6416
6521
  try {
6417
- raw = readFileSync15(path, "utf-8");
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 realpathSync6(resolved);
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 = readFileSync15(path);
6760
+ const bytes = readFileSync16(path);
6656
6761
  const mode = statSync11(path).mode & 511;
6657
- const sha256 = createHash2("sha256").update(bytes).digest("hex");
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(join26(getBackupsDir(), id, "manifest.json"), manifest);
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 = join26(getBackupsDir(), id, "manifest.json");
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 realpathSync7(p);
6838
+ return realpathSync8(p);
6734
6839
  } catch {
6735
6840
  return null;
6736
6841
  }
6737
6842
  }
6738
6843
  function isUnderRoot2(candidate, root) {
6739
- const rel = relative2(root, candidate);
6740
- return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
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 = join27(resolvedCwd, ".claude", "skills", skill);
6746
- const sharedDir = join27(resolvedCwd, "skills", skill);
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: join27(root, skill), root: userRootFlag, reason: "flag" };
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: join27(resolveConfiguredRoot2(root), skill) }));
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(join27(dir, "SKILL.md")),
6794
- patchMd: isFile2(join27(dir, "SKILL.patch.md")),
6795
- extendMd: isFile2(join27(dir, "SKILL.extend.md")),
6796
- configJson: isFile2(join27(dir, "skill-config.json")),
6797
- hooksDir: isDirectory(join27(dir, "hooks")),
6798
- scriptsDir: isDirectory(join27(dir, "scripts"))
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 = relative3(root, candidate);
7071
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
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 readFileSync16(resolvePath12(options.contentFile), "utf-8");
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 = lstatSync3(scopeDir);
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 = realpathSync8(scopeDir);
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 = join28(scopeDir, relPath);
7335
+ const destPath = join29(scopeDir, relPath);
7231
7336
  const destDir = dirname13(destPath);
7232
7337
  mkdirSync9(destDir, { recursive: true });
7233
- const realDestDir = realpathSync8(destDir);
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 = lstatSync3(destPath);
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 ? join28(scopeDir, probeRelPath) : void 0;
7327
- const existingContent = probePath && existsSync24(probePath) ? readFileSync16(probePath, "utf-8") : "";
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 = join28(scopeDir, relPath);
7331
- const before = existsSync24(destPath) ? readFileSync16(destPath, "utf-8") : "";
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 = join28(getBackupsDir(), backupId);
7533
+ const dir = join29(getBackupsDir(), backupId);
7429
7534
  mkdirSync9(dir, { recursive: true });
7430
- writeFileSync9(join28(dir, "post.json"), JSON.stringify(sidecar, null, 2) + "\n", { mode: 384 });
7535
+ writeFileSync9(join29(dir, "post.json"), JSON.stringify(sidecar, null, 2) + "\n", { mode: 384 });
7431
7536
  }
7432
7537
  function readPostWriteSidecar(backupId) {
7433
- const path = join28(getBackupsDir(), backupId, "post.json");
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 = readFileSync16(path, "utf-8");
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 = realpathSync8(resolvedUserRoot);
7587
+ realUserRoot = realpathSync9(resolvedUserRoot);
7483
7588
  } catch {
7484
7589
  realUserRoot = resolvedUserRoot;
7485
7590
  }
7486
7591
  let realSkillDir;
7487
7592
  try {
7488
- realSkillDir = realpathSync8(skillDir);
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 = join28(skillDir, "SKILL.md");
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 = readFileSync16(skillMdPath, "utf-8");
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 = createHash3("sha256").update(readFileSync16(skillMdPath)).digest("hex");
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 = realpathSync8(backupsDir);
7573
- const backupDir = join28(backupsDir, backupId);
7677
+ const realBackupsDir = realpathSync9(backupsDir);
7678
+ const backupDir = join29(backupsDir, backupId);
7574
7679
  if (!existsSync24(backupDir)) return;
7575
- const realBackupDir = realpathSync8(backupDir);
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 = realpathSync8(lexicalAncestor);
7585
- const remainder = relative3(lexicalAncestor, resolvePath12(path));
7586
- return remainder === "" ? real : join28(real, remainder);
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 realpathSync8(resolved);
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 = lstatSync3(file.path);
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 = relative3(rootLexical, lexicalTarget);
7632
- if (rel !== "" && (rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute3(rel))) continue;
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 = realpathSync8(rootLexical);
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 = relative3(rootLexical, resolvePath12(filePath));
7646
- const components = relPath.split(sep3).filter((c) => c.length > 0);
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 = join28(current, component);
7760
+ const next = join29(current, component);
7656
7761
  let lst;
7657
7762
  try {
7658
- lst = lstatSync3(next);
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 = join28(parentReal, leafComponent);
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 = lstatSync3(finalPath);
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 = join28(parentReal, leafComponent);
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 = lstatSync3(finalPath);
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) ? createHash3("sha256").update(readFileSync16(file.path)).digest("hex") : null;
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) {