@rhize/skill-forge 0.11.0 → 0.11.2

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";
@@ -131,7 +131,32 @@ function collectNpxPackageSpecs(args) {
131
131
  }
132
132
  return { packageFlagSpecs, positionalSpec };
133
133
  }
134
+ var ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
135
+ var PATH_SEGMENT_RE = /^[A-Za-z0-9._-]+$/;
136
+ function looksLikeFsPath(v) {
137
+ if (!/^(?:\/|~\/|\.\.?\/)/.test(v)) return false;
138
+ const segments = v.replace(/^~/, "").split("/").filter(Boolean);
139
+ return segments.length >= 2 && segments.every((seg) => PATH_SEGMENT_RE.test(seg));
140
+ }
141
+ function looksLikeBareUrl(v) {
142
+ let url;
143
+ try {
144
+ url = new URL(v);
145
+ } catch {
146
+ return false;
147
+ }
148
+ if (!["http:", "https:", "ws:", "wss:", "file:"].includes(url.protocol)) return false;
149
+ if (url.username !== "" || url.password !== "") return false;
150
+ if (url.search !== "") return false;
151
+ return true;
152
+ }
153
+ function looksStructuredNonSecret(value) {
154
+ const v = value.trim().replace(/^[[\s'"]+/, "").replace(/[\]\s'",]+$/, "");
155
+ if (v === "") return false;
156
+ return ISO_TIMESTAMP_RE.test(v) || looksLikeBareUrl(v) || looksLikeFsPath(v);
157
+ }
134
158
  function looksHighEntropy(value) {
159
+ if (looksStructuredNonSecret(value)) return false;
135
160
  if (/\s/.test(value)) return false;
136
161
  if (value.length < 20) return false;
137
162
  const hasLower = /[a-z]/.test(value);
@@ -143,6 +168,9 @@ function looksHighEntropy(value) {
143
168
  }
144
169
 
145
170
  // src/preferences.ts
171
+ function markAccepted(value) {
172
+ return value;
173
+ }
146
174
  var PREFERENCE_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
147
175
  var MAX_PREFERENCE_VALUE_LENGTH = 500;
148
176
  var MAX_PENDING_PROPOSALS = 50;
@@ -208,7 +236,7 @@ function setPreference(config, key, value, options = {}) {
208
236
  validateProvenanceText("origin", options.origin);
209
237
  validateProvenanceText("note", options.note);
210
238
  const entry = {
211
- value,
239
+ value: markAccepted(value),
212
240
  source: options.source ?? "user",
213
241
  updatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
214
242
  origin: options.origin,
@@ -285,7 +313,9 @@ function normalizePreferences(raw) {
285
313
  continue;
286
314
  }
287
315
  out[key] = {
288
- value: entry.value,
316
+ // Re-minted only after `validatePreference` passed above: a hand-edited config.json cannot
317
+ // smuggle in a value that would fail today's rules.
318
+ value: markAccepted(entry.value),
289
319
  source: entry.source === "agent" ? "agent" : "user",
290
320
  updatedAt: typeof entry.updatedAt === "string" ? entry.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString(),
291
321
  origin,
@@ -625,19 +655,117 @@ function defaultExec(command, args, options) {
625
655
 
626
656
  // src/install/promote.ts
627
657
  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";
658
+ import { basename as basename3, join as join6 } from "path";
659
+
660
+ // src/install/fingerprint.ts
661
+ import { createHash } from "crypto";
662
+ import { lstatSync, readFileSync as readFileSync3, readdirSync as readdirSync3, readlinkSync, realpathSync } from "fs";
663
+ import { isAbsolute, join as join5, relative, sep } from "path";
664
+ var SKIP_DIRS3 = /* @__PURE__ */ new Set([".git", "node_modules"]);
665
+ function isInside(child, root) {
666
+ const rel = relative(root, child);
667
+ return rel === "" || !rel.startsWith(".." + sep) && rel !== ".." && !isAbsolute(rel);
668
+ }
669
+ function fingerprintTree(root) {
670
+ const hash = createHash("sha256");
671
+ const symlinks = [];
672
+ const escaping = [];
673
+ const realRoot = realpathSync(root);
674
+ function walk2(dir) {
675
+ const entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
676
+ for (const entry of entries) {
677
+ const full = join5(dir, entry.name);
678
+ const rel = relative(root, full);
679
+ if (entry.isSymbolicLink()) {
680
+ const target = readlinkSync(full);
681
+ symlinks.push(`${rel} -> ${target}`);
682
+ hash.update(`L ${rel} ${target}
683
+ `);
684
+ try {
685
+ if (!isInside(realpathSync(full), realRoot)) escaping.push(rel);
686
+ } catch {
687
+ escaping.push(rel);
688
+ }
689
+ continue;
690
+ }
691
+ if (entry.isDirectory()) {
692
+ if (SKIP_DIRS3.has(entry.name)) continue;
693
+ hash.update(`D ${rel}
694
+ `);
695
+ walk2(full);
696
+ continue;
697
+ }
698
+ if (entry.isFile()) {
699
+ const contentHash = createHash("sha256").update(readFileSync3(full)).digest("hex");
700
+ hash.update(`F ${rel} ${contentHash}
701
+ `);
702
+ continue;
703
+ }
704
+ hash.update(`O ${rel}
705
+ `);
706
+ }
707
+ }
708
+ walk2(root);
709
+ return { digest: hash.digest("hex"), symlinks, escaping };
710
+ }
711
+ function fingerprintDirs(dirs) {
712
+ const hash = createHash("sha256");
713
+ const symlinks = [];
714
+ const escaping = [];
715
+ for (const dir of [...dirs].sort()) {
716
+ let result;
717
+ try {
718
+ result = fingerprintTree(dir);
719
+ } catch (err) {
720
+ hash.update(`MISSING ${dir} ${err.message}
721
+ `);
722
+ continue;
723
+ }
724
+ hash.update(`DIR ${dir} ${result.digest}
725
+ `);
726
+ symlinks.push(...result.symlinks.map((s) => `${dir}: ${s}`));
727
+ escaping.push(...result.escaping.map((s) => `${dir}: ${s}`));
728
+ }
729
+ return { digest: hash.digest("hex"), symlinks, escaping };
730
+ }
731
+ function isRealDirectory(path) {
732
+ try {
733
+ return lstatSync(path).isDirectory();
734
+ } catch {
735
+ return false;
736
+ }
737
+ }
738
+
739
+ // src/install/promote.ts
629
740
  function promote(entry, targetSkillsRoot, options = {}) {
630
741
  const force = options.force ?? false;
631
742
  for (const skillDir of entry.skillDirs) {
632
- const dest = join5(targetSkillsRoot, basename3(skillDir));
743
+ if (!isRealDirectory(skillDir)) {
744
+ throw new UserError(
745
+ `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`
746
+ );
747
+ }
748
+ const dest = join6(targetSkillsRoot, basename3(skillDir));
633
749
  if (existsSync4(dest) && !force) {
634
750
  throw new Error(`Refusing to overwrite existing skill dir: ${dest} (pass force to override)`);
635
751
  }
636
752
  }
753
+ const fingerprint = fingerprintDirs(entry.skillDirs);
754
+ if (fingerprint.escaping.length > 0) {
755
+ throw new UserError(
756
+ `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:
757
+ ` + fingerprint.escaping.join("\n ")
758
+ );
759
+ }
760
+ if (options.expectedFingerprint !== void 0 && options.expectedFingerprint !== fingerprint.digest) {
761
+ throw new UserError(
762
+ `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.`
763
+ );
764
+ }
637
765
  mkdirSync4(targetSkillsRoot, { recursive: true });
638
766
  const promoted = [];
639
767
  for (const skillDir of entry.skillDirs) {
640
- const dest = join5(targetSkillsRoot, basename3(skillDir));
768
+ const dest = join6(targetSkillsRoot, basename3(skillDir));
641
769
  if (existsSync4(dest)) {
642
770
  rmSync2(dest, { recursive: true, force: true });
643
771
  }
@@ -666,7 +794,7 @@ function moveDir(src, dest) {
666
794
  }
667
795
 
668
796
  // src/install/promoteMcp.ts
669
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
797
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
670
798
  import { dirname as dirname2 } from "path";
671
799
  function promoteMcp(_entry, profile, targetConfigPath, options = {}) {
672
800
  const force = options.force ?? false;
@@ -675,7 +803,7 @@ function promoteMcp(_entry, profile, targetConfigPath, options = {}) {
675
803
  let config = {};
676
804
  let rawExisting;
677
805
  if (existsSync5(targetConfigPath)) {
678
- rawExisting = readFileSync3(targetConfigPath, "utf-8");
806
+ rawExisting = readFileSync4(targetConfigPath, "utf-8");
679
807
  try {
680
808
  config = JSON.parse(rawExisting);
681
809
  } catch (err) {
@@ -759,11 +887,11 @@ function deriveServerEntry(profile) {
759
887
  }
760
888
 
761
889
  // src/install/mcpCandidate.ts
762
- import { readFileSync as readFileSync4 } from "fs";
763
- import { dirname as dirname3, join as join6 } from "path";
890
+ import { readFileSync as readFileSync5 } from "fs";
891
+ import { dirname as dirname3, join as join7 } from "path";
764
892
  function readPackageInfo(pkgPath) {
765
893
  try {
766
- const pkg2 = JSON.parse(readFileSync4(pkgPath, "utf-8"));
894
+ const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
767
895
  return {
768
896
  name: typeof pkg2.name === "string" ? pkg2.name : void 0,
769
897
  version: typeof pkg2.version === "string" ? pkg2.version : void 0
@@ -774,7 +902,7 @@ function readPackageInfo(pkgPath) {
774
902
  }
775
903
  function readConfigEntry(configPath, name) {
776
904
  try {
777
- const cfg = JSON.parse(readFileSync4(configPath, "utf-8"));
905
+ const cfg = JSON.parse(readFileSync5(configPath, "utf-8"));
778
906
  const servers = cfg.mcpServers ?? cfg.servers ?? {};
779
907
  const raw = servers[name] ?? {};
780
908
  return {
@@ -792,7 +920,7 @@ function resolveMcpCandidateDetail(profile) {
792
920
  return { name, version: readPackageInfo(profile.config).version };
793
921
  }
794
922
  const entry = readConfigEntry(profile.config, name);
795
- const sidecar = readPackageInfo(join6(dirname3(profile.config), "package.json"));
923
+ const sidecar = readPackageInfo(join7(dirname3(profile.config), "package.json"));
796
924
  return {
797
925
  name,
798
926
  command: entry?.command,
@@ -804,8 +932,8 @@ function resolveMcpCandidateDetail(profile) {
804
932
  }
805
933
 
806
934
  // 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";
935
+ import { appendFileSync, existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
936
+ import { join as join8 } from "path";
809
937
  var FORGE_VERBS = ["DEFER", "ABSORB", "FORK", "REJECT", "WATCH"];
810
938
  var LEDGER_HEADER = "# Skill Forge \u2014 Provenance Ledger\n\nOne entry per external-skill ingestion decision.\n";
811
939
  function todayLocalIsoDate() {
@@ -836,7 +964,7 @@ function appendProvenance(entry, skillsRoot) {
836
964
  - **Drift check:** \`${driftCheck}\`
837
965
  - **Notes:** ${entry.notes ?? ""}
838
966
  `;
839
- const ledgerPath = join7(skillsRoot, "SOURCES.md");
967
+ const ledgerPath = join8(skillsRoot, "SOURCES.md");
840
968
  if (!existsSync6(ledgerPath)) {
841
969
  writeFileSync4(ledgerPath, LEDGER_HEADER, "utf-8");
842
970
  }
@@ -884,41 +1012,41 @@ function parseProvenanceLedger(text) {
884
1012
  return { entries, warnings };
885
1013
  }
886
1014
  function readProvenanceLedger(skillsRoot) {
887
- const ledgerPath = join7(skillsRoot, "SOURCES.md");
1015
+ const ledgerPath = join8(skillsRoot, "SOURCES.md");
888
1016
  if (!existsSync6(ledgerPath)) {
889
1017
  return { ledgerPath, exists: false, entries: [], warnings: [] };
890
1018
  }
891
- const text = readFileSync5(ledgerPath, "utf-8");
1019
+ const text = readFileSync6(ledgerPath, "utf-8");
892
1020
  const parsed = parseProvenanceLedger(text);
893
1021
  return { ledgerPath, exists: true, ...parsed };
894
1022
  }
895
1023
 
896
1024
  // src/commands/gatePipeline.ts
897
- import { readdirSync as readdirSync6 } from "fs";
1025
+ import { readdirSync as readdirSync7 } from "fs";
898
1026
 
899
1027
  // 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";
1028
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
1029
+ import { basename as basename7, dirname as dirname5, join as join12 } from "path";
902
1030
 
903
1031
  // src/audit.ts
904
1032
  import {
905
1033
  existsSync as existsSync8,
906
- lstatSync,
1034
+ lstatSync as lstatSync2,
907
1035
  mkdtempSync,
908
- readFileSync as readFileSync7,
909
- readdirSync as readdirSync4,
910
- realpathSync,
1036
+ readFileSync as readFileSync8,
1037
+ readdirSync as readdirSync5,
1038
+ realpathSync as realpathSync2,
911
1039
  renameSync as renameSync3,
912
1040
  rmSync as rmSync3,
913
1041
  statSync as statSync6,
914
1042
  writeFileSync as writeFileSync5
915
1043
  } from "fs";
916
- import { basename as basename6, delimiter as delimiter3, extname as extname3, join as join10, resolve as resolvePath2 } from "path";
1044
+ import { basename as basename6, delimiter as delimiter3, extname as extname3, join as join11, resolve as resolvePath2 } from "path";
917
1045
 
918
1046
  // src/gate/safety.ts
919
1047
  import { statSync as statSync4 } from "fs";
920
1048
  import { execFile } from "child_process";
921
- import { delimiter as delimiter2, join as join8 } from "path";
1049
+ import { delimiter as delimiter2, join as join9 } from "path";
922
1050
  import { promisify } from "util";
923
1051
  var execFileAsync = promisify(execFile);
924
1052
  var DENY_RULES = [
@@ -1017,7 +1145,7 @@ function which(cmd) {
1017
1145
  const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
1018
1146
  for (const dir of dirs) {
1019
1147
  for (const ext of exts) {
1020
- const candidate = join8(dir, cmd + ext);
1148
+ const candidate = join9(dir, cmd + ext);
1021
1149
  try {
1022
1150
  if (statSync4(candidate).isFile()) return candidate;
1023
1151
  } catch {
@@ -1140,14 +1268,22 @@ function scanJsonConfig(file, parsed) {
1140
1268
  }
1141
1269
  if (node.env && typeof node.env === "object" && !Array.isArray(node.env)) {
1142
1270
  for (const [key, value] of Object.entries(node.env)) {
1143
- if (typeof value !== "string" || isPlaceholderValue(value)) continue;
1144
- if (looksCredentialKey(key) || looksHighEntropy(value)) {
1271
+ if (typeof value !== "string") continue;
1272
+ const kind = classifyCredentialAssignment(key, value);
1273
+ if (kind === "inline-secret") {
1145
1274
  findings.push({
1146
1275
  severity: "HIGH",
1147
1276
  rule: "mcp-inline-credential-value",
1148
1277
  file,
1149
1278
  detail: `env.${key} has an inline credential value \u2014 emit an empty string and document that the user must fill it in`
1150
1279
  });
1280
+ } else if (kind === "file-path") {
1281
+ findings.push({
1282
+ severity: "LOW",
1283
+ rule: "mcp-credential-file-path",
1284
+ file,
1285
+ detail: `env.${key} points at a credential file rather than an inline secret \u2014 the value leaks nothing, but keep that file out of version control`
1286
+ });
1151
1287
  }
1152
1288
  }
1153
1289
  }
@@ -1159,15 +1295,29 @@ var DANGEROUS_FLAG_RE = /--dangerously-[\w-]+|--no-sandbox\b/g;
1159
1295
  var CREDENTIAL_ASSIGNMENT_RE = /["']?([A-Za-z][A-Za-z0-9_]*)["']?\s*[:=]\s*["']([^"']{6,})["']/g;
1160
1296
  var CREDENTIAL_ASSIGNMENT_UNQUOTED_RE = /^\s*(?:export\s+|-\s+)?([A-Za-z][A-Za-z0-9_]*)\s*[:=]\s*(\S+)/;
1161
1297
  var LEADING_QUOTE_RE = /^["']/;
1298
+ function classifyCredentialAssignment(key, value) {
1299
+ if (isPlaceholderValue(value)) return "none";
1300
+ const credentialKey = looksCredentialKey(key);
1301
+ if (credentialKey && looksStructuredNonSecret(value) && !/^[a-z]+:\/\//i.test(value.trim())) return "file-path";
1302
+ if (credentialKey || looksHighEntropy(value)) return "inline-secret";
1303
+ return "none";
1304
+ }
1162
1305
  function pushCredentialFinding(findings, file, lineNo, varName, value) {
1163
- if (isPlaceholderValue(value)) return;
1164
- if (looksCredentialKey(varName) || looksHighEntropy(value)) {
1306
+ const kind = classifyCredentialAssignment(varName, value);
1307
+ if (kind === "inline-secret") {
1165
1308
  findings.push({
1166
1309
  severity: "HIGH",
1167
1310
  rule: "mcp-inline-credential-value",
1168
1311
  file,
1169
1312
  detail: `line ${lineNo}: inline credential value assigned to ${varName}`
1170
1313
  });
1314
+ } else if (kind === "file-path") {
1315
+ findings.push({
1316
+ severity: "LOW",
1317
+ rule: "mcp-credential-file-path",
1318
+ file,
1319
+ detail: `line ${lineNo}: ${varName} points at a credential file rather than an inline secret \u2014 the value leaks nothing, but keep that file out of version control`
1320
+ });
1171
1321
  }
1172
1322
  }
1173
1323
  function scanTextFile(file, content, allowCredentialCheck) {
@@ -1243,8 +1393,8 @@ async function mcpScan(targetPath, strictness = DEFAULT_STRICTNESS) {
1243
1393
  }
1244
1394
 
1245
1395
  // 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";
1396
+ import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
1397
+ import { basename as basename5, dirname as dirname4, join as join10, resolve as resolve2 } from "path";
1248
1398
  var STOPWORDS = new Set(
1249
1399
  `
1250
1400
  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 +1403,20 @@ skill skills user claude code based comprehensive enhanced practical including i
1253
1403
  `.split(/\s+/).filter(Boolean)
1254
1404
  );
1255
1405
  var DEFAULT_SET_THRESHOLD = 0.45;
1256
- function splitLimited(text, sep4, maxSplit) {
1406
+ function splitLimited(text, sep5, maxSplit) {
1257
1407
  const parts = [];
1258
1408
  let rest = text;
1259
1409
  for (let i = 0; i < maxSplit; i++) {
1260
- const idx = rest.indexOf(sep4);
1410
+ const idx = rest.indexOf(sep5);
1261
1411
  if (idx === -1) break;
1262
1412
  parts.push(rest.slice(0, idx));
1263
- rest = rest.slice(idx + sep4.length);
1413
+ rest = rest.slice(idx + sep5.length);
1264
1414
  }
1265
1415
  parts.push(rest);
1266
1416
  return parts;
1267
1417
  }
1268
1418
  function readFrontmatter(skillMdPath) {
1269
- const text = readFileSync6(skillMdPath, "utf-8");
1419
+ const text = readFileSync7(skillMdPath, "utf-8");
1270
1420
  const meta = { name: basename5(dirname4(skillMdPath)), description: "" };
1271
1421
  if (text.startsWith("---\n")) {
1272
1422
  const block = splitLimited(text, "---", 2)[1] ?? "";
@@ -1364,25 +1514,25 @@ function listSubdirs(dir) {
1364
1514
  if (!existsSync7(dir)) return [];
1365
1515
  let entries;
1366
1516
  try {
1367
- entries = readdirSync3(dir, { withFileTypes: true });
1517
+ entries = readdirSync4(dir, { withFileTypes: true });
1368
1518
  } catch {
1369
1519
  return [];
1370
1520
  }
1371
- return entries.filter((e) => e.isDirectory()).map((e) => join9(dir, e.name));
1521
+ return entries.filter((e) => e.isDirectory()).map((e) => join10(dir, e.name));
1372
1522
  }
1373
1523
  function findSkillMdFiles(root) {
1374
1524
  const found = /* @__PURE__ */ new Set();
1375
1525
  for (const dir of listSubdirs(root)) {
1376
- const candidate = join9(dir, "SKILL.md");
1526
+ const candidate = join10(dir, "SKILL.md");
1377
1527
  if (existsSync7(candidate)) found.add(resolve2(candidate));
1378
1528
  }
1379
- for (const dir of listSubdirs(join9(root, "skills"))) {
1380
- const candidate = join9(dir, "SKILL.md");
1529
+ for (const dir of listSubdirs(join10(root, "skills"))) {
1530
+ const candidate = join10(dir, "SKILL.md");
1381
1531
  if (existsSync7(candidate)) found.add(resolve2(candidate));
1382
1532
  }
1383
1533
  for (const topDir of listSubdirs(root)) {
1384
- for (const dir of listSubdirs(join9(topDir, "skills"))) {
1385
- const candidate = join9(dir, "SKILL.md");
1534
+ for (const dir of listSubdirs(join10(topDir, "skills"))) {
1535
+ const candidate = join10(dir, "SKILL.md");
1386
1536
  if (existsSync7(candidate)) found.add(resolve2(candidate));
1387
1537
  }
1388
1538
  }
@@ -1395,7 +1545,7 @@ function scanOverlap(candidatePath, skillsRoot) {
1395
1545
  }
1396
1546
  let cpath = resolve2(expandHome(candidatePath));
1397
1547
  if (isDir(cpath)) {
1398
- cpath = join9(cpath, "SKILL.md");
1548
+ cpath = join10(cpath, "SKILL.md");
1399
1549
  }
1400
1550
  if (!existsSync7(cpath)) {
1401
1551
  throw new Error(`candidate SKILL.md not found: ${cpath}`);
@@ -1547,13 +1697,13 @@ function isDirFollowingSymlink(p) {
1547
1697
  function listSubdirsFollowingSymlinks(dir) {
1548
1698
  let entries;
1549
1699
  try {
1550
- entries = readdirSync4(dir, { withFileTypes: true });
1700
+ entries = readdirSync5(dir, { withFileTypes: true });
1551
1701
  } catch {
1552
1702
  return [];
1553
1703
  }
1554
1704
  const out = [];
1555
1705
  for (const entry of entries) {
1556
- const full = join10(dir, entry.name);
1706
+ const full = join11(dir, entry.name);
1557
1707
  if (entry.isDirectory() || entry.isSymbolicLink() && isDirFollowingSymlink(full)) {
1558
1708
  out.push(full);
1559
1709
  }
@@ -1563,24 +1713,24 @@ function listSubdirsFollowingSymlinks(dir) {
1563
1713
  function listSubdirsNoSymlinks(dir) {
1564
1714
  let entries;
1565
1715
  try {
1566
- entries = readdirSync4(dir, { withFileTypes: true });
1716
+ entries = readdirSync5(dir, { withFileTypes: true });
1567
1717
  } catch {
1568
1718
  return [];
1569
1719
  }
1570
- return entries.filter((e) => e.isDirectory()).map((e) => join10(dir, e.name));
1720
+ return entries.filter((e) => e.isDirectory()).map((e) => join11(dir, e.name));
1571
1721
  }
1572
1722
  function findSkillDirsUnderRoot(root) {
1573
1723
  const found = /* @__PURE__ */ new Set();
1574
1724
  const rootSubdirs = listSubdirsFollowingSymlinks(root);
1575
1725
  for (const dir of rootSubdirs) {
1576
- if (existsSync8(join10(dir, "SKILL.md"))) found.add(dir);
1726
+ if (existsSync8(join11(dir, "SKILL.md"))) found.add(dir);
1577
1727
  }
1578
- for (const dir of listSubdirsFollowingSymlinks(join10(root, "skills"))) {
1579
- if (existsSync8(join10(dir, "SKILL.md"))) found.add(dir);
1728
+ for (const dir of listSubdirsFollowingSymlinks(join11(root, "skills"))) {
1729
+ if (existsSync8(join11(dir, "SKILL.md"))) found.add(dir);
1580
1730
  }
1581
1731
  for (const topDir of rootSubdirs) {
1582
- for (const dir of listSubdirsNoSymlinks(join10(topDir, "skills"))) {
1583
- if (existsSync8(join10(dir, "SKILL.md"))) found.add(dir);
1732
+ for (const dir of listSubdirsNoSymlinks(join11(topDir, "skills"))) {
1733
+ if (existsSync8(join11(dir, "SKILL.md"))) found.add(dir);
1584
1734
  }
1585
1735
  }
1586
1736
  return Array.from(found).filter((p) => !p.includes("/.git/"));
@@ -1594,7 +1744,7 @@ function collectSkillCandidates(roots) {
1594
1744
  let realPath;
1595
1745
  if (exists) {
1596
1746
  try {
1597
- realPath = realpathSync(resolvedRoot);
1747
+ realPath = realpathSync2(resolvedRoot);
1598
1748
  } catch {
1599
1749
  realPath = void 0;
1600
1750
  }
@@ -1613,7 +1763,7 @@ function dedupeSkillCandidates(candidates, findings) {
1613
1763
  for (const candidate of candidates) {
1614
1764
  let realPath;
1615
1765
  try {
1616
- realPath = realpathSync(candidate.configuredPath);
1766
+ realPath = realpathSync2(candidate.configuredPath);
1617
1767
  } catch (err) {
1618
1768
  findings.push({
1619
1769
  severity: "MEDIUM",
@@ -1676,7 +1826,7 @@ function estimateTokens(text) {
1676
1826
  return Math.ceil(Buffer.byteLength(text, "utf-8") / 4);
1677
1827
  }
1678
1828
  function profileAuditedSkill(group, findings, unreadableSkillMdPaths) {
1679
- const skillMdPath = join10(group.canonicalPath, "SKILL.md");
1829
+ const skillMdPath = join11(group.canonicalPath, "SKILL.md");
1680
1830
  const base = {
1681
1831
  path: group.canonicalPath,
1682
1832
  skillMdPath,
@@ -1687,7 +1837,7 @@ function profileAuditedSkill(group, findings, unreadableSkillMdPaths) {
1687
1837
  let sizeBytes;
1688
1838
  try {
1689
1839
  sizeBytes = statSync6(skillMdPath).size;
1690
- text = readFileSync7(skillMdPath, "utf-8");
1840
+ text = readFileSync8(skillMdPath, "utf-8");
1691
1841
  } catch (err) {
1692
1842
  findings.push({
1693
1843
  severity: "MEDIUM",
@@ -1765,7 +1915,7 @@ function auditMcpTarget(targetPath, notices) {
1765
1915
  }
1766
1916
  if (isToml) {
1767
1917
  try {
1768
- readFileSync7(targetPath, "utf-8");
1918
+ readFileSync8(targetPath, "utf-8");
1769
1919
  } catch (err) {
1770
1920
  findings.push({
1771
1921
  severity: "MEDIUM",
@@ -1780,7 +1930,7 @@ function auditMcpTarget(targetPath, notices) {
1780
1930
  }
1781
1931
  let raw;
1782
1932
  try {
1783
- raw = readFileSync7(targetPath, "utf-8");
1933
+ raw = readFileSync8(targetPath, "utf-8");
1784
1934
  } catch (err) {
1785
1935
  findings.push({
1786
1936
  severity: "MEDIUM",
@@ -1856,7 +2006,7 @@ function defaultWhich(cmd) {
1856
2006
  const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
1857
2007
  for (const dir of dirs) {
1858
2008
  for (const ext of exts) {
1859
- const candidate = join10(dir, cmd + ext);
2009
+ const candidate = join11(dir, cmd + ext);
1860
2010
  try {
1861
2011
  if (statSync6(candidate).isFile()) return candidate;
1862
2012
  } catch {
@@ -2208,10 +2358,10 @@ async function scaffoldFoundationSkill(config, profile, targetRoot) {
2208
2358
  if (!isDir(resolvedRoot)) {
2209
2359
  throw new UserError(`skills root does not exist: ${resolvedRoot}`);
2210
2360
  }
2211
- const destDir = join10(resolvedRoot, "business-foundation");
2361
+ const destDir = join11(resolvedRoot, "business-foundation");
2212
2362
  let destLstat;
2213
2363
  try {
2214
- destLstat = lstatSync(destDir);
2364
+ destLstat = lstatSync2(destDir);
2215
2365
  } catch {
2216
2366
  destLstat = void 0;
2217
2367
  }
@@ -2222,9 +2372,9 @@ async function scaffoldFoundationSkill(config, profile, targetRoot) {
2222
2372
  throw new UserError(`refusing to scaffold a foundation skill at ${destDir}: already exists`);
2223
2373
  }
2224
2374
  const content = renderFoundationSkillMd(profile);
2225
- const tempDir = mkdtempSync(join10(resolvedRoot, ".business-foundation-"));
2375
+ const tempDir = mkdtempSync(join11(resolvedRoot, ".business-foundation-"));
2226
2376
  try {
2227
- writeFileSync5(join10(tempDir, "SKILL.md"), content, { flag: "wx", mode: 384 });
2377
+ writeFileSync5(join11(tempDir, "SKILL.md"), content, { flag: "wx", mode: 384 });
2228
2378
  const safety = await scanSafety(tempDir, config.strictness);
2229
2379
  if (safety.verdict === "block") {
2230
2380
  throw new UserError(
@@ -2258,7 +2408,7 @@ async function scaffoldFoundationSkill(config, profile, targetRoot) {
2258
2408
 
2259
2409
  // src/auditState.ts
2260
2410
  function getAuditStatePath() {
2261
- return join11(getHomeDir(), "audit-state.json");
2411
+ return join12(getHomeDir(), "audit-state.json");
2262
2412
  }
2263
2413
  function emptyCounts() {
2264
2414
  return { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
@@ -2315,7 +2465,7 @@ function readAuditState() {
2315
2465
  const path = getAuditStatePath();
2316
2466
  if (!existsSync9(path)) return void 0;
2317
2467
  try {
2318
- const parsed = JSON.parse(readFileSync8(path, "utf-8"));
2468
+ const parsed = JSON.parse(readFileSync9(path, "utf-8"));
2319
2469
  if (parsed.schemaVersion !== 1 || typeof parsed.lastRunAt !== "string") return void 0;
2320
2470
  return {
2321
2471
  schemaVersion: 1,
@@ -2376,7 +2526,7 @@ function formatAuditAdvisory(state, options = {}) {
2376
2526
  }
2377
2527
 
2378
2528
  // src/commands/gatePipeline.ts
2379
- import { join as join14 } from "path";
2529
+ import { join as join15 } from "path";
2380
2530
 
2381
2531
  // src/gate/profile.ts
2382
2532
  import { execFileSync } from "child_process";
@@ -2385,23 +2535,23 @@ import {
2385
2535
  existsSync as existsSync11,
2386
2536
  mkdtempSync as mkdtempSync2,
2387
2537
  openSync,
2388
- readFileSync as readFileSync10,
2389
- readdirSync as readdirSync5,
2538
+ readFileSync as readFileSync11,
2539
+ readdirSync as readdirSync6,
2390
2540
  readSync,
2391
2541
  rmSync as rmSync4,
2392
2542
  statSync as statSync7
2393
2543
  } from "fs";
2394
2544
  import { tmpdir } from "os";
2395
- import { basename as basename8, dirname as dirname6, extname as extname5, join as join13, resolve as resolve3 } from "path";
2545
+ import { basename as basename8, dirname as dirname6, extname as extname5, join as join14, resolve as resolve3 } from "path";
2396
2546
 
2397
2547
  // src/gate/mcpCapabilities.ts
2398
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
2399
- import { extname as extname4, join as join12 } from "path";
2548
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2549
+ import { extname as extname4, join as join13 } from "path";
2400
2550
  var SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2401
2551
  var MAX_ITEMS_PER_CATEGORY = 200;
2402
2552
  function readJsonSafe(file) {
2403
2553
  try {
2404
- return JSON.parse(readFileSync9(file, "utf-8"));
2554
+ return JSON.parse(readFileSync10(file, "utf-8"));
2405
2555
  } catch {
2406
2556
  return void 0;
2407
2557
  }
@@ -2502,7 +2652,7 @@ var MCP_SDK_IMPORT_RE = /@modelcontextprotocol\/sdk/;
2502
2652
  function extractMcpCapabilities(packageDir) {
2503
2653
  const buckets = { tools: /* @__PURE__ */ new Set(), resources: /* @__PURE__ */ new Set(), prompts: /* @__PURE__ */ new Set() };
2504
2654
  let hasPackageJsonMarker = false;
2505
- const pkgPath = join12(packageDir, "package.json");
2655
+ const pkgPath = join13(packageDir, "package.json");
2506
2656
  if (existsSync10(pkgPath)) {
2507
2657
  const pkg2 = readJsonSafe(pkgPath);
2508
2658
  if (pkg2 && typeof pkg2 === "object" && !Array.isArray(pkg2)) {
@@ -2510,7 +2660,7 @@ function extractMcpCapabilities(packageDir) {
2510
2660
  }
2511
2661
  }
2512
2662
  for (const manifestName of [".mcp.json", "mcp.json"]) {
2513
- const manifestPath = join12(packageDir, manifestName);
2663
+ const manifestPath = join13(packageDir, manifestName);
2514
2664
  if (!existsSync10(manifestPath)) continue;
2515
2665
  const parsed = readJsonSafe(manifestPath);
2516
2666
  if (parsed !== void 0) {
@@ -2618,7 +2768,7 @@ function asProfile(candidatePath, artifact) {
2618
2768
  return asSkillProfile(profile, `at ${candidatePath}`);
2619
2769
  }
2620
2770
  function classifyInput(p) {
2621
- if (existsSync11(join13(p, ".claude-plugin", "plugin.json"))) return "plugin";
2771
+ if (existsSync11(join14(p, ".claude-plugin", "plugin.json"))) return "plugin";
2622
2772
  if (detectArtifactType(p) === "mcp") return "mcp";
2623
2773
  return "skill";
2624
2774
  }
@@ -2626,10 +2776,10 @@ var MCP_SDK_DEP2 = "@modelcontextprotocol/sdk";
2626
2776
  var MCP_KEYWORD2 = "mcp-server";
2627
2777
  var MCP_BIN_PREFIX2 = /^mcp-/i;
2628
2778
  function readPackageJsonSafe(root) {
2629
- const pkgPath = join13(root, "package.json");
2779
+ const pkgPath = join14(root, "package.json");
2630
2780
  if (!existsSync11(pkgPath)) return void 0;
2631
2781
  try {
2632
- return JSON.parse(readFileSync10(pkgPath, "utf-8"));
2782
+ return JSON.parse(readFileSync11(pkgPath, "utf-8"));
2633
2783
  } catch {
2634
2784
  return void 0;
2635
2785
  }
@@ -2650,7 +2800,7 @@ function hasMcpPackageMarkers(pkg2) {
2650
2800
  return false;
2651
2801
  }
2652
2802
  function detectArtifactType(root) {
2653
- if (existsSync11(join13(root, ".mcp.json")) || existsSync11(join13(root, "mcp.json"))) return "mcp";
2803
+ if (existsSync11(join14(root, ".mcp.json")) || existsSync11(join14(root, "mcp.json"))) return "mcp";
2654
2804
  const pkg2 = readPackageJsonSafe(root);
2655
2805
  if (pkg2 && hasMcpPackageMarkers(pkg2)) return "mcp";
2656
2806
  return "skill";
@@ -2660,7 +2810,7 @@ function resolveSkillMd(inputPath) {
2660
2810
  let tmpDir;
2661
2811
  const initialStat = statSync7(currentPath);
2662
2812
  if (extname5(currentPath) === ".skill" || initialStat.isFile() && isZipFile(currentPath)) {
2663
- tmpDir = mkdtempSync2(join13(tmpdir(), "forge_"));
2813
+ tmpDir = mkdtempSync2(join14(tmpdir(), "forge_"));
2664
2814
  extractZip(currentPath, tmpDir);
2665
2815
  currentPath = tmpDir;
2666
2816
  }
@@ -2669,7 +2819,7 @@ function resolveSkillMd(inputPath) {
2669
2819
  return { skillMd: currentPath, root: dirname6(currentPath), tmpDir };
2670
2820
  }
2671
2821
  if (stat.isDirectory()) {
2672
- const direct = join13(currentPath, "SKILL.md");
2822
+ const direct = join14(currentPath, "SKILL.md");
2673
2823
  if (existsSync11(direct)) {
2674
2824
  return { skillMd: direct, root: currentPath, tmpDir };
2675
2825
  }
@@ -2693,17 +2843,17 @@ function cleanupTmp(tmpDir) {
2693
2843
  }
2694
2844
  function findSkillMdOneLevelDown(dir) {
2695
2845
  const matches = /* @__PURE__ */ new Set();
2696
- for (const entry of readdirSync5(dir, { withFileTypes: true })) {
2846
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2697
2847
  if (entry.isDirectory()) {
2698
- const candidate = join13(dir, entry.name, "SKILL.md");
2848
+ const candidate = join14(dir, entry.name, "SKILL.md");
2699
2849
  if (existsSync11(candidate)) matches.add(candidate);
2700
2850
  }
2701
2851
  }
2702
- const skillsDir = join13(dir, "skills");
2852
+ const skillsDir = join14(dir, "skills");
2703
2853
  if (isDir(skillsDir)) {
2704
- for (const entry of readdirSync5(skillsDir, { withFileTypes: true })) {
2854
+ for (const entry of readdirSync6(skillsDir, { withFileTypes: true })) {
2705
2855
  if (entry.isDirectory()) {
2706
- const candidate = join13(skillsDir, entry.name, "SKILL.md");
2856
+ const candidate = join14(skillsDir, entry.name, "SKILL.md");
2707
2857
  if (existsSync11(candidate)) matches.add(candidate);
2708
2858
  }
2709
2859
  }
@@ -2787,9 +2937,9 @@ function detectLicense(root, fm) {
2787
2937
  if (fm.license) return fm.license;
2788
2938
  const names = ["LICENSE", "LICENSE.md", "LICENSE.txt", "COPYING", "license"];
2789
2939
  for (const name of names) {
2790
- const p = join13(root, name);
2940
+ const p = join14(root, name);
2791
2941
  if (existsSync11(p) && statSync7(p).isFile()) {
2792
- const head = readFileSync10(p, "utf-8").slice(0, 2e3).toLowerCase();
2942
+ const head = readFileSync11(p, "utf-8").slice(0, 2e3).toLowerCase();
2793
2943
  for (const [hint, label] of Object.entries(SPDX_HINTS)) {
2794
2944
  if (head.includes(hint)) return label;
2795
2945
  }
@@ -2800,8 +2950,8 @@ function detectLicense(root, fm) {
2800
2950
  }
2801
2951
  function walkFiles(dir) {
2802
2952
  const out = [];
2803
- for (const entry of readdirSync5(dir, { withFileTypes: true })) {
2804
- const full = join13(dir, entry.name);
2953
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2954
+ const full = join14(dir, entry.name);
2805
2955
  if (entry.isDirectory()) {
2806
2956
  out.push(...walkFiles(full));
2807
2957
  } else if (entry.isFile()) {
@@ -2813,7 +2963,7 @@ function walkFiles(dir) {
2813
2963
  function countResources(root) {
2814
2964
  const resources = {};
2815
2965
  for (const d of RESOURCE_DIRS) {
2816
- const dp = join13(root, d);
2966
+ const dp = join14(root, d);
2817
2967
  if (isDir(dp)) {
2818
2968
  resources[d] = walkFiles(dp).length;
2819
2969
  }
@@ -2824,11 +2974,11 @@ function scanMcpAndImports(text, root) {
2824
2974
  const mcpRefRe = /mcp__[a-zA-Z0-9_]+/g;
2825
2975
  const mcpRefs = new Set(text.match(mcpRefRe) ?? []);
2826
2976
  const imports = /* @__PURE__ */ new Set();
2827
- const scriptsDir = join13(root, "scripts");
2977
+ const scriptsDir = join14(root, "scripts");
2828
2978
  if (isDir(scriptsDir)) {
2829
2979
  for (const f of walkFiles(scriptsDir)) {
2830
2980
  if (!f.endsWith(".py")) continue;
2831
- const ft = readFileSync10(f, "utf-8");
2981
+ const ft = readFileSync11(f, "utf-8");
2832
2982
  for (const m2 of ft.match(mcpRefRe) ?? []) mcpRefs.add(m2);
2833
2983
  const importRe = /^\s*(?:import|from)\s+([a-zA-Z0-9_]+)/gm;
2834
2984
  let m;
@@ -2845,7 +2995,7 @@ function scanMcpAndImports(text, root) {
2845
2995
  function profileSkillDir(inputPath) {
2846
2996
  const { skillMd, root, tmpDir } = resolveSkillMd(inputPath);
2847
2997
  try {
2848
- const text = readFileSync10(skillMd, "utf-8");
2998
+ const text = readFileSync11(skillMd, "utf-8");
2849
2999
  const fm = parseFrontmatter(text);
2850
3000
  const bodyLines = (text.match(/\n/g)?.length ?? 0) + 1;
2851
3001
  const headers = Array.from(text.matchAll(/^#{1,3}\s+(.+)$/gm)).map((m) => m[1]);
@@ -2873,20 +3023,20 @@ function profileSkillDir(inputPath) {
2873
3023
  }
2874
3024
  }
2875
3025
  function profilePlugin(p) {
2876
- const manifestPath = join13(p, ".claude-plugin", "plugin.json");
3026
+ const manifestPath = join14(p, ".claude-plugin", "plugin.json");
2877
3027
  let manifest;
2878
3028
  try {
2879
- manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
3029
+ manifest = JSON.parse(readFileSync11(manifestPath, "utf-8"));
2880
3030
  } catch (err) {
2881
3031
  throw new Error(`could not parse plugin.json under ${p}: ${err.message}`);
2882
3032
  }
2883
- const skillsDir = join13(p, "skills");
3033
+ const skillsDir = join14(p, "skills");
2884
3034
  let skills = [];
2885
3035
  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();
3036
+ skills = readdirSync6(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && existsSync11(join14(skillsDir, d.name, "SKILL.md"))).map((d) => d.name).sort();
2887
3037
  }
2888
- const commandsDir = join13(p, "commands");
2889
- const commands = isDir(commandsDir) ? readdirSync5(commandsDir).filter((f) => f.endsWith(".md")).length : 0;
3038
+ const commandsDir = join14(p, "commands");
3039
+ const commands = isDir(commandsDir) ? readdirSync6(commandsDir).filter((f) => f.endsWith(".md")).length : 0;
2890
3040
  const mc = manifest.mcpServers ?? manifest.mcp_servers ?? {};
2891
3041
  const mcpServers = typeof mc === "object" && mc !== null ? Object.keys(mc).sort() : [];
2892
3042
  return {
@@ -2903,11 +3053,11 @@ function profilePlugin(p) {
2903
3053
  };
2904
3054
  }
2905
3055
  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;
3056
+ const explicitConfig = existsSync11(join14(p, ".mcp.json")) ? join14(p, ".mcp.json") : existsSync11(join14(p, "mcp.json")) ? join14(p, "mcp.json") : void 0;
2907
3057
  if (explicitConfig) {
2908
3058
  let cfg;
2909
3059
  try {
2910
- cfg = JSON.parse(readFileSync10(explicitConfig, "utf-8"));
3060
+ cfg = JSON.parse(readFileSync11(explicitConfig, "utf-8"));
2911
3061
  } catch (err) {
2912
3062
  throw new Error(`could not parse MCP config ${explicitConfig}: ${err.message}`);
2913
3063
  }
@@ -2923,10 +3073,10 @@ function profileMcp(p) {
2923
3073
  capabilities: extractMcpCapabilities(p)
2924
3074
  };
2925
3075
  }
2926
- const pkgPath = join13(p, "package.json");
3076
+ const pkgPath = join14(p, "package.json");
2927
3077
  let pkg2;
2928
3078
  try {
2929
- pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
3079
+ pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
2930
3080
  } catch (err) {
2931
3081
  throw new Error(`could not parse package.json ${pkgPath}: ${err.message}`);
2932
3082
  }
@@ -2943,14 +3093,14 @@ function profileMcp(p) {
2943
3093
  }
2944
3094
 
2945
3095
  // src/gate/mcpOverlap.ts
2946
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
3096
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
2947
3097
  import { resolve as resolve4 } from "path";
2948
3098
  function readTargetServers(targetPath) {
2949
3099
  const path = resolve4(expandHome(targetPath));
2950
3100
  if (!existsSync12(path)) return [];
2951
3101
  let cfg;
2952
3102
  try {
2953
- cfg = JSON.parse(readFileSync11(path, "utf-8"));
3103
+ cfg = JSON.parse(readFileSync12(path, "utf-8"));
2954
3104
  } catch {
2955
3105
  return [];
2956
3106
  }
@@ -3151,10 +3301,10 @@ function resolveMcpCandidatePath(entry) {
3151
3301
  if (entry.packageDir) return entry.packageDir;
3152
3302
  let children = [];
3153
3303
  try {
3154
- children = readdirSync6(entry.quarantinePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3304
+ children = readdirSync7(entry.quarantinePath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3155
3305
  } catch {
3156
3306
  }
3157
- return children.length === 1 ? join14(entry.quarantinePath, children[0]) : entry.quarantinePath;
3307
+ return children.length === 1 ? join15(entry.quarantinePath, children[0]) : entry.quarantinePath;
3158
3308
  }
3159
3309
  async function runGate(candidatePath, skillsRoot, {
3160
3310
  proFeatureLabel = "overlap analysis",
@@ -3186,17 +3336,17 @@ async function runGate(candidatePath, skillsRoot, {
3186
3336
 
3187
3337
  // src/queue.ts
3188
3338
  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";
3339
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
3340
+ import { join as join16 } from "path";
3191
3341
  function getQueuePath() {
3192
- return join15(getHomeDir(), "queue.json");
3342
+ return join16(getHomeDir(), "queue.json");
3193
3343
  }
3194
3344
  function readQueue() {
3195
3345
  const path = getQueuePath();
3196
3346
  if (!existsSync13(path)) {
3197
3347
  return { version: 1, entries: [] };
3198
3348
  }
3199
- const raw = readFileSync12(path, "utf-8");
3349
+ const raw = readFileSync13(path, "utf-8");
3200
3350
  const parsed = JSON.parse(raw);
3201
3351
  return { version: 1, entries: parsed.entries ?? [] };
3202
3352
  }
@@ -3204,7 +3354,7 @@ function writeQueue(queue2) {
3204
3354
  const home = getHomeDir();
3205
3355
  mkdirSync6(home, { recursive: true });
3206
3356
  const finalPath = getQueuePath();
3207
- const tempPath = join15(home, `.queue.json.tmp-${randomUUID3()}`);
3357
+ const tempPath = join16(home, `.queue.json.tmp-${randomUUID3()}`);
3208
3358
  try {
3209
3359
  writeFileSync6(tempPath, JSON.stringify(queue2, null, 2) + "\n", "utf-8");
3210
3360
  renameSync4(tempPath, finalPath);
@@ -3254,15 +3404,15 @@ function generateId(existingIds) {
3254
3404
  // src/handoff.ts
3255
3405
  import { spawnSync } from "child_process";
3256
3406
  import { existsSync as existsSync14 } from "fs";
3257
- import { dirname as dirname7, join as join16 } from "path";
3407
+ import { dirname as dirname7, join as join17 } from "path";
3258
3408
  import { fileURLToPath } from "url";
3259
3409
  var KNOWN_AGENT_BINARIES = ["claude", "codex", "cursor-agent", "windsurf", "opencode", "gemini"];
3260
3410
  var HANDOFF_INSTRUCTION = "Read {prompt} and follow its instructions for the skill installed at {path}";
3261
3411
  function resolvePromptPath(assetName = "ingest-prompt.md") {
3262
3412
  const here = dirname7(fileURLToPath(import.meta.url));
3263
- const builtCandidate = join16(here, assetName);
3413
+ const builtCandidate = join17(here, assetName);
3264
3414
  if (existsSync14(builtCandidate)) return builtCandidate;
3265
- return join16(here, "..", "assets", assetName);
3415
+ return join17(here, "..", "assets", assetName);
3266
3416
  }
3267
3417
  function defaultIsOnPath(bin) {
3268
3418
  const finder = process.platform === "win32" ? "where" : "which";
@@ -3325,7 +3475,7 @@ import { createInterface as createInterface3 } from "readline/promises";
3325
3475
  // src/agents.ts
3326
3476
  import { statSync as statSync8 } from "fs";
3327
3477
  import { homedir as homedir3 } from "os";
3328
- import { join as join17, resolve as resolve5 } from "path";
3478
+ import { join as join18, resolve as resolve5 } from "path";
3329
3479
  function isFile(p) {
3330
3480
  try {
3331
3481
  return statSync8(p).isFile();
@@ -3339,7 +3489,7 @@ function projectPath(root, cwd) {
3339
3489
  function globalPath(root, home) {
3340
3490
  if (!root) return void 0;
3341
3491
  if (root === "~") return home;
3342
- if (root.startsWith("~/")) return join17(home, root.slice(2));
3492
+ if (root.startsWith("~/")) return join18(home, root.slice(2));
3343
3493
  return root;
3344
3494
  }
3345
3495
  function makeAgent(spec) {
@@ -3570,7 +3720,7 @@ var AGENTS = [
3570
3720
 
3571
3721
  // src/commands/audit.ts
3572
3722
  import { existsSync as existsSync15 } from "fs";
3573
- import { join as join18, resolve as resolvePath3 } from "path";
3723
+ import { join as join19, resolve as resolvePath3 } from "path";
3574
3724
  import { createInterface as createInterface2 } from "readline/promises";
3575
3725
 
3576
3726
  // src/decision.ts
@@ -3638,7 +3788,7 @@ async function captureBusinessProfile(rl, existing) {
3638
3788
  function resolveReportPath(reportOption) {
3639
3789
  if (reportOption) return resolvePath3(reportOption);
3640
3790
  const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3641
- return join18(getHomeDir(), "reports", `audit-${iso}.md`);
3791
+ return join19(getHomeDir(), "reports", `audit-${iso}.md`);
3642
3792
  }
3643
3793
  function printTerminalSummary(report, reportPath) {
3644
3794
  const { summary } = report;
@@ -4063,6 +4213,7 @@ async function add(source, options = {}) {
4063
4213
  }
4064
4214
  const resolved = resolve(source, artifact);
4065
4215
  const entry = await quarantine(resolved, config.quarantineDir);
4216
+ const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;
4066
4217
  if (entry.skillDirs.length === 0) {
4067
4218
  console.error(`[skill-forge] no SKILL.md found in installed source: ${source}`);
4068
4219
  reject(entry);
@@ -4088,7 +4239,7 @@ async function add(source, options = {}) {
4088
4239
  const decision = options.yes || options.json ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, "this skill");
4089
4240
  let installedPath = null;
4090
4241
  if (decision === "promote") {
4091
- const result = promote(entry, targetRoot);
4242
+ const result = promote(entry, targetRoot, { expectedFingerprint: gatedFingerprint });
4092
4243
  logOutcome(`[skill-forge] promoted -> ${result.promoted.join(", ")}`, options.json);
4093
4244
  if (proEnabled) {
4094
4245
  try {
@@ -4179,7 +4330,7 @@ async function addMcp(source, config, options) {
4179
4330
  envVars: detail.env ? Object.keys(detail.env) : void 0,
4180
4331
  candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
4181
4332
  };
4182
- const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join19(process.cwd(), ".mcp.json");
4333
+ const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join20(process.cwd(), ".mcp.json");
4183
4334
  const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
4184
4335
  reject(entry);
4185
4336
  logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
@@ -4274,7 +4425,7 @@ async function scan(source, options = {}) {
4274
4425
  }
4275
4426
 
4276
4427
  // src/commands/list.ts
4277
- import { join as join20 } from "path";
4428
+ import { join as join21 } from "path";
4278
4429
  async function list() {
4279
4430
  await maybeOfferInit();
4280
4431
  const config = loadConfig();
@@ -4287,14 +4438,14 @@ async function list() {
4287
4438
  }
4288
4439
  console.log(`[skill-forge] ${ids.length} held in quarantine (${quarantineDir}):`);
4289
4440
  for (const id of ids) {
4290
- const skillDirs = findSkillDirs(join20(quarantineDir, id));
4441
+ const skillDirs = findSkillDirs(join21(quarantineDir, id));
4291
4442
  if (skillDirs.length === 0) {
4292
4443
  console.log(` ${id} (no SKILL.md found)`);
4293
4444
  continue;
4294
4445
  }
4295
4446
  const names = skillDirs.map((dir) => {
4296
4447
  try {
4297
- return readFrontmatter(join20(dir, "SKILL.md")).name;
4448
+ return readFrontmatter(join21(dir, "SKILL.md")).name;
4298
4449
  } catch {
4299
4450
  return dir;
4300
4451
  }
@@ -4307,20 +4458,20 @@ async function list() {
4307
4458
  import { existsSync as existsSync18 } from "fs";
4308
4459
 
4309
4460
  // 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";
4461
+ import { existsSync as existsSync17, realpathSync as realpathSync3 } from "fs";
4462
+ import { basename as basename9, join as join22, resolve as resolvePath4, sep as sep2 } from "path";
4312
4463
  var PRO_FEATURE_LABEL2 = "overlap analysis, the provenance ledger, and the pending-ingestion queue";
4313
4464
  function resolveHeldPath(quarantineDir, id) {
4314
4465
  if (id === "" || id !== basename9(id) || id === "." || id === "..") {
4315
4466
  throw new UserError(`invalid quarantine id "${id}" \u2014 pass a single id exactly as printed by \`skill-forge list\``);
4316
4467
  }
4317
- const candidate = join21(quarantineDir, id);
4468
+ const candidate = join22(quarantineDir, id);
4318
4469
  if (!existsSync17(candidate)) {
4319
4470
  throw new UserError(`no held entry with id "${id}" \u2014 run \`skill-forge list\` to see what is in quarantine`);
4320
4471
  }
4321
- const realRoot = realpathSync2(resolvePath4(quarantineDir));
4322
- const realCandidate = realpathSync2(candidate);
4323
- if (!realCandidate.startsWith(realRoot + sep)) {
4472
+ const realRoot = realpathSync3(resolvePath4(quarantineDir));
4473
+ const realCandidate = realpathSync3(candidate);
4474
+ if (!realCandidate.startsWith(realRoot + sep2)) {
4324
4475
  throw new UserError(`refusing to act on "${id}": it resolves outside the quarantine dir (${realRoot})`);
4325
4476
  }
4326
4477
  return realCandidate;
@@ -4343,7 +4494,7 @@ function resolveTargetRoot(config, target) {
4343
4494
  function heldSkillNames(skillDirs) {
4344
4495
  return skillDirs.map((dir) => {
4345
4496
  try {
4346
- return readFrontmatter(join21(dir, "SKILL.md")).name;
4497
+ return readFrontmatter(join22(dir, "SKILL.md")).name;
4347
4498
  } catch {
4348
4499
  return basename9(dir);
4349
4500
  }
@@ -4351,7 +4502,7 @@ function heldSkillNames(skillDirs) {
4351
4502
  }
4352
4503
  function looksLikeMcpSandbox(quarantinePath) {
4353
4504
  const candidate = resolveMcpCandidatePath({ quarantinePath });
4354
- return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync17(join21(candidate, f)));
4505
+ return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync17(join22(candidate, f)));
4355
4506
  }
4356
4507
  async function promoteHeldMcp(id, entry, config, options) {
4357
4508
  const candidatePath = resolveMcpCandidatePath(entry);
@@ -4385,7 +4536,7 @@ async function promoteHeldMcp(id, entry, config, options) {
4385
4536
  envVars: detail.env ? Object.keys(detail.env) : void 0,
4386
4537
  candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
4387
4538
  };
4388
- const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join21(process.cwd(), ".mcp.json");
4539
+ const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join22(process.cwd(), ".mcp.json");
4389
4540
  const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
4390
4541
  reject(entry);
4391
4542
  logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
@@ -4430,6 +4581,7 @@ async function promoteHeld(id, options = {}) {
4430
4581
  }
4431
4582
  const targetRoot = resolveTargetRoot(config, options.target);
4432
4583
  const candidatePath = entry.skillDirs[0];
4584
+ const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;
4433
4585
  logOutcome(`[skill-forge] re-gating held entry ${id} (${heldSkillNames(entry.skillDirs).join(", ")})`, options.json);
4434
4586
  const { safety, gateResult, proEnabled } = await runGate(candidatePath, targetRoot, {
4435
4587
  proFeatureLabel: PRO_FEATURE_LABEL2,
@@ -4450,7 +4602,7 @@ async function promoteHeld(id, options = {}) {
4450
4602
  logOutcome(`[skill-forge] rejected \u2014 removed ${quarantinePath}`, options.json);
4451
4603
  return;
4452
4604
  }
4453
- const result = promote(entry, targetRoot, { force: options.force });
4605
+ const result = promote(entry, targetRoot, { force: options.force, expectedFingerprint: gatedFingerprint });
4454
4606
  logOutcome(`[skill-forge] promoted ${result.promoted.length} skill dir(s):`, options.json);
4455
4607
  for (const promotedPath of result.promoted) logOutcome(` ${promotedPath}`, options.json);
4456
4608
  if (!proEnabled) return;
@@ -4858,17 +5010,17 @@ async function status() {
4858
5010
 
4859
5011
  // src/evolve.ts
4860
5012
  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";
5013
+ import { createHash as createHash2 } from "crypto";
5014
+ import { cpSync as cpSync3, existsSync as existsSync19, lstatSync as lstatSync3, mkdtempSync as mkdtempSync3, readFileSync as readFileSync14, rmSync as rmSync6 } from "fs";
4863
5015
  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";
5016
+ import { basename as basename10, dirname as dirname9, join as join23, resolve as resolvePath5, sep as sep3 } from "path";
4865
5017
  var SKILLOPT_BIN = "skillopt-sleep";
4866
5018
  var PRO_FEATURE_LABEL3 = "self-evolution via SkillOpt-Sleep (evolve) \u2014 re-gating, provenance, and the pending-ingestion queue";
4867
5019
  async function evolve(targetSkillDir, options = {}, deps = {}) {
4868
5020
  const exec = deps.exec ?? defaultExec;
4869
5021
  const json = options.json;
4870
5022
  const resolvedTargetDir = resolvePath5(targetSkillDir);
4871
- const skillMdPath = join22(resolvedTargetDir, "SKILL.md");
5023
+ const skillMdPath = join23(resolvedTargetDir, "SKILL.md");
4872
5024
  if (!existsSync19(skillMdPath)) {
4873
5025
  throw new UserError(`no SKILL.md found at ${skillMdPath} \u2014 evolve requires an existing, installed skill directory`);
4874
5026
  }
@@ -4927,16 +5079,16 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
4927
5079
  }
4928
5080
  return;
4929
5081
  }
4930
- const stagingRoot = join22(project, ".skillopt-sleep", "staging");
5082
+ const stagingRoot = join23(project, ".skillopt-sleep", "staging");
4931
5083
  const stagingDir = resolvePath5(parsed.staging_dir);
4932
- if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep2)) {
5084
+ if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep3)) {
4933
5085
  throw new UserError(
4934
5086
  `skillopt-sleep reported a staging_dir outside the expected sandbox (${stagingRoot}): ${stagingDir} \u2014 refusing to use it`
4935
5087
  );
4936
5088
  }
4937
- const proposedSkillMd = join22(stagingDir, "proposed_SKILL.md");
4938
- const proposedClaudeMd = join22(stagingDir, "proposed_CLAUDE.md");
4939
- const reportMdPath = join22(stagingDir, "report.md");
5089
+ const proposedSkillMd = join23(stagingDir, "proposed_SKILL.md");
5090
+ const proposedClaudeMd = join23(stagingDir, "proposed_CLAUDE.md");
5091
+ const reportMdPath = join23(stagingDir, "report.md");
4940
5092
  if (!existsSync19(proposedSkillMd)) {
4941
5093
  throw new UserError(`skillopt-sleep reported an accepted proposal but ${proposedSkillMd} is missing`);
4942
5094
  }
@@ -4947,13 +5099,13 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
4947
5099
  assertNotSymlink(proposedClaudeMd, "proposed_CLAUDE.md");
4948
5100
  const scannedSkillHash = sha256File(proposedSkillMd);
4949
5101
  const scannedClaudeHash = sha256File(proposedClaudeMd);
4950
- const tempDir = mkdtempSync3(join22(tmpdir2(), "skill-forge-evolve-"));
5102
+ const tempDir = mkdtempSync3(join23(tmpdir2(), "skill-forge-evolve-"));
4951
5103
  let profile;
4952
5104
  let safety;
4953
5105
  try {
4954
- cpSync3(proposedSkillMd, join22(tempDir, "SKILL.md"));
5106
+ cpSync3(proposedSkillMd, join23(tempDir, "SKILL.md"));
4955
5107
  if (existsSync19(proposedClaudeMd)) {
4956
- cpSync3(proposedClaudeMd, join22(tempDir, "CLAUDE.md"));
5108
+ cpSync3(proposedClaudeMd, join23(tempDir, "CLAUDE.md"));
4957
5109
  }
4958
5110
  profile = asSkillProfile(profileSkill(tempDir), "for the evolved proposal");
4959
5111
  const strictness = loadConfig().strictness;
@@ -5094,10 +5246,10 @@ function validateRunOutput(raw) {
5094
5246
  return { accepted: Boolean(obj.accepted), staging_dir: stagingDirRaw ?? null };
5095
5247
  }
5096
5248
  function readManifest(stagingDir) {
5097
- const manifestPath = join22(stagingDir, "manifest.json");
5249
+ const manifestPath = join23(stagingDir, "manifest.json");
5098
5250
  let raw;
5099
5251
  try {
5100
- raw = readFileSync13(manifestPath, "utf-8");
5252
+ raw = readFileSync14(manifestPath, "utf-8");
5101
5253
  } catch (err) {
5102
5254
  throw new UserError(`could not read staging manifest ${manifestPath}: ${err.message}`);
5103
5255
  }
@@ -5132,11 +5284,11 @@ function validateManifest(raw, manifestPath) {
5132
5284
  }
5133
5285
  function filesIdentical(a, b) {
5134
5286
  if (!existsSync19(a) || !existsSync19(b)) return false;
5135
- return readFileSync13(a).equals(readFileSync13(b));
5287
+ return readFileSync14(a).equals(readFileSync14(b));
5136
5288
  }
5137
5289
  function isAlreadyAdopted(stagingDir, manifest) {
5138
- const proposedSkillMd = join22(stagingDir, "proposed_SKILL.md");
5139
- const proposedClaudeMd = join22(stagingDir, "proposed_CLAUDE.md");
5290
+ const proposedSkillMd = join23(stagingDir, "proposed_SKILL.md");
5291
+ const proposedClaudeMd = join23(stagingDir, "proposed_CLAUDE.md");
5140
5292
  let sawComparison = false;
5141
5293
  if (existsSync19(proposedSkillMd)) {
5142
5294
  sawComparison = true;
@@ -5151,7 +5303,7 @@ function isAlreadyAdopted(stagingDir, manifest) {
5151
5303
  function assertNotSymlink(path, label) {
5152
5304
  let stat;
5153
5305
  try {
5154
- stat = lstatSync2(path);
5306
+ stat = lstatSync3(path);
5155
5307
  } catch {
5156
5308
  return;
5157
5309
  }
@@ -5161,7 +5313,7 @@ function assertNotSymlink(path, label) {
5161
5313
  }
5162
5314
  function sha256File(path) {
5163
5315
  if (!existsSync19(path)) return null;
5164
- return createHash("sha256").update(readFileSync13(path)).digest("hex");
5316
+ return createHash2("sha256").update(readFileSync14(path)).digest("hex");
5165
5317
  }
5166
5318
 
5167
5319
  // src/commands/evolve.ts
@@ -5173,21 +5325,21 @@ async function evolveCommand(targetSkillDir, options = {}) {
5173
5325
  import { resolve as resolvePath7 } from "path";
5174
5326
 
5175
5327
  // 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";
5328
+ import { existsSync as existsSync20, readFileSync as readFileSync15, readdirSync as readdirSync8, realpathSync as realpathSync4, statSync as statSync9 } from "fs";
5329
+ import { basename as basename11, join as join24, resolve as resolvePath6 } from "path";
5178
5330
  var PRO_FEATURE_LABEL4 = "set-level organizer reports";
5179
5331
  var TIERS = /* @__PURE__ */ new Set(["resource", "custom"]);
5180
5332
  var MATURITIES = /* @__PURE__ */ new Set(["seedling", "stable", "deprecated"]);
5181
5333
  function listSubdirs2(dir) {
5182
5334
  let entries;
5183
5335
  try {
5184
- entries = readdirSync7(dir, { withFileTypes: true });
5336
+ entries = readdirSync8(dir, { withFileTypes: true });
5185
5337
  } catch {
5186
5338
  return [];
5187
5339
  }
5188
5340
  const out = [];
5189
5341
  for (const entry of entries) {
5190
- const full = join23(dir, entry.name);
5342
+ const full = join24(dir, entry.name);
5191
5343
  if (entry.isDirectory()) {
5192
5344
  out.push(full);
5193
5345
  continue;
@@ -5205,14 +5357,14 @@ function findSkillDirsUnderRoot2(root) {
5205
5357
  const found = /* @__PURE__ */ new Set();
5206
5358
  const topDirs = listSubdirs2(root);
5207
5359
  for (const dir of topDirs) {
5208
- if (existsSync20(join23(dir, "SKILL.md"))) found.add(dir);
5360
+ if (existsSync20(join24(dir, "SKILL.md"))) found.add(dir);
5209
5361
  }
5210
- for (const dir of listSubdirs2(join23(root, "skills"))) {
5211
- if (existsSync20(join23(dir, "SKILL.md"))) found.add(dir);
5362
+ for (const dir of listSubdirs2(join24(root, "skills"))) {
5363
+ if (existsSync20(join24(dir, "SKILL.md"))) found.add(dir);
5212
5364
  }
5213
5365
  for (const topDir of topDirs) {
5214
- for (const dir of listSubdirs2(join23(topDir, "skills"))) {
5215
- if (existsSync20(join23(dir, "SKILL.md"))) found.add(dir);
5366
+ for (const dir of listSubdirs2(join24(topDir, "skills"))) {
5367
+ if (existsSync20(join24(dir, "SKILL.md"))) found.add(dir);
5216
5368
  }
5217
5369
  }
5218
5370
  return Array.from(found).filter((p) => !p.includes("/.git/"));
@@ -5281,7 +5433,7 @@ function loadUsageSnapshot(snapshotPath) {
5281
5433
  const resolvedPath = resolvePath6(expandHome(snapshotPath));
5282
5434
  let raw;
5283
5435
  try {
5284
- raw = readFileSync14(resolvedPath, "utf-8");
5436
+ raw = readFileSync15(resolvedPath, "utf-8");
5285
5437
  } catch (err) {
5286
5438
  return { usage: /* @__PURE__ */ new Map(), warning: `could not read usage snapshot (${err.message}); proceeding without usage` };
5287
5439
  }
@@ -5380,17 +5532,17 @@ function buildRegistryEntries(candidates, warnings) {
5380
5532
  for (const candidate of candidates) {
5381
5533
  let realPath;
5382
5534
  try {
5383
- realPath = realpathSync3(candidate.dirPath);
5535
+ realPath = realpathSync4(candidate.dirPath);
5384
5536
  } catch (err) {
5385
5537
  warnings.push(`could not resolve skill directory ${candidate.dirPath}: ${err.message}`);
5386
5538
  continue;
5387
5539
  }
5388
5540
  if (seenRealPaths.has(realPath)) continue;
5389
5541
  seenRealPaths.add(realPath);
5390
- const skillMdPath = join23(realPath, "SKILL.md");
5542
+ const skillMdPath = join24(realPath, "SKILL.md");
5391
5543
  let text;
5392
5544
  try {
5393
- text = readFileSync14(skillMdPath, "utf-8");
5545
+ text = readFileSync15(skillMdPath, "utf-8");
5394
5546
  } catch (err) {
5395
5547
  warnings.push(`could not read ${skillMdPath}: ${err.message}`);
5396
5548
  continue;
@@ -5795,8 +5947,8 @@ async function find(query, options = {}, deps = {}) {
5795
5947
  }
5796
5948
 
5797
5949
  // src/commands/watch.ts
5798
- import { realpathSync as realpathSync4 } from "fs";
5799
- import { join as join24, resolve as resolvePath8 } from "path";
5950
+ import { realpathSync as realpathSync5 } from "fs";
5951
+ import { join as join25, resolve as resolvePath8 } from "path";
5800
5952
  var PRO_FEATURE_LABEL5 = "provenance drift watch (watch)";
5801
5953
  function isGitUrl(source) {
5802
5954
  const s = source.trim();
@@ -5846,7 +5998,7 @@ async function runWatch(config, options = {}, deps = {}) {
5846
5998
  let realPath = resolvedRoot;
5847
5999
  if (exists) {
5848
6000
  try {
5849
- realPath = realpathSync4(resolvedRoot);
6001
+ realPath = realpathSync5(resolvedRoot);
5850
6002
  } catch {
5851
6003
  realPath = resolvedRoot;
5852
6004
  }
@@ -5858,7 +6010,7 @@ async function runWatch(config, options = {}, deps = {}) {
5858
6010
  ledger = readProvenanceLedger(resolvedRoot);
5859
6011
  } catch (err) {
5860
6012
  const message = err instanceof Error ? err.message : String(err);
5861
- const ledgerPath = join24(resolvedRoot, "SOURCES.md");
6013
+ const ledgerPath = join25(resolvedRoot, "SOURCES.md");
5862
6014
  roots.push({ root, realPath, exists, ledgerPath, ledgerExists: false });
5863
6015
  warnings.push(`${ledgerPath}: could not read ledger: ${message}`);
5864
6016
  continue;
@@ -5950,7 +6102,7 @@ async function watchCommand(options = {}) {
5950
6102
 
5951
6103
  // src/commands/routine.ts
5952
6104
  import { existsSync as existsSync21, rmSync as rmSync7, statSync as statSync10 } from "fs";
5953
- import { join as join25 } from "path";
6105
+ import { join as join26 } from "path";
5954
6106
  var PRO_FEATURE_LABEL6 = "the scheduled maintenance routine";
5955
6107
  var SEVERITY_ORDER = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
5956
6108
  function parseFailOnFloor(failOn) {
@@ -5971,7 +6123,7 @@ function exceedsFloor(audit2, failOn) {
5971
6123
  }
5972
6124
  function resolveReportPath2(override, generatedAt) {
5973
6125
  if (override) return override;
5974
- return join25(getHomeDir(), "reports", `routine-${generatedAt.replace(/[:.]/g, "-")}.md`);
6126
+ return join26(getHomeDir(), "reports", `routine-${generatedAt.replace(/[:.]/g, "-")}.md`);
5975
6127
  }
5976
6128
  function runHousekeeping(config, options) {
5977
6129
  const actions = [];
@@ -5980,7 +6132,7 @@ function runHousekeeping(config, options) {
5980
6132
  const pruneDays = options.pruneDays ?? 30;
5981
6133
  const cutoff = Date.now() - pruneDays * 864e5;
5982
6134
  for (const id of listQuarantineIds(config.quarantineDir)) {
5983
- const path = join25(config.quarantineDir, id);
6135
+ const path = join26(config.quarantineDir, id);
5984
6136
  let mtime;
5985
6137
  try {
5986
6138
  mtime = statSync10(path).mtimeMs;
@@ -6192,8 +6344,8 @@ async function routineCommand(options = {}) {
6192
6344
  }
6193
6345
 
6194
6346
  // src/commands/ingest.ts
6195
- import { realpathSync as realpathSync5 } from "fs";
6196
- import { dirname as dirname11, isAbsolute, relative, resolve as resolvePath9 } from "path";
6347
+ import { realpathSync as realpathSync6 } from "fs";
6348
+ import { dirname as dirname11, isAbsolute as isAbsolute2, relative as relative2, resolve as resolvePath9 } from "path";
6197
6349
  var PRO_FEATURE_LABEL7 = "the pending-ingestion queue";
6198
6350
  var VALID_STATUSES = ["ingested", "dismissed"];
6199
6351
  function requireProFeature() {
@@ -6234,14 +6386,14 @@ function printPendingList(pending, json) {
6234
6386
  }
6235
6387
  function realpathOrNull(p) {
6236
6388
  try {
6237
- return realpathSync5(p);
6389
+ return realpathSync6(p);
6238
6390
  } catch {
6239
6391
  return null;
6240
6392
  }
6241
6393
  }
6242
6394
  function isUnderRoot(candidate, root) {
6243
- const rel = relative(root, candidate);
6244
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
6395
+ const rel = relative2(root, candidate);
6396
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
6245
6397
  }
6246
6398
  function validatePendingEntries(entries, config) {
6247
6399
  const quarantineRoot = realpathOrNull(resolvePath9(config.quarantineDir));
@@ -6318,14 +6470,14 @@ async function queueClose(id, options = {}) {
6318
6470
  }
6319
6471
 
6320
6472
  // 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";
6473
+ import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
6474
+ 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";
6475
+ import { dirname as dirname13, isAbsolute as isAbsolute4, join as join29, relative as relative4, resolve as resolvePath12, sep as sep4 } from "path";
6324
6476
  import { createInterface as createInterface6 } from "readline/promises";
6325
6477
 
6326
6478
  // 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";
6479
+ import { existsSync as existsSync23, realpathSync as realpathSync8, statSync as statSync12 } from "fs";
6480
+ import { isAbsolute as isAbsolute3, join as join28, relative as relative3, resolve as resolvePath11 } from "path";
6329
6481
 
6330
6482
  // src/refine/models.ts
6331
6483
  import { randomBytes } from "crypto";
@@ -6394,27 +6546,27 @@ function contentSimilarity(a, b) {
6394
6546
  }
6395
6547
 
6396
6548
  // 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";
6549
+ import { createHash as createHash3 } from "crypto";
6550
+ import { existsSync as existsSync22, readFileSync as readFileSync16, statSync as statSync11, realpathSync as realpathSync7 } from "fs";
6551
+ import { join as join27, resolve as resolvePath10 } from "path";
6400
6552
  var SIMILARITY_JOIN_THRESHOLD = 0.7;
6401
6553
  var READY_THRESHOLD = 2;
6402
6554
  function getRefinementsDir() {
6403
- return join26(getHomeDir(), "refinements");
6555
+ return join27(getHomeDir(), "refinements");
6404
6556
  }
6405
6557
  function getRefinementsPath() {
6406
- return join26(getRefinementsDir(), "refinements.json");
6558
+ return join27(getRefinementsDir(), "refinements.json");
6407
6559
  }
6408
6560
  function getPatternsPath() {
6409
- return join26(getRefinementsDir(), "patterns.json");
6561
+ return join27(getRefinementsDir(), "patterns.json");
6410
6562
  }
6411
6563
  function getBackupsDir() {
6412
- return join26(getRefinementsDir(), "backups");
6564
+ return join27(getRefinementsDir(), "backups");
6413
6565
  }
6414
6566
  function readJsonFile(path, label) {
6415
6567
  let raw;
6416
6568
  try {
6417
- raw = readFileSync15(path, "utf-8");
6569
+ raw = readFileSync16(path, "utf-8");
6418
6570
  } catch (err) {
6419
6571
  throw new UserError(`unable to read ${label} at ${path}: ${err.message}`);
6420
6572
  }
@@ -6517,7 +6669,7 @@ function writePatternsStore(store) {
6517
6669
  function resolveProjectRealpath(projectRoot) {
6518
6670
  const resolved = resolvePath10(projectRoot);
6519
6671
  try {
6520
- return realpathSync6(resolved);
6672
+ return realpathSync7(resolved);
6521
6673
  } catch {
6522
6674
  return resolved;
6523
6675
  }
@@ -6652,13 +6804,13 @@ function createBackup(filePaths, deps = {}) {
6652
6804
  if (!existsSync22(path)) {
6653
6805
  return { path, existed: false, sha256: null, mode: null, contentBase64: null };
6654
6806
  }
6655
- const bytes = readFileSync15(path);
6807
+ const bytes = readFileSync16(path);
6656
6808
  const mode = statSync11(path).mode & 511;
6657
- const sha256 = createHash2("sha256").update(bytes).digest("hex");
6809
+ const sha256 = createHash3("sha256").update(bytes).digest("hex");
6658
6810
  return { path, existed: true, sha256, mode, contentBase64: bytes.toString("base64") };
6659
6811
  });
6660
6812
  const manifest = { id, createdAt: now.toISOString(), files };
6661
- writeJsonAtomic(join26(getBackupsDir(), id, "manifest.json"), manifest);
6813
+ writeJsonAtomic(join27(getBackupsDir(), id, "manifest.json"), manifest);
6662
6814
  return manifest;
6663
6815
  }
6664
6816
  function validateBackupManifestShape(value, path) {
@@ -6707,7 +6859,7 @@ function readBackupManifest(id) {
6707
6859
  if (!isValidBackupId(id)) {
6708
6860
  throw new UserError(`invalid backup id "${id}" \u2014 expected the format produced by \`skill-forge refine promote\` (e.g. 20260720T142305Z-a1b2)`);
6709
6861
  }
6710
- const manifestPath = join26(getBackupsDir(), id, "manifest.json");
6862
+ const manifestPath = join27(getBackupsDir(), id, "manifest.json");
6711
6863
  if (!existsSync22(manifestPath)) {
6712
6864
  throw new UserError(`no backup with id: ${id}`);
6713
6865
  }
@@ -6730,20 +6882,20 @@ function assertPlainSkillName(name) {
6730
6882
  }
6731
6883
  function realpathOrNull2(p) {
6732
6884
  try {
6733
- return realpathSync7(p);
6885
+ return realpathSync8(p);
6734
6886
  } catch {
6735
6887
  return null;
6736
6888
  }
6737
6889
  }
6738
6890
  function isUnderRoot2(candidate, root) {
6739
- const rel = relative2(root, candidate);
6740
- return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
6891
+ const rel = relative3(root, candidate);
6892
+ return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
6741
6893
  }
6742
6894
  function resolveScopes(config, cwd, skill, userRootFlag) {
6743
6895
  assertPlainSkillName(skill);
6744
6896
  const resolvedCwd = resolvePath11(cwd);
6745
- const localDir = join27(resolvedCwd, ".claude", "skills", skill);
6746
- const sharedDir = join27(resolvedCwd, "skills", skill);
6897
+ const localDir = join28(resolvedCwd, ".claude", "skills", skill);
6898
+ const sharedDir = join28(resolvedCwd, "skills", skill);
6747
6899
  const local = { scope: "local", dir: localDir, exists: existsSync23(localDir) };
6748
6900
  const shared = { scope: "shared", dir: sharedDir, exists: existsSync23(sharedDir) };
6749
6901
  const user = resolveUserScope(config, resolvedCwd, skill, userRootFlag);
@@ -6752,10 +6904,10 @@ function resolveScopes(config, cwd, skill, userRootFlag) {
6752
6904
  function resolveUserScope(config, resolvedCwd, skill, userRootFlag) {
6753
6905
  if (userRootFlag) {
6754
6906
  const root = resolvePath11(userRootFlag);
6755
- return { resolved: true, dir: join27(root, skill), root: userRootFlag, reason: "flag" };
6907
+ return { resolved: true, dir: join28(root, skill), root: userRootFlag, reason: "flag" };
6756
6908
  }
6757
6909
  const cwdReal = realpathOrNull2(resolvedCwd) ?? resolvedCwd;
6758
- const candidates = config.skillsRoots.map((root) => ({ root, dir: join27(resolveConfiguredRoot2(root), skill) }));
6910
+ const candidates = config.skillsRoots.map((root) => ({ root, dir: join28(resolveConfiguredRoot2(root), skill) }));
6759
6911
  for (const candidate of candidates) {
6760
6912
  const skillReal = realpathOrNull2(candidate.dir);
6761
6913
  if (skillReal) {
@@ -6790,12 +6942,12 @@ function inventoryScopeDir(scope, dir) {
6790
6942
  scope,
6791
6943
  dir,
6792
6944
  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"))
6945
+ skillMd: isFile2(join28(dir, "SKILL.md")),
6946
+ patchMd: isFile2(join28(dir, "SKILL.patch.md")),
6947
+ extendMd: isFile2(join28(dir, "SKILL.extend.md")),
6948
+ configJson: isFile2(join28(dir, "skill-config.json")),
6949
+ hooksDir: isDirectory(join28(dir, "hooks")),
6950
+ scriptsDir: isDirectory(join28(dir, "scripts"))
6799
6951
  };
6800
6952
  }
6801
6953
  function inventoryAllScopes(resolution) {
@@ -7067,15 +7219,15 @@ function resolveConfiguredRoot3(root) {
7067
7219
  return resolvePath12(expandHome(root));
7068
7220
  }
7069
7221
  function isUnderRoot3(candidate, root) {
7070
- const rel = relative3(root, candidate);
7071
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
7222
+ const rel = relative4(root, candidate);
7223
+ return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
7072
7224
  }
7073
7225
  function sanitizeDiffForConsole(diff) {
7074
7226
  return diff.split("\n").map((line) => sanitizeForMarkdown(line)).join("\n");
7075
7227
  }
7076
7228
  function resolveContentOption(options) {
7077
7229
  if (options.content !== void 0) return options.content;
7078
- if (options.contentFile) return readFileSync16(resolvePath12(options.contentFile), "utf-8");
7230
+ if (options.contentFile) return readFileSync17(resolvePath12(options.contentFile), "utf-8");
7079
7231
  return void 0;
7080
7232
  }
7081
7233
  function validateCaptureOptions(options) {
@@ -7201,7 +7353,7 @@ ${rendered}` : rendered;
7201
7353
  function ensureScopeDirSafe(scopeDir, resolvedCwd) {
7202
7354
  let lst;
7203
7355
  try {
7204
- lst = lstatSync3(scopeDir);
7356
+ lst = lstatSync4(scopeDir);
7205
7357
  } catch {
7206
7358
  lst = void 0;
7207
7359
  }
@@ -7213,7 +7365,7 @@ function ensureScopeDirSafe(scopeDir, resolvedCwd) {
7213
7365
  const lexical = resolvePath12(scopeDir);
7214
7366
  let realResolved;
7215
7367
  try {
7216
- realResolved = realpathSync8(scopeDir);
7368
+ realResolved = realpathSync9(scopeDir);
7217
7369
  } catch {
7218
7370
  realResolved = real;
7219
7371
  }
@@ -7227,16 +7379,16 @@ function ensureScopeDirSafe(scopeDir, resolvedCwd) {
7227
7379
  }
7228
7380
  function writeOverrideFile(scopeDir, relPath, content, resolvedCwd) {
7229
7381
  const realScopeDir = ensureScopeDirSafe(scopeDir, resolvedCwd);
7230
- const destPath = join28(scopeDir, relPath);
7382
+ const destPath = join29(scopeDir, relPath);
7231
7383
  const destDir = dirname13(destPath);
7232
7384
  mkdirSync9(destDir, { recursive: true });
7233
- const realDestDir = realpathSync8(destDir);
7385
+ const realDestDir = realpathSync9(destDir);
7234
7386
  if (!isUnderRoot3(realDestDir, realScopeDir)) {
7235
7387
  throw new UserError(`refusing to write ${destPath}: resolves outside scope directory ${scopeDir}`);
7236
7388
  }
7237
7389
  let destLst;
7238
7390
  try {
7239
- destLst = lstatSync3(destPath);
7391
+ destLst = lstatSync4(destPath);
7240
7392
  } catch {
7241
7393
  destLst = void 0;
7242
7394
  }
@@ -7323,12 +7475,12 @@ Write ${preview.destPath}? [y/N] `);
7323
7475
  }
7324
7476
  function renderCaptureArtifactPreview(opts, content, refinementId, generatedAt, scopeDir) {
7325
7477
  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") : "";
7478
+ const probePath = probeRelPath ? join29(scopeDir, probeRelPath) : void 0;
7479
+ const existingContent = probePath && existsSync24(probePath) ? readFileSync17(probePath, "utf-8") : "";
7328
7480
  const rendered = renderCaptureArtifact(opts, content, refinementId, generatedAt, existingContent);
7329
7481
  const relPath = rendered.relPath;
7330
- const destPath = join28(scopeDir, relPath);
7331
- const before = existsSync24(destPath) ? readFileSync16(destPath, "utf-8") : "";
7482
+ const destPath = join29(scopeDir, relPath);
7483
+ const before = existsSync24(destPath) ? readFileSync17(destPath, "utf-8") : "";
7332
7484
  const diff = previewDiff(before, rendered.content, destPath);
7333
7485
  return { relPath, content: rendered.content, destPath, diff };
7334
7486
  }
@@ -7425,16 +7577,16 @@ async function refineWhichCommand(skill, options = {}) {
7425
7577
  }
7426
7578
  function writePostWriteSidecar(backupId, files) {
7427
7579
  const sidecar = { schemaVersion: 1, files };
7428
- const dir = join28(getBackupsDir(), backupId);
7580
+ const dir = join29(getBackupsDir(), backupId);
7429
7581
  mkdirSync9(dir, { recursive: true });
7430
- writeFileSync9(join28(dir, "post.json"), JSON.stringify(sidecar, null, 2) + "\n", { mode: 384 });
7582
+ writeFileSync9(join29(dir, "post.json"), JSON.stringify(sidecar, null, 2) + "\n", { mode: 384 });
7431
7583
  }
7432
7584
  function readPostWriteSidecar(backupId) {
7433
- const path = join28(getBackupsDir(), backupId, "post.json");
7585
+ const path = join29(getBackupsDir(), backupId, "post.json");
7434
7586
  if (!existsSync24(path)) return { hashes: /* @__PURE__ */ new Map(), status: "missing" };
7435
7587
  let raw;
7436
7588
  try {
7437
- raw = readFileSync16(path, "utf-8");
7589
+ raw = readFileSync17(path, "utf-8");
7438
7590
  } catch {
7439
7591
  return { hashes: /* @__PURE__ */ new Map(), status: "unreadable" };
7440
7592
  }
@@ -7479,20 +7631,20 @@ async function refinePromoteCommand(patternId, options = {}) {
7479
7631
  const resolvedUserRoot = resolveConfiguredRoot3(scopes.user.root);
7480
7632
  let realUserRoot;
7481
7633
  try {
7482
- realUserRoot = realpathSync8(resolvedUserRoot);
7634
+ realUserRoot = realpathSync9(resolvedUserRoot);
7483
7635
  } catch {
7484
7636
  realUserRoot = resolvedUserRoot;
7485
7637
  }
7486
7638
  let realSkillDir;
7487
7639
  try {
7488
- realSkillDir = realpathSync8(skillDir);
7640
+ realSkillDir = realpathSync9(skillDir);
7489
7641
  } catch {
7490
7642
  realSkillDir = resolvePath12(skillDir);
7491
7643
  }
7492
7644
  if (!isUnderRoot3(realSkillDir, realUserRoot)) {
7493
7645
  throw new UserError(`refusing to promote: resolved skill directory ${skillDir} is outside the configured user scope root ${scopes.user.root}`);
7494
7646
  }
7495
- const skillMdPath = join28(skillDir, "SKILL.md");
7647
+ const skillMdPath = join29(skillDir, "SKILL.md");
7496
7648
  if (!existsSync24(skillMdPath)) {
7497
7649
  throw new UserError(`user-scope SKILL.md not found at ${skillMdPath} \u2014 refusing to promote against a missing base skill.`);
7498
7650
  }
@@ -7510,7 +7662,7 @@ async function refinePromoteCommand(patternId, options = {}) {
7510
7662
  `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
7663
  );
7512
7664
  }
7513
- const before = readFileSync16(skillMdPath, "utf-8");
7665
+ const before = readFileSync17(skillMdPath, "utf-8");
7514
7666
  const after = applyPatchBlocks(before, [block]);
7515
7667
  const diff = previewDiff(before, after, skillMdPath);
7516
7668
  if (options.dryRun) {
@@ -7542,7 +7694,7 @@ Promote pattern ${patternId} into ${skillMdPath}? [y/N] `);
7542
7694
  throw err;
7543
7695
  }
7544
7696
  try {
7545
- const postSha256 = createHash3("sha256").update(readFileSync16(skillMdPath)).digest("hex");
7697
+ const postSha256 = createHash4("sha256").update(readFileSync17(skillMdPath)).digest("hex");
7546
7698
  writePostWriteSidecar(backup.id, [{ path: skillMdPath, sha256: postSha256 }]);
7547
7699
  markGeneralized(patternId, backup.id);
7548
7700
  const skillsRoot = resolveConfiguredRoot3(scopes.user.root);
@@ -7569,10 +7721,10 @@ Promote pattern ${patternId} into ${skillMdPath}? [y/N] `);
7569
7721
  function assertBackupDirContained(backupId) {
7570
7722
  const backupsDir = getBackupsDir();
7571
7723
  if (!existsSync24(backupsDir)) return;
7572
- const realBackupsDir = realpathSync8(backupsDir);
7573
- const backupDir = join28(backupsDir, backupId);
7724
+ const realBackupsDir = realpathSync9(backupsDir);
7725
+ const backupDir = join29(backupsDir, backupId);
7574
7726
  if (!existsSync24(backupDir)) return;
7575
- const realBackupDir = realpathSync8(backupDir);
7727
+ const realBackupDir = realpathSync9(backupDir);
7576
7728
  if (!isUnderRoot3(realBackupDir, realBackupsDir)) {
7577
7729
  throw new UserError(`refusing rollback: backup id "${backupId}" resolves outside the backups directory`);
7578
7730
  }
@@ -7581,9 +7733,9 @@ function resolveTargetRealPath(path) {
7581
7733
  let lexicalAncestor = resolvePath12(path);
7582
7734
  while (true) {
7583
7735
  try {
7584
- const real = realpathSync8(lexicalAncestor);
7585
- const remainder = relative3(lexicalAncestor, resolvePath12(path));
7586
- return remainder === "" ? real : join28(real, remainder);
7736
+ const real = realpathSync9(lexicalAncestor);
7737
+ const remainder = relative4(lexicalAncestor, resolvePath12(path));
7738
+ return remainder === "" ? real : join29(real, remainder);
7587
7739
  } catch {
7588
7740
  const parent = dirname13(lexicalAncestor);
7589
7741
  if (parent === lexicalAncestor) return resolvePath12(path);
@@ -7598,7 +7750,7 @@ function assertManifestPathsWithinRoots(manifest, config) {
7598
7750
  const roots = config.skillsRoots.map((root) => {
7599
7751
  const resolved = resolveConfiguredRoot3(root);
7600
7752
  try {
7601
- return realpathSync8(resolved);
7753
+ return realpathSync9(resolved);
7602
7754
  } catch {
7603
7755
  return resolved;
7604
7756
  }
@@ -7615,7 +7767,7 @@ function assertNoSymlinkLeaves(manifest) {
7615
7767
  for (const file of manifest.files) {
7616
7768
  let lst;
7617
7769
  try {
7618
- lst = lstatSync3(file.path);
7770
+ lst = lstatSync4(file.path);
7619
7771
  } catch {
7620
7772
  lst = void 0;
7621
7773
  }
@@ -7628,11 +7780,11 @@ function findLexicalRoot(filePath, config) {
7628
7780
  const lexicalTarget = resolvePath12(filePath);
7629
7781
  for (const root of config.skillsRoots) {
7630
7782
  const rootLexical = resolveConfiguredRoot3(root);
7631
- const rel = relative3(rootLexical, lexicalTarget);
7632
- if (rel !== "" && (rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute3(rel))) continue;
7783
+ const rel = relative4(rootLexical, lexicalTarget);
7784
+ if (rel !== "" && (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute4(rel))) continue;
7633
7785
  let rootReal;
7634
7786
  try {
7635
- rootReal = realpathSync8(rootLexical);
7787
+ rootReal = realpathSync9(rootLexical);
7636
7788
  } catch {
7637
7789
  rootReal = rootLexical;
7638
7790
  }
@@ -7642,8 +7794,8 @@ function findLexicalRoot(filePath, config) {
7642
7794
  }
7643
7795
  function walkRealComponents(filePath, config, createMissing) {
7644
7796
  const { rootLexical, rootReal } = findLexicalRoot(filePath, config);
7645
- const relPath = relative3(rootLexical, resolvePath12(filePath));
7646
- const components = relPath.split(sep3).filter((c) => c.length > 0);
7797
+ const relPath = relative4(rootLexical, resolvePath12(filePath));
7798
+ const components = relPath.split(sep4).filter((c) => c.length > 0);
7647
7799
  if (components.length === 0) {
7648
7800
  throw new UserError(`refusing to operate on ${filePath}: resolves to the skills root itself`);
7649
7801
  }
@@ -7652,10 +7804,10 @@ function walkRealComponents(filePath, config, createMissing) {
7652
7804
  let current = rootReal;
7653
7805
  for (const component of dirComponents) {
7654
7806
  if (current === void 0) break;
7655
- const next = join28(current, component);
7807
+ const next = join29(current, component);
7656
7808
  let lst;
7657
7809
  try {
7658
- lst = lstatSync3(next);
7810
+ lst = lstatSync4(next);
7659
7811
  } catch {
7660
7812
  lst = void 0;
7661
7813
  }
@@ -7683,13 +7835,13 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
7683
7835
  if (parentReal === void 0) {
7684
7836
  throw new UserError(`refusing to restore ${filePath}: could not establish a real parent directory`);
7685
7837
  }
7686
- const finalPath = join28(parentReal, leafComponent);
7838
+ const finalPath = join29(parentReal, leafComponent);
7687
7839
  if (!isUnderRoot3(finalPath, rootReal)) {
7688
7840
  throw new UserError(`refusing to restore ${filePath}: resolves outside root ${rootReal}`);
7689
7841
  }
7690
7842
  let leafLst;
7691
7843
  try {
7692
- leafLst = lstatSync3(finalPath);
7844
+ leafLst = lstatSync4(finalPath);
7693
7845
  } catch {
7694
7846
  leafLst = void 0;
7695
7847
  }
@@ -7709,13 +7861,13 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
7709
7861
  function safeDeleteTombstone(filePath, config) {
7710
7862
  const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, false);
7711
7863
  if (parentReal === void 0) return;
7712
- const finalPath = join28(parentReal, leafComponent);
7864
+ const finalPath = join29(parentReal, leafComponent);
7713
7865
  if (!isUnderRoot3(finalPath, rootReal)) {
7714
7866
  throw new UserError(`refusing to delete ${filePath}: resolves outside root ${rootReal}`);
7715
7867
  }
7716
7868
  let leafLst;
7717
7869
  try {
7718
- leafLst = lstatSync3(finalPath);
7870
+ leafLst = lstatSync4(finalPath);
7719
7871
  } catch {
7720
7872
  leafLst = void 0;
7721
7873
  }
@@ -7751,7 +7903,7 @@ async function refineRollbackCommand(backupId, options = {}) {
7751
7903
  for (const file of manifest.files) {
7752
7904
  const expected = sidecar.hashes.get(file.path);
7753
7905
  if (expected === void 0) continue;
7754
- const currentSha256 = existsSync24(file.path) ? createHash3("sha256").update(readFileSync16(file.path)).digest("hex") : null;
7906
+ const currentSha256 = existsSync24(file.path) ? createHash4("sha256").update(readFileSync17(file.path)).digest("hex") : null;
7755
7907
  if (currentSha256 !== expected) mismatches.push(file.path);
7756
7908
  }
7757
7909
  if (mismatches.length > 0 && !options.force) {