@rhize/skill-forge 0.20.0 → 0.21.0

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
@@ -659,7 +659,7 @@ import { Command } from "commander";
659
659
  import { createRequire } from "module";
660
660
 
661
661
  // src/commands/add.ts
662
- import { join as join26 } from "path";
662
+ import { join as join27 } from "path";
663
663
 
664
664
  // src/install/resolve.ts
665
665
  import { existsSync as existsSync3 } from "fs";
@@ -1515,10 +1515,68 @@ function which(cmd) {
1515
1515
  }
1516
1516
  return void 0;
1517
1517
  }
1518
- function normalizeSeverity(sev) {
1519
- const s = (sev ?? "").toUpperCase();
1520
- if (s === "LOW" || s === "MEDIUM" || s === "HIGH" || s === "CRITICAL") return s;
1521
- return "MEDIUM";
1518
+ function scannerUnavailable(targetPath) {
1519
+ return {
1520
+ severity: "HIGH",
1521
+ rule: "skillspector-unavailable",
1522
+ file: targetPath,
1523
+ detail: "Installed SkillSpector failed or returned an unsupported/incomplete report; external safety evidence is unavailable."
1524
+ };
1525
+ }
1526
+ function reportRecord(value) {
1527
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid report record");
1528
+ return value;
1529
+ }
1530
+ function reportSeverity(value) {
1531
+ if (typeof value !== "string") throw new Error("Missing severity");
1532
+ const severity = value.toUpperCase();
1533
+ if (severity === "LOW" || severity === "MEDIUM" || severity === "HIGH" || severity === "CRITICAL") return severity;
1534
+ throw new Error("Unknown severity");
1535
+ }
1536
+ function parseSkillSpectorReport(value, targetPath) {
1537
+ try {
1538
+ const report = reportRecord(value);
1539
+ const modern = "issues" in report;
1540
+ const raw = modern ? report.issues : report.filtered_findings ?? report.findings;
1541
+ if (!Array.isArray(raw)) throw new Error("Missing findings");
1542
+ const findings = raw.map((entry) => {
1543
+ const issue = reportRecord(entry);
1544
+ const rule = modern ? issue.id : issue.rule_id;
1545
+ const detail = modern ? issue.finding ?? issue.explanation : issue.message;
1546
+ if (typeof rule !== "string" || !rule || typeof detail !== "string") throw new Error("Invalid finding");
1547
+ const location = modern ? reportRecord(issue.location) : void 0;
1548
+ if (location && typeof location.file !== "string") throw new Error("Invalid location");
1549
+ const line = location?.start_line;
1550
+ return {
1551
+ severity: reportSeverity(issue.severity),
1552
+ rule,
1553
+ file: location?.file ?? targetPath,
1554
+ detail: typeof line === "number" && Number.isInteger(line) && line > 0 ? `${line}: ${detail}` : detail
1555
+ };
1556
+ });
1557
+ const assessment = modern ? reportRecord(report.risk_assessment) : void 0;
1558
+ const summary = assessment?.severity ?? report.risk_severity;
1559
+ if (modern || summary !== void 0) {
1560
+ const severity = reportSeverity(summary);
1561
+ if (severity !== "LOW") findings.push({
1562
+ severity,
1563
+ rule: "skillspector-summary",
1564
+ file: targetPath,
1565
+ detail: `SkillSpector aggregate risk: ${severity}.`
1566
+ });
1567
+ }
1568
+ if (modern) {
1569
+ const completeness = reportRecord(report.analysis_completeness);
1570
+ if (report.execution_successful !== true || completeness.is_complete !== true || completeness.execution_successful === false) {
1571
+ findings.push(scannerUnavailable(targetPath));
1572
+ }
1573
+ } else if (report.execution_successful === false) {
1574
+ findings.push(scannerUnavailable(targetPath));
1575
+ }
1576
+ return findings;
1577
+ } catch {
1578
+ return [scannerUnavailable(targetPath)];
1579
+ }
1522
1580
  }
1523
1581
  async function scanWithSkillSpector(targetPath) {
1524
1582
  const bin = which("skillspector");
@@ -1528,16 +1586,15 @@ async function scanWithSkillSpector(targetPath) {
1528
1586
  timeout: 6e4,
1529
1587
  maxBuffer: 10 * 1024 * 1024
1530
1588
  });
1531
- const parsed = JSON.parse(stdout);
1532
- const raw = parsed.filtered_findings ?? parsed.findings ?? [];
1533
- return raw.map((f) => ({
1534
- severity: normalizeSeverity(f.severity),
1535
- rule: f.rule_id ?? "skillspector",
1536
- file: targetPath,
1537
- detail: f.message ?? ""
1538
- }));
1539
- } catch {
1540
- return [];
1589
+ return parseSkillSpectorReport(JSON.parse(stdout), targetPath);
1590
+ } catch (error) {
1591
+ const failed = scannerUnavailable(targetPath);
1592
+ try {
1593
+ const stdout = error.stdout;
1594
+ return stdout ? [...parseSkillSpectorReport(JSON.parse(stdout), targetPath), failed] : [failed];
1595
+ } catch {
1596
+ return [failed];
1597
+ }
1541
1598
  }
1542
1599
  }
1543
1600
  async function scanSafety(targetPath, strictness = DEFAULT_STRICTNESS, capturedFiles) {
@@ -4612,7 +4669,7 @@ function runHandoff(config, installedPath, options = {}) {
4612
4669
  // src/commands/init.ts
4613
4670
  import { existsSync as existsSync19 } from "fs";
4614
4671
  import { homedir as homedir5 } from "os";
4615
- import { join as join25 } from "path";
4672
+ import { join as join26 } from "path";
4616
4673
  import { createInterface as createInterface3 } from "readline/promises";
4617
4674
 
4618
4675
  // src/agents.ts
@@ -5036,7 +5093,7 @@ function readPluginInventory(path) {
5036
5093
 
5037
5094
  // src/commands/audit.ts
5038
5095
  import { existsSync as existsSync18 } from "fs";
5039
- import { join as join24, resolve as resolvePath5 } from "path";
5096
+ import { join as join25, resolve as resolvePath5 } from "path";
5040
5097
  import { createInterface as createInterface2 } from "readline/promises";
5041
5098
 
5042
5099
  // src/decision.ts
@@ -5402,6 +5459,129 @@ function loadUsageSnapshot(pathOrDir, opts = {}) {
5402
5459
  return toSnapshot(parsed, resolved);
5403
5460
  }
5404
5461
 
5462
+ // src/typedFindingTriage.ts
5463
+ import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
5464
+ import { closeSync as closeSync3, constants as constants2, lstatSync as lstatSync4, mkdirSync as mkdirSync7, openSync as openSync3, writeFileSync as writeFileSync7 } from "fs";
5465
+ import { join as join24 } from "path";
5466
+ import { performance } from "perf_hooks";
5467
+ function digest(value) {
5468
+ return createHash3("sha256").update(JSON.stringify(value)).digest("hex");
5469
+ }
5470
+ function localEndpoint(baseUrl) {
5471
+ const url = new URL(baseUrl);
5472
+ if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
5473
+ throw new Error("local_endpoint_required");
5474
+ }
5475
+ return new URL("/v1/systemone", url);
5476
+ }
5477
+ async function rankFindingMetadata(findings, baseUrl = "http://127.0.0.1:8000", fetchImpl = fetch) {
5478
+ const metadata = findings.map((finding2) => ({
5479
+ fingerprint: finding2.fingerprint ?? "",
5480
+ severity: finding2.severity,
5481
+ rule: finding2.rule,
5482
+ classification: finding2.classification ?? "none"
5483
+ }));
5484
+ const sourceSha256 = digest(metadata);
5485
+ const candidates = metadata.filter((row) => /^[0-9a-f]{64}$/.test(row.fingerprint) && /^[a-z0-9-]{1,64}$/.test(row.rule)).slice(0, 8);
5486
+ const base = {
5487
+ schema: "skill-forge-typed-triage-v1",
5488
+ sourceSha256,
5489
+ evaluated: candidates.length,
5490
+ deferred: findings.length - candidates.length,
5491
+ gateChanged: false
5492
+ };
5493
+ if (!candidates.length) return { ...base, status: "not_applicable", variant: "A_incumbent" };
5494
+ const questions = Object.fromEntries(candidates.map((row, index) => [
5495
+ `c${index}`,
5496
+ { type: "noul", instructions: `Would reviewing finding ${row.fingerprint.slice(0, 16)} first improve triage? Do not downgrade or accept it.` }
5497
+ ]));
5498
+ const request = { model: "typed-decisions", state: { findings: candidates.map((row) => ({
5499
+ id: row.fingerprint.slice(0, 16),
5500
+ severity: row.severity,
5501
+ rule: row.rule,
5502
+ classification: row.classification
5503
+ })) }, questions };
5504
+ const controller = new AbortController();
5505
+ const timeout = setTimeout(() => controller.abort(), 8e3);
5506
+ const start = performance.now();
5507
+ try {
5508
+ const response = await fetchImpl(localEndpoint(baseUrl), {
5509
+ method: "POST",
5510
+ headers: { "Content-Type": "application/json" },
5511
+ body: JSON.stringify(request),
5512
+ signal: controller.signal
5513
+ });
5514
+ if (!response.ok) throw new Error("provider_http_error");
5515
+ if (!response.body) throw new Error("empty_response");
5516
+ const reader = response.body.getReader();
5517
+ const decoder = new TextDecoder();
5518
+ let raw = "";
5519
+ let total = 0;
5520
+ for (; ; ) {
5521
+ const chunk = await reader.read();
5522
+ if (chunk.done) break;
5523
+ total += chunk.value.byteLength;
5524
+ if (total > 262144) {
5525
+ await reader.cancel();
5526
+ throw new Error("oversized_response");
5527
+ }
5528
+ raw += decoder.decode(chunk.value, { stream: true });
5529
+ }
5530
+ raw += decoder.decode();
5531
+ const data = JSON.parse(raw);
5532
+ const routing = data.routing;
5533
+ const answers = data.answers;
5534
+ const usage = data.usage;
5535
+ if (routing?.model !== request.model || !answers || Object.keys(answers).sort().join() !== Object.keys(questions).sort().join()) {
5536
+ throw new Error("route_or_answer_mismatch");
5537
+ }
5538
+ if (!usage || !Number.isSafeInteger(usage.input_tokens) || !Number.isSafeInteger(usage.output_tokens) || Number(usage.input_tokens) < 0 || Number(usage.output_tokens) < 0) throw new Error("invalid_usage");
5539
+ const ranking = candidates.map((row, index) => {
5540
+ const answer = answers[`c${index}`];
5541
+ const score4 = answer?.type === "noul" ? answer.noul : void 0;
5542
+ if (typeof score4 !== "number" || !Number.isFinite(score4) || score4 < 0 || score4 > 1) throw new Error("invalid_score");
5543
+ return { fingerprint: row.fingerprint, severity: row.severity, score: score4 };
5544
+ }).sort((a, b) => b.score - a.score || a.fingerprint.localeCompare(b.fingerprint));
5545
+ return {
5546
+ ...base,
5547
+ status: "shadow",
5548
+ variant: "B_local_laya",
5549
+ ranking,
5550
+ routedModel: String(routing.model),
5551
+ latencyMs: Math.round((performance.now() - start) * 1e3) / 1e3,
5552
+ usage: { input_tokens: Number(usage.input_tokens), output_tokens: Number(usage.output_tokens) }
5553
+ };
5554
+ } catch (error) {
5555
+ const known = /* @__PURE__ */ new Set([
5556
+ "local_endpoint_required",
5557
+ "provider_http_error",
5558
+ "empty_response",
5559
+ "oversized_response",
5560
+ "route_or_answer_mismatch",
5561
+ "invalid_usage",
5562
+ "invalid_score"
5563
+ ]);
5564
+ const reasonCode = error instanceof Error && known.has(error.message) ? error.message : "provider_unavailable";
5565
+ return { ...base, status: "unavailable", variant: "A_incumbent", reasonCode };
5566
+ } finally {
5567
+ clearTimeout(timeout);
5568
+ }
5569
+ }
5570
+ function writeTypedTriageReceipt(home, result) {
5571
+ const dir = join24(home, "typed-finding-triage");
5572
+ mkdirSync7(dir, { recursive: true, mode: 448 });
5573
+ if (lstatSync4(dir).isSymbolicLink()) throw new Error("receipt_root_symlink");
5574
+ const data = JSON.stringify(result) + "\n";
5575
+ const path = join24(dir, `${randomUUID4()}.json`);
5576
+ const fd = openSync3(path, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
5577
+ try {
5578
+ writeFileSync7(fd, data);
5579
+ } finally {
5580
+ closeSync3(fd);
5581
+ }
5582
+ return createHash3("sha256").update(data).digest("hex");
5583
+ }
5584
+
5405
5585
  // src/commands/audit.ts
5406
5586
  var CURATION_PROMPT_ASSET = "curation-prompt.md";
5407
5587
  function resolvePluginAndUsageContext(config, options, deps = {}) {
@@ -5458,7 +5638,7 @@ async function captureBusinessProfile(rl, existing) {
5458
5638
  function resolveReportPath(reportOption) {
5459
5639
  if (reportOption) return resolvePath5(reportOption);
5460
5640
  const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5461
- return join24(getHomeDir(), "reports", `audit-${iso}.md`);
5641
+ return join25(getHomeDir(), "reports", `audit-${iso}.md`);
5462
5642
  }
5463
5643
  function summarizePluginRecommendations(plugins2) {
5464
5644
  const counts = { keep: 0, review: 0, unobserved: 0, unknown: 0 };
@@ -5546,6 +5726,18 @@ async function auditCommand(options = {}, deps = {}) {
5546
5726
  const extraNotices = [...storeNotices, ...pluginUsageNotices];
5547
5727
  if (extraNotices.length > 0) report.notices = [...report.notices, ...extraNotices];
5548
5728
  if (options.compare) report.comparison = loadAuditComparison(report, options.compare);
5729
+ if (options.layaShadow) {
5730
+ const triage = await rankFindingMetadata(report.hygiene, options.layaBaseUrl);
5731
+ try {
5732
+ triage.receiptSha256 = writeTypedTriageReceipt(getHomeDir(), triage);
5733
+ } catch {
5734
+ triage.status = "unavailable";
5735
+ triage.variant = "A_incumbent";
5736
+ triage.reasonCode = "receipt_unavailable";
5737
+ delete triage.ranking;
5738
+ }
5739
+ report.typedFindingTriage = triage;
5740
+ }
5549
5741
  const reportPath = resolveReportPath(options.report);
5550
5742
  writeNewFile(reportPath, renderAuditMarkdown(report), "--report");
5551
5743
  try {
@@ -5874,7 +6066,7 @@ async function init(options = {}, deps = {}) {
5874
6066
  "[skill-forge] No detected agent CLI was found on PATH \u2014 keeping every detected target selected."
5875
6067
  );
5876
6068
  }
5877
- const installedPluginsPath = options.installedPluginsPath ?? join25(home, ".claude", "plugins", "installed_plugins.json");
6069
+ const installedPluginsPath = options.installedPluginsPath ?? join26(home, ".claude", "plugins", "installed_plugins.json");
5878
6070
  const claudePluginsRegistryExists = existsSync19(installedPluginsPath);
5879
6071
  if (options.defaults) {
5880
6072
  const selectedRoots = narrow ? rootGroups.filter(hasInstalledMember) : rootGroups;
@@ -6085,7 +6277,7 @@ async function addMcp(source, config, options) {
6085
6277
  envVars: detail.env ? Object.keys(detail.env) : void 0,
6086
6278
  candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
6087
6279
  };
6088
- const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join26(process.cwd(), ".mcp.json");
6280
+ const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join27(process.cwd(), ".mcp.json");
6089
6281
  const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
6090
6282
  reject(entry);
6091
6283
  logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
@@ -6181,7 +6373,7 @@ async function scan(source, options = {}) {
6181
6373
  }
6182
6374
 
6183
6375
  // src/commands/list.ts
6184
- import { join as join27 } from "path";
6376
+ import { join as join28 } from "path";
6185
6377
  async function list() {
6186
6378
  await maybeOfferInit();
6187
6379
  const config = loadConfig();
@@ -6194,14 +6386,14 @@ async function list() {
6194
6386
  }
6195
6387
  console.log(`[skill-forge] ${ids.length} held in quarantine (${quarantineDir}):`);
6196
6388
  for (const id of ids) {
6197
- const skillDirs = findSkillDirs(join27(quarantineDir, id));
6389
+ const skillDirs = findSkillDirs(join28(quarantineDir, id));
6198
6390
  if (skillDirs.length === 0) {
6199
6391
  console.log(` ${id} (no SKILL.md found)`);
6200
6392
  continue;
6201
6393
  }
6202
6394
  const names = skillDirs.map((dir) => {
6203
6395
  try {
6204
- return readFrontmatter(join27(dir, "SKILL.md")).name;
6396
+ return readFrontmatter(join28(dir, "SKILL.md")).name;
6205
6397
  } catch {
6206
6398
  return dir;
6207
6399
  }
@@ -6215,13 +6407,13 @@ import { existsSync as existsSync21 } from "fs";
6215
6407
 
6216
6408
  // src/commands/held.ts
6217
6409
  import { existsSync as existsSync20, realpathSync as realpathSync6 } from "fs";
6218
- import { basename as basename9, join as join28, resolve as resolvePath6, sep as sep7 } from "path";
6410
+ import { basename as basename9, join as join29, resolve as resolvePath6, sep as sep7 } from "path";
6219
6411
  var PRO_FEATURE_LABEL2 = "overlap analysis, the provenance ledger, and the pending-ingestion queue";
6220
6412
  function resolveHeldPath(quarantineDir, id) {
6221
6413
  if (id === "" || id !== basename9(id) || id === "." || id === "..") {
6222
6414
  throw new UserError(`invalid quarantine id "${id}" \u2014 pass a single id exactly as printed by \`skill-forge list\``);
6223
6415
  }
6224
- const candidate = join28(quarantineDir, id);
6416
+ const candidate = join29(quarantineDir, id);
6225
6417
  if (!existsSync20(candidate)) {
6226
6418
  throw new UserError(`no held entry with id "${id}" \u2014 run \`skill-forge list\` to see what is in quarantine`);
6227
6419
  }
@@ -6250,7 +6442,7 @@ function resolveTargetRoot(config, target) {
6250
6442
  function heldSkillNames(skillDirs) {
6251
6443
  return skillDirs.map((dir) => {
6252
6444
  try {
6253
- return readFrontmatter(join28(dir, "SKILL.md")).name;
6445
+ return readFrontmatter(join29(dir, "SKILL.md")).name;
6254
6446
  } catch {
6255
6447
  return basename9(dir);
6256
6448
  }
@@ -6258,7 +6450,7 @@ function heldSkillNames(skillDirs) {
6258
6450
  }
6259
6451
  function looksLikeMcpSandbox(quarantinePath) {
6260
6452
  const candidate = resolveMcpCandidatePath({ quarantinePath });
6261
- return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync20(join28(candidate, f)));
6453
+ return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync20(join29(candidate, f)));
6262
6454
  }
6263
6455
  async function promoteHeldMcp(id, entry, config, options) {
6264
6456
  const candidatePath = resolveMcpCandidatePath(entry);
@@ -6292,7 +6484,7 @@ async function promoteHeldMcp(id, entry, config, options) {
6292
6484
  envVars: detail.env ? Object.keys(detail.env) : void 0,
6293
6485
  candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
6294
6486
  };
6295
- const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join28(process.cwd(), ".mcp.json");
6487
+ const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join29(process.cwd(), ".mcp.json");
6296
6488
  const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
6297
6489
  reject(entry);
6298
6490
  logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
@@ -6770,17 +6962,17 @@ async function status() {
6770
6962
 
6771
6963
  // src/evolve.ts
6772
6964
  import { createInterface as createInterface5 } from "readline/promises";
6773
- import { createHash as createHash3 } from "crypto";
6774
- import { cpSync as cpSync3, existsSync as existsSync22, lstatSync as lstatSync4, mkdtempSync as mkdtempSync3, readFileSync as readFileSync21, rmSync as rmSync6 } from "fs";
6965
+ import { createHash as createHash4 } from "crypto";
6966
+ import { cpSync as cpSync3, existsSync as existsSync22, lstatSync as lstatSync5, mkdtempSync as mkdtempSync3, readFileSync as readFileSync21, rmSync as rmSync6 } from "fs";
6775
6967
  import { tmpdir as tmpdir2 } from "os";
6776
- import { basename as basename10, dirname as dirname9, join as join29, resolve as resolvePath7, sep as sep8 } from "path";
6968
+ import { basename as basename10, dirname as dirname9, join as join30, resolve as resolvePath7, sep as sep8 } from "path";
6777
6969
  var SKILLOPT_BIN = "skillopt-sleep";
6778
6970
  var PRO_FEATURE_LABEL3 = "self-evolution via SkillOpt-Sleep (evolve) \u2014 re-gating, provenance, and the pending-ingestion queue";
6779
6971
  async function evolve(targetSkillDir, options = {}, deps = {}) {
6780
6972
  const exec = deps.exec ?? defaultExec;
6781
6973
  const json = options.json;
6782
6974
  const resolvedTargetDir = resolvePath7(targetSkillDir);
6783
- const skillMdPath = join29(resolvedTargetDir, "SKILL.md");
6975
+ const skillMdPath = join30(resolvedTargetDir, "SKILL.md");
6784
6976
  if (!existsSync22(skillMdPath)) {
6785
6977
  throw new UserError(`no SKILL.md found at ${skillMdPath} \u2014 evolve requires an existing, installed skill directory`);
6786
6978
  }
@@ -6839,16 +7031,16 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
6839
7031
  }
6840
7032
  return;
6841
7033
  }
6842
- const stagingRoot = join29(project, ".skillopt-sleep", "staging");
7034
+ const stagingRoot = join30(project, ".skillopt-sleep", "staging");
6843
7035
  const stagingDir = resolvePath7(parsed.staging_dir);
6844
7036
  if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep8)) {
6845
7037
  throw new UserError(
6846
7038
  `skillopt-sleep reported a staging_dir outside the expected sandbox (${stagingRoot}): ${stagingDir} \u2014 refusing to use it`
6847
7039
  );
6848
7040
  }
6849
- const proposedSkillMd = join29(stagingDir, "proposed_SKILL.md");
6850
- const proposedClaudeMd = join29(stagingDir, "proposed_CLAUDE.md");
6851
- const reportMdPath = join29(stagingDir, "report.md");
7041
+ const proposedSkillMd = join30(stagingDir, "proposed_SKILL.md");
7042
+ const proposedClaudeMd = join30(stagingDir, "proposed_CLAUDE.md");
7043
+ const reportMdPath = join30(stagingDir, "report.md");
6852
7044
  if (!existsSync22(proposedSkillMd)) {
6853
7045
  throw new UserError(`skillopt-sleep reported an accepted proposal but ${proposedSkillMd} is missing`);
6854
7046
  }
@@ -6859,13 +7051,13 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
6859
7051
  assertNotSymlink(proposedClaudeMd, "proposed_CLAUDE.md");
6860
7052
  const scannedSkillHash = sha256File(proposedSkillMd);
6861
7053
  const scannedClaudeHash = sha256File(proposedClaudeMd);
6862
- const tempDir = mkdtempSync3(join29(tmpdir2(), "skill-forge-evolve-"));
7054
+ const tempDir = mkdtempSync3(join30(tmpdir2(), "skill-forge-evolve-"));
6863
7055
  let profile;
6864
7056
  let safety;
6865
7057
  try {
6866
- cpSync3(proposedSkillMd, join29(tempDir, "SKILL.md"));
7058
+ cpSync3(proposedSkillMd, join30(tempDir, "SKILL.md"));
6867
7059
  if (existsSync22(proposedClaudeMd)) {
6868
- cpSync3(proposedClaudeMd, join29(tempDir, "CLAUDE.md"));
7060
+ cpSync3(proposedClaudeMd, join30(tempDir, "CLAUDE.md"));
6869
7061
  }
6870
7062
  profile = asSkillProfile(profileSkill(tempDir), "for the evolved proposal");
6871
7063
  const strictness = loadConfig().strictness;
@@ -7006,7 +7198,7 @@ function validateRunOutput(raw) {
7006
7198
  return { accepted: Boolean(obj.accepted), staging_dir: stagingDirRaw ?? null };
7007
7199
  }
7008
7200
  function readManifest(stagingDir) {
7009
- const manifestPath = join29(stagingDir, "manifest.json");
7201
+ const manifestPath = join30(stagingDir, "manifest.json");
7010
7202
  let raw;
7011
7203
  try {
7012
7204
  raw = readFileSync21(manifestPath, "utf-8");
@@ -7047,8 +7239,8 @@ function filesIdentical(a, b) {
7047
7239
  return readFileSync21(a).equals(readFileSync21(b));
7048
7240
  }
7049
7241
  function isAlreadyAdopted(stagingDir, manifest) {
7050
- const proposedSkillMd = join29(stagingDir, "proposed_SKILL.md");
7051
- const proposedClaudeMd = join29(stagingDir, "proposed_CLAUDE.md");
7242
+ const proposedSkillMd = join30(stagingDir, "proposed_SKILL.md");
7243
+ const proposedClaudeMd = join30(stagingDir, "proposed_CLAUDE.md");
7052
7244
  let sawComparison = false;
7053
7245
  if (existsSync22(proposedSkillMd)) {
7054
7246
  sawComparison = true;
@@ -7063,7 +7255,7 @@ function isAlreadyAdopted(stagingDir, manifest) {
7063
7255
  function assertNotSymlink(path, label2) {
7064
7256
  let stat;
7065
7257
  try {
7066
- stat = lstatSync4(path);
7258
+ stat = lstatSync5(path);
7067
7259
  } catch {
7068
7260
  return;
7069
7261
  }
@@ -7073,7 +7265,7 @@ function assertNotSymlink(path, label2) {
7073
7265
  }
7074
7266
  function sha256File(path) {
7075
7267
  if (!existsSync22(path)) return null;
7076
- return createHash3("sha256").update(readFileSync21(path)).digest("hex");
7268
+ return createHash4("sha256").update(readFileSync21(path)).digest("hex");
7077
7269
  }
7078
7270
 
7079
7271
  // src/commands/evolve.ts
@@ -7192,7 +7384,7 @@ import { resolve as resolvePath9 } from "path";
7192
7384
 
7193
7385
  // src/organize.ts
7194
7386
  import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync10, realpathSync as realpathSync7, statSync as statSync10 } from "fs";
7195
- import { basename as basename11, join as join30, resolve as resolvePath8 } from "path";
7387
+ import { basename as basename11, join as join31, resolve as resolvePath8 } from "path";
7196
7388
  var PRO_FEATURE_LABEL4 = "set-level organizer reports";
7197
7389
  var TIERS = /* @__PURE__ */ new Set(["resource", "custom"]);
7198
7390
  var MATURITIES = /* @__PURE__ */ new Set(["seedling", "stable", "deprecated", "experimental"]);
@@ -7205,7 +7397,7 @@ function listSubdirs2(dir) {
7205
7397
  }
7206
7398
  const out = [];
7207
7399
  for (const entry of entries) {
7208
- const full = join30(dir, entry.name);
7400
+ const full = join31(dir, entry.name);
7209
7401
  if (entry.isDirectory()) {
7210
7402
  out.push(full);
7211
7403
  continue;
@@ -7223,14 +7415,14 @@ function findSkillDirsUnderRoot2(root) {
7223
7415
  const found = /* @__PURE__ */ new Set();
7224
7416
  const topDirs = listSubdirs2(root);
7225
7417
  for (const dir of topDirs) {
7226
- if (existsSync24(join30(dir, "SKILL.md"))) found.add(dir);
7418
+ if (existsSync24(join31(dir, "SKILL.md"))) found.add(dir);
7227
7419
  }
7228
- for (const dir of listSubdirs2(join30(root, "skills"))) {
7229
- if (existsSync24(join30(dir, "SKILL.md"))) found.add(dir);
7420
+ for (const dir of listSubdirs2(join31(root, "skills"))) {
7421
+ if (existsSync24(join31(dir, "SKILL.md"))) found.add(dir);
7230
7422
  }
7231
7423
  for (const topDir of topDirs) {
7232
- for (const dir of listSubdirs2(join30(topDir, "skills"))) {
7233
- if (existsSync24(join30(dir, "SKILL.md"))) found.add(dir);
7424
+ for (const dir of listSubdirs2(join31(topDir, "skills"))) {
7425
+ if (existsSync24(join31(dir, "SKILL.md"))) found.add(dir);
7234
7426
  }
7235
7427
  }
7236
7428
  return Array.from(found).filter((p) => !p.includes("/.git/"));
@@ -7433,7 +7625,7 @@ function buildRegistryEntries(candidates, warnings) {
7433
7625
  }
7434
7626
  if (seenRealPaths.has(realPath)) continue;
7435
7627
  seenRealPaths.add(realPath);
7436
- const skillMdPath = join30(realPath, "SKILL.md");
7628
+ const skillMdPath = join31(realPath, "SKILL.md");
7437
7629
  let text;
7438
7630
  try {
7439
7631
  text = readFileSync22(skillMdPath, "utf-8");
@@ -7843,14 +8035,14 @@ async function find(query, options = {}, deps = {}) {
7843
8035
 
7844
8036
  // src/commands/watch.ts
7845
8037
  import { realpathSync as realpathSync8 } from "fs";
7846
- import { join as join31, resolve as resolvePath10 } from "path";
8038
+ import { join as join32, resolve as resolvePath10 } from "path";
7847
8039
 
7848
8040
  // src/gate/skillMapDrift.ts
7849
- import { createHash as createHash4 } from "crypto";
8041
+ import { createHash as createHash5 } from "crypto";
7850
8042
  import { readFileSync as readFileSync23, existsSync as existsSync25 } from "fs";
7851
8043
  import { dirname as dirname10, isAbsolute as isAbsolute6, resolve as resolve8 } from "path";
7852
8044
  function sha256(text) {
7853
- return createHash4("sha256").update(text, "utf-8").digest("hex");
8045
+ return createHash5("sha256").update(text, "utf-8").digest("hex");
7854
8046
  }
7855
8047
  function shortHash(hash) {
7856
8048
  return hash.slice(0, 12);
@@ -8060,7 +8252,7 @@ async function runWatch(config, options = {}, deps = {}) {
8060
8252
  ledger = readProvenanceLedger(resolvedRoot);
8061
8253
  } catch (err) {
8062
8254
  const message = err instanceof Error ? err.message : String(err);
8063
- const ledgerPath = join31(resolvedRoot, "SOURCES.md");
8255
+ const ledgerPath = join32(resolvedRoot, "SOURCES.md");
8064
8256
  roots.push({ root, realPath, exists, ledgerPath, ledgerExists: false });
8065
8257
  warnings.push(`${ledgerPath}: could not read ledger: ${message}`);
8066
8258
  continue;
@@ -8171,7 +8363,7 @@ async function watchCommand(options = {}) {
8171
8363
 
8172
8364
  // src/commands/routine.ts
8173
8365
  import { existsSync as existsSync26, rmSync as rmSync7, statSync as statSync11 } from "fs";
8174
- import { join as join32 } from "path";
8366
+ import { join as join33 } from "path";
8175
8367
  var PRO_FEATURE_LABEL6 = "the scheduled maintenance routine";
8176
8368
  var SEVERITY_ORDER = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
8177
8369
  function parseFailOnFloor(failOn) {
@@ -8192,7 +8384,7 @@ function exceedsFloor(findings, failOn) {
8192
8384
  }
8193
8385
  function resolveReportPath2(override, generatedAt) {
8194
8386
  if (override) return override;
8195
- return join32(getHomeDir(), "reports", `routine-${generatedAt.replace(/[:.]/g, "-")}.md`);
8387
+ return join33(getHomeDir(), "reports", `routine-${generatedAt.replace(/[:.]/g, "-")}.md`);
8196
8388
  }
8197
8389
  function runHousekeeping(config, options) {
8198
8390
  const actions = [];
@@ -8201,7 +8393,7 @@ function runHousekeeping(config, options) {
8201
8393
  const pruneDays = options.pruneDays ?? 30;
8202
8394
  const cutoff = Date.now() - pruneDays * 864e5;
8203
8395
  for (const id of listQuarantineIds(config.quarantineDir)) {
8204
- const path = join32(config.quarantineDir, id);
8396
+ const path = join33(config.quarantineDir, id);
8205
8397
  let mtime;
8206
8398
  try {
8207
8399
  mtime = statSync11(path).mtimeMs;
@@ -8587,20 +8779,20 @@ async function queueClose(id, options = {}) {
8587
8779
  import { createInterface as createInterface6 } from "readline/promises";
8588
8780
  import {
8589
8781
  chmodSync as chmodSync3,
8590
- lstatSync as lstatSync6,
8591
- mkdirSync as mkdirSync9,
8782
+ lstatSync as lstatSync7,
8783
+ mkdirSync as mkdirSync10,
8592
8784
  readFileSync as readFileSync27,
8593
8785
  readSync as readSync3,
8594
- writeFileSync as writeFileSync8
8786
+ writeFileSync as writeFileSync9
8595
8787
  } from "fs";
8596
- import { dirname as dirname13, isAbsolute as isAbsolute9, join as join37, relative as relative8, resolve as resolvePath12 } from "path";
8788
+ import { dirname as dirname13, isAbsolute as isAbsolute9, join as join38, relative as relative8, resolve as resolvePath12 } from "path";
8597
8789
 
8598
8790
  // src/insights/inventory.ts
8599
8791
  import { readFileSync as readFileSync24 } from "fs";
8600
- import { join as join33 } from "path";
8792
+ import { join as join34 } from "path";
8601
8793
 
8602
8794
  // src/insights/models.ts
8603
- import { createHash as createHash5 } from "crypto";
8795
+ import { createHash as createHash6 } from "crypto";
8604
8796
  var INSIGHT_SCHEMA_VERSION = 1;
8605
8797
  var MAX_EVIDENCE_EXCERPT_CHARS = 500;
8606
8798
  var SOURCE_TYPES = ["url", "file", "directory", "connector", "stdin"];
@@ -8652,7 +8844,7 @@ function canonicalJson(value) {
8652
8844
  return JSON.stringify(normalize(value));
8653
8845
  }
8654
8846
  function stableHash(value) {
8655
- return createHash5("sha256").update(canonicalJson(value)).digest("hex");
8847
+ return createHash6("sha256").update(canonicalJson(value)).digest("hex");
8656
8848
  }
8657
8849
  function stableId(prefix, value, length = 16) {
8658
8850
  if (!/^[A-Z][A-Z0-9-]*$/.test(prefix)) {
@@ -8711,7 +8903,7 @@ function declaredMapCoverage(path, claim) {
8711
8903
  }
8712
8904
  function fingerprintInstalledSkill(path) {
8713
8905
  try {
8714
- return { fingerprint: stableHash(readFileSync24(join33(path, "SKILL.md"), "utf-8")) };
8906
+ return { fingerprint: stableHash(readFileSync24(join34(path, "SKILL.md"), "utf-8")) };
8715
8907
  } catch (error) {
8716
8908
  return { reason: `could not fingerprint SKILL.md: ${error.message}` };
8717
8909
  }
@@ -8862,26 +9054,26 @@ function buildInsightInventory(config, options = {}) {
8862
9054
 
8863
9055
  // src/insights/jira.ts
8864
9056
  import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
8865
- import { join as join36 } from "path";
9057
+ import { join as join37 } from "path";
8866
9058
 
8867
9059
  // src/insights/store.ts
8868
- import { chmodSync as chmodSync2, existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync25 } from "fs";
8869
- import { join as join35 } from "path";
9060
+ import { chmodSync as chmodSync2, existsSync as existsSync27, mkdirSync as mkdirSync9, readFileSync as readFileSync25 } from "fs";
9061
+ import { join as join36 } from "path";
8870
9062
 
8871
9063
  // src/insights/sources.ts
8872
- import { createHash as createHash6 } from "crypto";
9064
+ import { createHash as createHash7 } from "crypto";
8873
9065
  import {
8874
- closeSync as closeSync3,
8875
- constants as constants2,
9066
+ closeSync as closeSync4,
9067
+ constants as constants3,
8876
9068
  fstatSync as fstatSync2,
8877
- lstatSync as lstatSync5,
8878
- openSync as openSync3,
9069
+ lstatSync as lstatSync6,
9070
+ openSync as openSync4,
8879
9071
  readSync as readSync2,
8880
9072
  readdirSync as readdirSync11,
8881
9073
  realpathSync as realpathSync10,
8882
9074
  statSync as statSync12
8883
9075
  } from "fs";
8884
- import { basename as basename12, extname as extname8, isAbsolute as isAbsolute8, join as join34, relative as relative7, resolve as resolve9, sep as sep9 } from "path";
9076
+ import { basename as basename12, extname as extname8, isAbsolute as isAbsolute8, join as join35, relative as relative7, resolve as resolve9, sep as sep9 } from "path";
8885
9077
  var DEFAULT_MAX_SOURCE_FILE_BYTES = 5 * 1024 * 1024;
8886
9078
  var DEFAULT_MAX_SOURCE_TOTAL_BYTES = 25 * 1024 * 1024;
8887
9079
  var SKIP_DIRS4 = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", ".next", "out", "__pycache__", ".venv", "venv"]);
@@ -8980,12 +9172,12 @@ function observeFile(path, relativePath, declaredMediaType, maxFileBytes, remain
8980
9172
  }
8981
9173
  const mediaType = mediaTypeFor(path, declaredMediaType);
8982
9174
  if (!mediaType) return { path: relativePath, reason: "unsupported-media-type" };
8983
- const noFollow = typeof constants2.O_NOFOLLOW === "number" ? constants2.O_NOFOLLOW : 0;
9175
+ const noFollow = typeof constants3.O_NOFOLLOW === "number" ? constants3.O_NOFOLLOW : 0;
8984
9176
  let descriptor;
8985
9177
  let bytes;
8986
9178
  let descriptorStat;
8987
9179
  try {
8988
- descriptor = openSync3(path, constants2.O_RDONLY | noFollow);
9180
+ descriptor = openSync4(path, constants3.O_RDONLY | noFollow);
8989
9181
  descriptorStat = fstatSync2(descriptor);
8990
9182
  if (!descriptorStat.isFile()) return { path: relativePath, reason: "unsupported-media-type" };
8991
9183
  if (descriptorStat.size > maxFileBytes) {
@@ -9012,7 +9204,7 @@ function observeFile(path, relativePath, declaredMediaType, maxFileBytes, remain
9012
9204
  } catch (error) {
9013
9205
  throw new UserError(`unable to read source file ${relativePath}: ${error.message}`);
9014
9206
  } finally {
9015
- if (descriptor !== void 0) closeSync3(descriptor);
9207
+ if (descriptor !== void 0) closeSync4(descriptor);
9016
9208
  }
9017
9209
  if (!bytes || !descriptorStat) throw new UserError(`source file could not be observed: ${relativePath}`);
9018
9210
  let realPath;
@@ -9032,7 +9224,7 @@ function observeFile(path, relativePath, declaredMediaType, maxFileBytes, remain
9032
9224
  realPath,
9033
9225
  relativePath,
9034
9226
  byteLength: bytes.byteLength,
9035
- sha256: createHash6("sha256").update(bytes).digest("hex"),
9227
+ sha256: createHash7("sha256").update(bytes).digest("hex"),
9036
9228
  mediaType,
9037
9229
  suspiciousInstructionFlags: suspiciousInstructionFlags(bytes, mediaType),
9038
9230
  bytes
@@ -9116,7 +9308,7 @@ function normalizeStdin(input, retrievedAt, maxFileBytes, remainingBytes) {
9116
9308
  const bytes = Buffer.from(input.content);
9117
9309
  if (bytes.byteLength > maxFileBytes) throw new UserError(`stdin source exceeds the per-source byte limit (${maxFileBytes})`);
9118
9310
  if (bytes.byteLength > remainingBytes) throw new UserError(`stdin source exceeds the total source byte limit`);
9119
- const contentSha256 = createHash6("sha256").update(bytes).digest("hex");
9311
+ const contentSha256 = createHash7("sha256").update(bytes).digest("hex");
9120
9312
  const mediaType = input.mediaType ?? "text/plain";
9121
9313
  const fingerprint = stableHash({ type: input.type, contentSha256, mediaType });
9122
9314
  const common = normalizedCommon(input, retrievedAt);
@@ -9142,7 +9334,7 @@ function normalizeFile(input, retrievedAt, maxFileBytes, remainingBytes) {
9142
9334
  const resolved = resolve9(input.path);
9143
9335
  let lexicalStat;
9144
9336
  try {
9145
- lexicalStat = lstatSync5(resolved);
9337
+ lexicalStat = lstatSync6(resolved);
9146
9338
  } catch (err) {
9147
9339
  throw new UserError(`unable to read source file ${resolved}: ${err.message}`);
9148
9340
  }
@@ -9193,7 +9385,7 @@ function normalizeDirectory(input, retrievedAt, maxFileBytes, remainingBytes) {
9193
9385
  const resolved = resolve9(input.path);
9194
9386
  let lexicalStat;
9195
9387
  try {
9196
- lexicalStat = lstatSync5(resolved);
9388
+ lexicalStat = lstatSync6(resolved);
9197
9389
  } catch (err) {
9198
9390
  throw new UserError(`unable to read source directory ${resolved}: ${err.message}`);
9199
9391
  }
@@ -9213,7 +9405,7 @@ function normalizeDirectory(input, retrievedAt, maxFileBytes, remainingBytes) {
9213
9405
  throw new UserError(`unable to enumerate source directory ${dir}: ${err.message}`);
9214
9406
  }
9215
9407
  for (const entry of entries) {
9216
- const full = join34(dir, entry.name);
9408
+ const full = join35(dir, entry.name);
9217
9409
  const logicalPath = logicalDir ? `${logicalDir}/${entry.name}` : entry.name;
9218
9410
  if (entry.isDirectory() && SKIP_DIRS4.has(entry.name)) {
9219
9411
  skipped.push({ path: logicalPath, reason: "ignored-directory" });
@@ -9279,7 +9471,7 @@ function normalizeDirectory(input, retrievedAt, maxFileBytes, remainingBytes) {
9279
9471
  walk2(realRoot, "");
9280
9472
  observations.sort((a, b) => compareStrings2(a.relativePath, b.relativePath));
9281
9473
  skipped.sort((a, b) => compareStrings2(a.path, b.path) || compareStrings2(a.reason, b.reason));
9282
- const contentSha256 = createHash6("sha256");
9474
+ const contentSha256 = createHash7("sha256");
9283
9475
  for (const file of observations) contentSha256.update(`${file.relativePath}\0${file.sha256}
9284
9476
  `);
9285
9477
  const aggregateHash = contentSha256.digest("hex");
@@ -9365,17 +9557,17 @@ function normalizeSources(inputs, options = {}) {
9365
9557
  // src/insights/store.ts
9366
9558
  var STUDY_ID = /^STUDY-[0-9a-f]{16}$/;
9367
9559
  function getInsightsDir() {
9368
- return join35(getHomeDir(), "insights");
9560
+ return join36(getHomeDir(), "insights");
9369
9561
  }
9370
9562
  function getStudyDir(studyId) {
9371
9563
  assertStudyId(studyId);
9372
- return join35(getInsightsDir(), studyId);
9564
+ return join36(getInsightsDir(), studyId);
9373
9565
  }
9374
9566
  function getStudyPath(studyId) {
9375
- return join35(getStudyDir(studyId), "study.json");
9567
+ return join36(getStudyDir(studyId), "study.json");
9376
9568
  }
9377
9569
  function getStudyEventsPath(studyId) {
9378
- return join35(getStudyDir(studyId), "events.jsonl");
9570
+ return join36(getStudyDir(studyId), "events.jsonl");
9379
9571
  }
9380
9572
  function createStudyId(sourceInventoryHash, createdAt) {
9381
9573
  return stableId("STUDY", { sourceInventoryHash, createdAt });
@@ -9384,7 +9576,7 @@ function assertStudyId(studyId) {
9384
9576
  if (!STUDY_ID.test(studyId)) throw new UserError(`invalid insight study id: ${studyId}`);
9385
9577
  }
9386
9578
  function ensurePrivateDir(path) {
9387
- mkdirSync8(path, { recursive: true, mode: 448 });
9579
+ mkdirSync9(path, { recursive: true, mode: 448 });
9388
9580
  chmodSync2(path, 448);
9389
9581
  }
9390
9582
  function isRecord2(value) {
@@ -10308,7 +10500,7 @@ function buildJiraManifest(study, projectKey, options = {}) {
10308
10500
  return manifest;
10309
10501
  }
10310
10502
  function getJiraReceiptsPath(studyId) {
10311
- return join36(getStudyDir(studyId), "jira-receipts.jsonl");
10503
+ return join37(getStudyDir(studyId), "jira-receipts.jsonl");
10312
10504
  }
10313
10505
  function assertCurrentJiraManifest(study, value) {
10314
10506
  if (study.status !== "jira-manifested" || !study.jira) {
@@ -10714,7 +10906,7 @@ function positionalSource(value) {
10714
10906
  const resolved = resolvePath12(value);
10715
10907
  let stat;
10716
10908
  try {
10717
- stat = lstatSync6(resolved);
10909
+ stat = lstatSync7(resolved);
10718
10910
  } catch (error) {
10719
10911
  throw new UserError(`unable to read source ${resolved}: ${error.message}`);
10720
10912
  }
@@ -10730,10 +10922,10 @@ function collectSourceInputs(sourceArgs, options) {
10730
10922
  return inputs;
10731
10923
  }
10732
10924
  function contextPath(studyId) {
10733
- return join37(getStudyDir(studyId), CONTEXT_FILENAME);
10925
+ return join38(getStudyDir(studyId), CONTEXT_FILENAME);
10734
10926
  }
10735
10927
  function inventoryPath(studyId) {
10736
- return join37(getStudyDir(studyId), INVENTORY_FILENAME);
10928
+ return join38(getStudyDir(studyId), INVENTORY_FILENAME);
10737
10929
  }
10738
10930
  function writeCommandContext(studyId, skillMapPath, skillMapCoverage, inventory) {
10739
10931
  const context = {
@@ -10777,17 +10969,17 @@ function retainedPath(root, relativePath) {
10777
10969
  }
10778
10970
  function retainObservedSources(study, observedFiles) {
10779
10971
  if (observedFiles.length === 0) return study;
10780
- const root = join37(getStudyDir(study.studyId), "retained-sources");
10781
- mkdirSync9(root, { recursive: true, mode: 448 });
10972
+ const root = join38(getStudyDir(study.studyId), "retained-sources");
10973
+ mkdirSync10(root, { recursive: true, mode: 448 });
10782
10974
  chmodSync3(root, 448);
10783
10975
  const retainedSourceIds = /* @__PURE__ */ new Set();
10784
10976
  for (const file of observedFiles) {
10785
- const sourceRoot = join37(root, file.sourceId);
10786
- mkdirSync9(sourceRoot, { recursive: true, mode: 448 });
10977
+ const sourceRoot = join38(root, file.sourceId);
10978
+ mkdirSync10(sourceRoot, { recursive: true, mode: 448 });
10787
10979
  chmodSync3(sourceRoot, 448);
10788
10980
  const destination = retainedPath(sourceRoot, file.relativePath);
10789
- mkdirSync9(dirname13(destination), { recursive: true, mode: 448 });
10790
- writeFileSync8(destination, file.bytes, { flag: "wx", mode: 384 });
10981
+ mkdirSync10(dirname13(destination), { recursive: true, mode: 448 });
10982
+ writeFileSync9(destination, file.bytes, { flag: "wx", mode: 384 });
10791
10983
  chmodSync3(destination, 384);
10792
10984
  retainedSourceIds.add(file.sourceId);
10793
10985
  }
@@ -11020,7 +11212,7 @@ async function insightJira(studyId, options) {
11020
11212
  throw new UserError(`study ${studyId} cannot generate a Jira manifest: ${validation.blockers.map((issue) => issue.message).join("; ")}`);
11021
11213
  }
11022
11214
  if (localMode) {
11023
- const privatePath2 = join37(getStudyDir(studyId), JIRA_MANIFEST_FILENAME);
11215
+ const privatePath2 = join38(getStudyDir(studyId), JIRA_MANIFEST_FILENAME);
11024
11216
  const manifest2 = assertCurrentJiraManifest(study, parseJsonFile(privatePath2, "stored Jira manifest"));
11025
11217
  const receipt = options.receipt === void 0 ? void 0 : appendJiraReceipt(studyId, manifest2, parseJiraReceiptInput(parseJsonFile(options.receipt, "Jira receipt import")));
11026
11218
  const resume = buildJiraResumeState(manifest2, readJiraReceipts(studyId, manifest2));
@@ -11032,7 +11224,7 @@ async function insightJira(studyId, options) {
11032
11224
  return;
11033
11225
  }
11034
11226
  const manifest = buildJiraManifest(study, projectKey);
11035
- const privatePath = join37(getStudyDir(studyId), JIRA_MANIFEST_FILENAME);
11227
+ const privatePath = join38(getStudyDir(studyId), JIRA_MANIFEST_FILENAME);
11036
11228
  writeJsonAtomic(privatePath, manifest);
11037
11229
  if (options.out) {
11038
11230
  writeNewFile(resolvePath12(options.out), `${JSON.stringify(manifest, null, 2)}
@@ -11106,8 +11298,8 @@ async function insightClose(studyId, options) {
11106
11298
  import { tmpdir as tmpdir3 } from "os";
11107
11299
 
11108
11300
  // src/refine/activation.ts
11109
- import { lstatSync as lstatSync7, readFileSync as readFileSync28, readdirSync as readdirSync12, realpathSync as realpathSync11 } from "fs";
11110
- import { join as join38 } from "path";
11301
+ import { lstatSync as lstatSync8, readFileSync as readFileSync28, readdirSync as readdirSync12, realpathSync as realpathSync11 } from "fs";
11302
+ import { join as join39 } from "path";
11111
11303
 
11112
11304
  // src/refine/models.ts
11113
11305
  import { randomBytes } from "crypto";
@@ -11470,8 +11662,8 @@ function activationFiles(baseDir, overrideDir, name) {
11470
11662
  function walk2(dir, prefix = "") {
11471
11663
  for (const entry of readdirSync12(dir).sort()) {
11472
11664
  if (entry === ".git" || entry === "node_modules") continue;
11473
- const path = join38(dir, entry), relativePath = prefix ? `${prefix}/${entry}` : entry;
11474
- const stat = lstatSync7(path);
11665
+ const path = join39(dir, entry), relativePath = prefix ? `${prefix}/${entry}` : entry;
11666
+ const stat = lstatSync8(path);
11475
11667
  if (stat.isSymbolicLink()) throw new UserError(`Activation refuses symlinks: ${path}`);
11476
11668
  if (stat.isDirectory()) {
11477
11669
  walk2(path, relativePath);
@@ -11496,8 +11688,8 @@ function activationFiles(baseDir, overrideDir, name) {
11496
11688
  }
11497
11689
  let content = skill.bytes.toString("utf8");
11498
11690
  for (const artifact of artifacts) {
11499
- const path = join38(overrideDir, artifact);
11500
- const stat = lstatSync7(path);
11691
+ const path = join39(overrideDir, artifact);
11692
+ const stat = lstatSync8(path);
11501
11693
  if (!stat.isFile() || stat.isSymbolicLink()) throw new UserError("Override artifact must be a regular file, not a symlink.");
11502
11694
  if (stat.size > 2 * 1024 * 1024) throw new UserError("Override artifact exceeds 2 MiB.");
11503
11695
  const text = readFileSync28(path, "utf8");
@@ -11527,32 +11719,32 @@ ${content.slice(frontmatter[0].length)}`;
11527
11719
  }
11528
11720
 
11529
11721
  // src/commands/refine.ts
11530
- import { createHash as createHash8, randomUUID as randomUUID5 } from "crypto";
11531
- import { chmodSync as chmodSync4, existsSync as existsSync31, lstatSync as lstatSync8, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmdirSync, readFileSync as readFileSync30, realpathSync as realpathSync14, renameSync as renameSync6, rmSync as rmSync9, writeFileSync as writeFileSync10 } from "fs";
11532
- import { dirname as dirname15, isAbsolute as isAbsolute11, join as join41, relative as relative10, resolve as resolvePath15, sep as sep10 } from "path";
11722
+ import { createHash as createHash9, randomUUID as randomUUID6 } from "crypto";
11723
+ import { chmodSync as chmodSync4, existsSync as existsSync31, lstatSync as lstatSync9, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmdirSync, readFileSync as readFileSync30, realpathSync as realpathSync14, renameSync as renameSync6, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
11724
+ import { dirname as dirname15, isAbsolute as isAbsolute11, join as join42, relative as relative10, resolve as resolvePath15, sep as sep10 } from "path";
11533
11725
  import { createInterface as createInterface7 } from "readline/promises";
11534
11726
 
11535
11727
  // src/refine/context.ts
11536
11728
  import { existsSync as existsSync30, realpathSync as realpathSync13, statSync as statSync14 } from "fs";
11537
- import { isAbsolute as isAbsolute10, join as join40, relative as relative9, resolve as resolvePath14 } from "path";
11729
+ import { isAbsolute as isAbsolute10, join as join41, relative as relative9, resolve as resolvePath14 } from "path";
11538
11730
 
11539
11731
  // src/refine/store.ts
11540
- import { createHash as createHash7 } from "crypto";
11732
+ import { createHash as createHash8 } from "crypto";
11541
11733
  import { existsSync as existsSync29, readFileSync as readFileSync29, statSync as statSync13, realpathSync as realpathSync12 } from "fs";
11542
- import { join as join39, resolve as resolvePath13 } from "path";
11734
+ import { join as join40, resolve as resolvePath13 } from "path";
11543
11735
  var SIMILARITY_JOIN_THRESHOLD = 0.7;
11544
11736
  var READY_THRESHOLD = 2;
11545
11737
  function getRefinementsDir() {
11546
- return join39(getHomeDir(), "refinements");
11738
+ return join40(getHomeDir(), "refinements");
11547
11739
  }
11548
11740
  function getRefinementsPath() {
11549
- return join39(getRefinementsDir(), "refinements.json");
11741
+ return join40(getRefinementsDir(), "refinements.json");
11550
11742
  }
11551
11743
  function getPatternsPath() {
11552
- return join39(getRefinementsDir(), "patterns.json");
11744
+ return join40(getRefinementsDir(), "patterns.json");
11553
11745
  }
11554
11746
  function getBackupsDir() {
11555
- return join39(getRefinementsDir(), "backups");
11747
+ return join40(getRefinementsDir(), "backups");
11556
11748
  }
11557
11749
  function readJsonFile(path, label2) {
11558
11750
  let raw;
@@ -11797,11 +11989,11 @@ function createBackup(filePaths, deps = {}) {
11797
11989
  }
11798
11990
  const bytes = readFileSync29(path);
11799
11991
  const mode = statSync13(path).mode & 511;
11800
- const sha2562 = createHash7("sha256").update(bytes).digest("hex");
11992
+ const sha2562 = createHash8("sha256").update(bytes).digest("hex");
11801
11993
  return { path, existed: true, sha256: sha2562, mode, contentBase64: bytes.toString("base64") };
11802
11994
  });
11803
11995
  const manifest = { id, createdAt: now.toISOString(), files };
11804
- writeJsonAtomic(join39(getBackupsDir(), id, "manifest.json"), manifest);
11996
+ writeJsonAtomic(join40(getBackupsDir(), id, "manifest.json"), manifest);
11805
11997
  return manifest;
11806
11998
  }
11807
11999
  function validateBackupManifestShape(value, path) {
@@ -11850,7 +12042,7 @@ function readBackupManifest(id) {
11850
12042
  if (!isValidBackupId(id)) {
11851
12043
  throw new UserError(`invalid backup id "${id}" \u2014 expected the format produced by \`skill-forge refine promote\` (e.g. 20260720T142305Z-a1b2)`);
11852
12044
  }
11853
- const manifestPath = join39(getBackupsDir(), id, "manifest.json");
12045
+ const manifestPath = join40(getBackupsDir(), id, "manifest.json");
11854
12046
  if (!existsSync29(manifestPath)) {
11855
12047
  throw new UserError(`no backup with id: ${id}`);
11856
12048
  }
@@ -11885,8 +12077,8 @@ function isUnderRoot2(candidate, root) {
11885
12077
  function resolveScopes(config, cwd, skill, userRootFlag) {
11886
12078
  assertPlainSkillName(skill);
11887
12079
  const resolvedCwd = resolvePath14(cwd);
11888
- const localDir = join40(resolvedCwd, ".claude", "skills", skill);
11889
- const sharedDir = join40(resolvedCwd, "skills", skill);
12080
+ const localDir = join41(resolvedCwd, ".claude", "skills", skill);
12081
+ const sharedDir = join41(resolvedCwd, "skills", skill);
11890
12082
  const local = { scope: "local", dir: localDir, exists: existsSync30(localDir) };
11891
12083
  const shared = { scope: "shared", dir: sharedDir, exists: existsSync30(sharedDir) };
11892
12084
  const user = resolveUserScope(config, resolvedCwd, skill, userRootFlag);
@@ -11895,10 +12087,10 @@ function resolveScopes(config, cwd, skill, userRootFlag) {
11895
12087
  function resolveUserScope(config, resolvedCwd, skill, userRootFlag) {
11896
12088
  if (userRootFlag) {
11897
12089
  const root = resolvePath14(userRootFlag);
11898
- return { resolved: true, dir: join40(root, skill), root: userRootFlag, reason: "flag" };
12090
+ return { resolved: true, dir: join41(root, skill), root: userRootFlag, reason: "flag" };
11899
12091
  }
11900
12092
  const cwdReal = realpathOrNull2(resolvedCwd) ?? resolvedCwd;
11901
- const candidates = config.skillsRoots.map((root) => ({ root, dir: join40(resolveConfiguredRoot2(root), skill) }));
12093
+ const candidates = config.skillsRoots.map((root) => ({ root, dir: join41(resolveConfiguredRoot2(root), skill) }));
11902
12094
  for (const candidate of candidates) {
11903
12095
  const skillReal = realpathOrNull2(candidate.dir);
11904
12096
  if (skillReal) {
@@ -11933,12 +12125,12 @@ function inventoryScopeDir(scope2, dir) {
11933
12125
  scope: scope2,
11934
12126
  dir,
11935
12127
  dirExists: isDirectory(dir),
11936
- skillMd: isFile2(join40(dir, "SKILL.md")),
11937
- patchMd: isFile2(join40(dir, "SKILL.patch.md")),
11938
- extendMd: isFile2(join40(dir, "SKILL.extend.md")),
11939
- configJson: isFile2(join40(dir, "skill-config.json")),
11940
- hooksDir: isDirectory(join40(dir, "hooks")),
11941
- scriptsDir: isDirectory(join40(dir, "scripts"))
12128
+ skillMd: isFile2(join41(dir, "SKILL.md")),
12129
+ patchMd: isFile2(join41(dir, "SKILL.patch.md")),
12130
+ extendMd: isFile2(join41(dir, "SKILL.extend.md")),
12131
+ configJson: isFile2(join41(dir, "skill-config.json")),
12132
+ hooksDir: isDirectory(join41(dir, "hooks")),
12133
+ scriptsDir: isDirectory(join41(dir, "scripts"))
11942
12134
  };
11943
12135
  }
11944
12136
  function inventoryAllScopes(resolution) {
@@ -12124,14 +12316,14 @@ ${rendered}` : rendered;
12124
12316
  function ensureScopeDirSafe(scopeDir, resolvedCwd) {
12125
12317
  let lst;
12126
12318
  try {
12127
- lst = lstatSync8(scopeDir);
12319
+ lst = lstatSync9(scopeDir);
12128
12320
  } catch {
12129
12321
  lst = void 0;
12130
12322
  }
12131
12323
  if (lst?.isSymbolicLink()) {
12132
12324
  throw new UserError(`refusing to write under ${scopeDir}: scope directory is a symlink`);
12133
12325
  }
12134
- mkdirSync11(scopeDir, { recursive: true });
12326
+ mkdirSync12(scopeDir, { recursive: true });
12135
12327
  const real = resolveConfiguredRoot3(scopeDir);
12136
12328
  const lexical = resolvePath15(scopeDir);
12137
12329
  let realResolved;
@@ -12150,23 +12342,23 @@ function ensureScopeDirSafe(scopeDir, resolvedCwd) {
12150
12342
  }
12151
12343
  function writeOverrideFile(scopeDir, relPath, content, resolvedCwd) {
12152
12344
  const realScopeDir = ensureScopeDirSafe(scopeDir, resolvedCwd);
12153
- const destPath = join41(scopeDir, relPath);
12345
+ const destPath = join42(scopeDir, relPath);
12154
12346
  const destDir = dirname15(destPath);
12155
- mkdirSync11(destDir, { recursive: true });
12347
+ mkdirSync12(destDir, { recursive: true });
12156
12348
  const realDestDir = realpathSync14(destDir);
12157
12349
  if (!isUnderRoot3(realDestDir, realScopeDir)) {
12158
12350
  throw new UserError(`refusing to write ${destPath}: resolves outside scope directory ${scopeDir}`);
12159
12351
  }
12160
12352
  let destLst;
12161
12353
  try {
12162
- destLst = lstatSync8(destPath);
12354
+ destLst = lstatSync9(destPath);
12163
12355
  } catch {
12164
12356
  destLst = void 0;
12165
12357
  }
12166
12358
  if (destLst?.isSymbolicLink()) {
12167
12359
  throw new UserError(`refusing to write ${destPath}: destination is a symlink`);
12168
12360
  }
12169
- writeFileSync10(destPath, content, { mode: 384 });
12361
+ writeFileSync11(destPath, content, { mode: 384 });
12170
12362
  chmodSync4(destPath, 384);
12171
12363
  return destPath;
12172
12364
  }
@@ -12246,11 +12438,11 @@ Write ${preview.destPath}? [y/N] `);
12246
12438
  }
12247
12439
  function renderCaptureArtifactPreview(opts, content, refinementId, generatedAt, scopeDir) {
12248
12440
  const probeRelPath = opts.overrideType === "patch" ? "SKILL.patch.md" : opts.overrideType === "extend" || opts.overrideType === "new" ? "SKILL.extend.md" : void 0;
12249
- const probePath = probeRelPath ? join41(scopeDir, probeRelPath) : void 0;
12441
+ const probePath = probeRelPath ? join42(scopeDir, probeRelPath) : void 0;
12250
12442
  const existingContent = probePath && existsSync31(probePath) ? readFileSync30(probePath, "utf-8") : "";
12251
12443
  const rendered = renderCaptureArtifact(opts, content, refinementId, generatedAt, existingContent);
12252
12444
  const relPath = rendered.relPath;
12253
- const destPath = join41(scopeDir, relPath);
12445
+ const destPath = join42(scopeDir, relPath);
12254
12446
  const before = existsSync31(destPath) ? readFileSync30(destPath, "utf-8") : "";
12255
12447
  const diff = previewDiff(before, rendered.content, destPath);
12256
12448
  return { relPath, content: rendered.content, destPath, diff };
@@ -12266,8 +12458,8 @@ async function refineActivateCommand(options) {
12266
12458
  const scopes = resolveScopes(loadConfig(), cwd, options.skill);
12267
12459
  const overrideDir = options.scope === "shared" ? scopes.shared.dir : scopes.local.dir;
12268
12460
  if (realpathSync14(overrideDir) !== overrideDir) throw new UserError("Activation refuses a symlinked override directory.");
12269
- const hostRoot = join41(cwd, PROJECT_SKILL_ROOTS[options.host]);
12270
- const destination = join41(hostRoot, options.as);
12461
+ const hostRoot = join42(cwd, PROJECT_SKILL_ROOTS[options.host]);
12462
+ const destination = join42(hostRoot, options.as);
12271
12463
  if (existsSync31(destination) || resolveTargetRealPath(destination) !== destination) throw new UserError("Activation destination already exists or contains a symlink; choose a new --as name.");
12272
12464
  const files = activationFiles(options.base, overrideDir, options.as);
12273
12465
  const receiptName = ".skill-forge-activation.json";
@@ -12278,7 +12470,7 @@ async function refineActivateCommand(options) {
12278
12470
  skill: options.as,
12279
12471
  capture: options.skill,
12280
12472
  source: realpathSync14(options.base),
12281
- sourceSha256: createHash8("sha256").update(readFileSync30(join41(options.base, "SKILL.md"))).digest("hex"),
12473
+ sourceSha256: createHash9("sha256").update(readFileSync30(join42(options.base, "SKILL.md"))).digest("hex"),
12282
12474
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12283
12475
  runtimeVerified: false
12284
12476
  };
@@ -12286,15 +12478,15 @@ async function refineActivateCommand(options) {
12286
12478
  if (files.length > 1e3 || files.some((f) => f.bytes.length > 2 * 1024 * 1024) || files.reduce((n, f) => n + f.bytes.length, 0) > 16 * 1024 * 1024) {
12287
12479
  throw new UserError("Composed activation exceeds file or total size limits.");
12288
12480
  }
12289
- const stage = mkdtempSync4(join41(tmpdir3(), "skill-forge-activate-"));
12481
+ const stage = mkdtempSync4(join42(tmpdir3(), "skill-forge-activate-"));
12290
12482
  try {
12291
12483
  for (const file of files) {
12292
- const path = join41(stage, file.relativePath);
12293
- mkdirSync11(dirname15(path), { recursive: true });
12294
- writeFileSync10(path, file.bytes, { mode: file.mode });
12484
+ const path = join42(stage, file.relativePath);
12485
+ mkdirSync12(dirname15(path), { recursive: true });
12486
+ writeFileSync11(path, file.bytes, { mode: file.mode });
12295
12487
  }
12296
- const safety = await scanSafety(stage, "block-high", files.map((f) => ({ file: join41(stage, f.relativePath), content: f.bytes.toString("utf8") })));
12297
- safety.findings = safety.findings.map((f) => ({ ...f, file: join41(destination, relative10(stage, f.file)) }));
12488
+ const safety = await scanSafety(stage, "block-high", files.map((f) => ({ file: join42(stage, f.relativePath), content: f.bytes.toString("utf8") })));
12489
+ safety.findings = safety.findings.map((f) => ({ ...f, file: join42(destination, relative10(stage, f.file)) }));
12298
12490
  const preview = {
12299
12491
  destination,
12300
12492
  provenance,
@@ -12303,7 +12495,7 @@ async function refineActivateCommand(options) {
12303
12495
  source: realpathSync14(options.base),
12304
12496
  files: files.map((f) => f.relativePath),
12305
12497
  safety,
12306
- diff: previewDiff(readFileSync30(join41(options.base, "SKILL.md"), "utf8"), files.find((f) => f.relativePath === "SKILL.md").bytes.toString("utf8"), join41(destination, "SKILL.md"))
12498
+ diff: previewDiff(readFileSync30(join42(options.base, "SKILL.md"), "utf8"), files.find((f) => f.relativePath === "SKILL.md").bytes.toString("utf8"), join42(destination, "SKILL.md"))
12307
12499
  };
12308
12500
  if (options.dryRun) {
12309
12501
  emit2(options.json, { ...preview, dryRun: true, activated: false }, () => console.log(JSON.stringify(preview, null, 2)));
@@ -12317,15 +12509,15 @@ async function refineActivateCommand(options) {
12317
12509
  }
12318
12510
  if (resolveTargetRealPath(destination) !== destination) throw new UserError("Activation destination became symlinked.");
12319
12511
  ensureScopeDirSafe(hostRoot, cwd);
12320
- mkdirSync11(destination);
12321
- const backup = createBackup(files.map((f) => join41(destination, f.relativePath)));
12322
- const hashes = files.map((f) => ({ path: join41(destination, f.relativePath), sha256: createHash8("sha256").update(f.bytes).digest("hex") }));
12512
+ mkdirSync12(destination);
12513
+ const backup = createBackup(files.map((f) => join42(destination, f.relativePath)));
12514
+ const hashes = files.map((f) => ({ path: join42(destination, f.relativePath), sha256: createHash9("sha256").update(f.bytes).digest("hex") }));
12323
12515
  writePostWriteSidecar(backup.id, hashes);
12324
12516
  const projectConfig = { ...loadConfig(), skillsRoots: [hostRoot] };
12325
12517
  try {
12326
- for (const file of files) safeRestoreWrite(join41(destination, file.relativePath), file.bytes, file.mode, projectConfig);
12518
+ for (const file of files) safeRestoreWrite(join42(destination, file.relativePath), file.bytes, file.mode, projectConfig);
12327
12519
  for (const file of hashes) {
12328
- if (createHash8("sha256").update(readFileSync30(file.path)).digest("hex") !== file.sha256) throw new UserError("Activation read-back hash mismatch.");
12520
+ if (createHash9("sha256").update(readFileSync30(file.path)).digest("hex") !== file.sha256) throw new UserError("Activation read-back hash mismatch.");
12329
12521
  }
12330
12522
  } catch (error) {
12331
12523
  throw new UserError(`Activation write failed; backup ${backup.id} records recovery paths. Inspect before using rollback --project --force: ${error.message}`);
@@ -12335,7 +12527,7 @@ async function refineActivateCommand(options) {
12335
12527
  activated: true,
12336
12528
  backupId: backup.id,
12337
12529
  runtimeVerified: false,
12338
- verification: `Open a fresh ${options.host} task in this project, invoke ${preview.invocation}, and verify it reads ${join41(destination, "SKILL.md")}.`,
12530
+ verification: `Open a fresh ${options.host} task in this project, invoke ${preview.invocation}, and verify it reads ${join42(destination, "SKILL.md")}.`,
12339
12531
  rollback: `skill-forge refine rollback ${backup.id} --project --yes`
12340
12532
  }, () => {
12341
12533
  console.log(`Activated ${destination}. Invoke ${preview.invocation} in a fresh ${options.host} task and verify the loaded file; runtime use is not yet observed.`);
@@ -12438,12 +12630,12 @@ async function refineWhichCommand(skill, options = {}) {
12438
12630
  }
12439
12631
  function writePostWriteSidecar(backupId, files) {
12440
12632
  const sidecar = { schemaVersion: 1, files };
12441
- const dir = join41(getBackupsDir(), backupId);
12442
- mkdirSync11(dir, { recursive: true });
12443
- writeFileSync10(join41(dir, "post.json"), JSON.stringify(sidecar, null, 2) + "\n", { mode: 384 });
12633
+ const dir = join42(getBackupsDir(), backupId);
12634
+ mkdirSync12(dir, { recursive: true });
12635
+ writeFileSync11(join42(dir, "post.json"), JSON.stringify(sidecar, null, 2) + "\n", { mode: 384 });
12444
12636
  }
12445
12637
  function readPostWriteSidecar(backupId) {
12446
- const path = join41(getBackupsDir(), backupId, "post.json");
12638
+ const path = join42(getBackupsDir(), backupId, "post.json");
12447
12639
  if (!existsSync31(path)) return { hashes: /* @__PURE__ */ new Map(), status: "missing" };
12448
12640
  let raw;
12449
12641
  try {
@@ -12505,7 +12697,7 @@ async function refinePromoteCommand(patternId, options = {}) {
12505
12697
  if (!isUnderRoot3(realSkillDir, realUserRoot)) {
12506
12698
  throw new UserError(`refusing to promote: resolved skill directory ${skillDir} is outside the configured user scope root ${scopes.user.root}`);
12507
12699
  }
12508
- const skillMdPath = join41(skillDir, "SKILL.md");
12700
+ const skillMdPath = join42(skillDir, "SKILL.md");
12509
12701
  if (!existsSync31(skillMdPath)) {
12510
12702
  throw new UserError(`user-scope SKILL.md not found at ${skillMdPath} \u2014 refusing to promote against a missing base skill.`);
12511
12703
  }
@@ -12546,16 +12738,16 @@ Promote pattern ${patternId} into ${skillMdPath}? [y/N] `);
12546
12738
  return;
12547
12739
  }
12548
12740
  const backup = createBackup([skillMdPath]);
12549
- const tempPath = `${skillMdPath}.tmp-${randomUUID5()}`;
12741
+ const tempPath = `${skillMdPath}.tmp-${randomUUID6()}`;
12550
12742
  try {
12551
- writeFileSync10(tempPath, after, "utf-8");
12743
+ writeFileSync11(tempPath, after, "utf-8");
12552
12744
  renameSync6(tempPath, skillMdPath);
12553
12745
  } catch (err) {
12554
12746
  rmSync9(tempPath, { force: true });
12555
12747
  throw err;
12556
12748
  }
12557
12749
  try {
12558
- const postSha256 = createHash8("sha256").update(readFileSync30(skillMdPath)).digest("hex");
12750
+ const postSha256 = createHash9("sha256").update(readFileSync30(skillMdPath)).digest("hex");
12559
12751
  writePostWriteSidecar(backup.id, [{ path: skillMdPath, sha256: postSha256 }]);
12560
12752
  markGeneralized(patternId, backup.id);
12561
12753
  const skillsRoot = resolveConfiguredRoot3(scopes.user.root);
@@ -12583,7 +12775,7 @@ function assertBackupDirContained(backupId) {
12583
12775
  const backupsDir = getBackupsDir();
12584
12776
  if (!existsSync31(backupsDir)) return;
12585
12777
  const realBackupsDir = realpathSync14(backupsDir);
12586
- const backupDir = join41(backupsDir, backupId);
12778
+ const backupDir = join42(backupsDir, backupId);
12587
12779
  if (!existsSync31(backupDir)) return;
12588
12780
  const realBackupDir = realpathSync14(backupDir);
12589
12781
  if (!isUnderRoot3(realBackupDir, realBackupsDir)) {
@@ -12596,7 +12788,7 @@ function resolveTargetRealPath(path) {
12596
12788
  try {
12597
12789
  const real = realpathSync14(lexicalAncestor);
12598
12790
  const remainder = relative10(lexicalAncestor, resolvePath15(path));
12599
- return remainder === "" ? real : join41(real, remainder);
12791
+ return remainder === "" ? real : join42(real, remainder);
12600
12792
  } catch {
12601
12793
  const parent = dirname15(lexicalAncestor);
12602
12794
  if (parent === lexicalAncestor) return resolvePath15(path);
@@ -12628,7 +12820,7 @@ function assertNoSymlinkLeaves(manifest) {
12628
12820
  for (const file of manifest.files) {
12629
12821
  let lst;
12630
12822
  try {
12631
- lst = lstatSync8(file.path);
12823
+ lst = lstatSync9(file.path);
12632
12824
  } catch {
12633
12825
  lst = void 0;
12634
12826
  }
@@ -12665,10 +12857,10 @@ function walkRealComponents(filePath, config, createMissing) {
12665
12857
  let current = rootReal;
12666
12858
  for (const component of dirComponents) {
12667
12859
  if (current === void 0) break;
12668
- const next = join41(current, component);
12860
+ const next = join42(current, component);
12669
12861
  let lst;
12670
12862
  try {
12671
- lst = lstatSync8(next);
12863
+ lst = lstatSync9(next);
12672
12864
  } catch {
12673
12865
  lst = void 0;
12674
12866
  }
@@ -12677,7 +12869,7 @@ function walkRealComponents(filePath, config, createMissing) {
12677
12869
  current = void 0;
12678
12870
  continue;
12679
12871
  }
12680
- mkdirSync11(next);
12872
+ mkdirSync12(next);
12681
12873
  current = next;
12682
12874
  continue;
12683
12875
  }
@@ -12696,22 +12888,22 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
12696
12888
  if (parentReal === void 0) {
12697
12889
  throw new UserError(`refusing to restore ${filePath}: could not establish a real parent directory`);
12698
12890
  }
12699
- const finalPath = join41(parentReal, leafComponent);
12891
+ const finalPath = join42(parentReal, leafComponent);
12700
12892
  if (!isUnderRoot3(finalPath, rootReal)) {
12701
12893
  throw new UserError(`refusing to restore ${filePath}: resolves outside root ${rootReal}`);
12702
12894
  }
12703
12895
  let leafLst;
12704
12896
  try {
12705
- leafLst = lstatSync8(finalPath);
12897
+ leafLst = lstatSync9(finalPath);
12706
12898
  } catch {
12707
12899
  leafLst = void 0;
12708
12900
  }
12709
12901
  if (leafLst?.isSymbolicLink()) {
12710
12902
  throw new UserError(`refusing to restore ${filePath}: it is now a symlink \u2014 refusing to follow or overwrite it.`);
12711
12903
  }
12712
- const tempPath = `${finalPath}.tmp-${randomUUID5()}`;
12904
+ const tempPath = `${finalPath}.tmp-${randomUUID6()}`;
12713
12905
  try {
12714
- writeFileSync10(tempPath, contentBuffer);
12906
+ writeFileSync11(tempPath, contentBuffer);
12715
12907
  chmodSync4(tempPath, mode);
12716
12908
  renameSync6(tempPath, finalPath);
12717
12909
  } catch (err) {
@@ -12722,13 +12914,13 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
12722
12914
  function safeDeleteTombstone(filePath, config) {
12723
12915
  const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, false);
12724
12916
  if (parentReal === void 0) return;
12725
- const finalPath = join41(parentReal, leafComponent);
12917
+ const finalPath = join42(parentReal, leafComponent);
12726
12918
  if (!isUnderRoot3(finalPath, rootReal)) {
12727
12919
  throw new UserError(`refusing to delete ${filePath}: resolves outside root ${rootReal}`);
12728
12920
  }
12729
12921
  let leafLst;
12730
12922
  try {
12731
- leafLst = lstatSync8(finalPath);
12923
+ leafLst = lstatSync9(finalPath);
12732
12924
  } catch {
12733
12925
  leafLst = void 0;
12734
12926
  }
@@ -12747,7 +12939,7 @@ async function refineRollbackCommand(backupId, options = {}) {
12747
12939
  assertBackupDirContained(backupId);
12748
12940
  const manifest = readBackupManifest(backupId);
12749
12941
  const config = loadConfig();
12750
- if (options.project) config.skillsRoots = Object.values(PROJECT_SKILL_ROOTS).map((p) => join41(realpathSync14(process.cwd()), p));
12942
+ if (options.project) config.skillsRoots = Object.values(PROJECT_SKILL_ROOTS).map((p) => join42(realpathSync14(process.cwd()), p));
12751
12943
  const assertProjectRoots = () => {
12752
12944
  if (options.project && config.skillsRoots.some((root) => resolveTargetRealPath(root) !== root)) {
12753
12945
  throw new UserError("refusing project rollback: a project skill root or its ancestor is symlinked.");
@@ -12774,7 +12966,7 @@ async function refineRollbackCommand(backupId, options = {}) {
12774
12966
  for (const file of manifest.files) {
12775
12967
  const expected = sidecar.hashes.get(file.path);
12776
12968
  if (expected === void 0) continue;
12777
- const currentSha256 = existsSync31(file.path) ? createHash8("sha256").update(readFileSync30(file.path)).digest("hex") : null;
12969
+ const currentSha256 = existsSync31(file.path) ? createHash9("sha256").update(readFileSync30(file.path)).digest("hex") : null;
12778
12970
  if (currentSha256 !== expected) mismatches.push(file.path);
12779
12971
  }
12780
12972
  if (mismatches.length > 0 && !options.force) {
@@ -12932,7 +13124,7 @@ configCmd.command("propose <key> <value>").description("Queue a preference for r
12932
13124
  configCmd.command("review").description("Review pending agent-proposed preferences one at a time").option("--accept-all", "accept every pending proposal without prompting").option("--reject-all", "reject every pending proposal without prompting").action(configReview);
12933
13125
  program.command("guide [topic]").description("What this tool does, what state you are in, and the next command to run").action(guide);
12934
13126
  program.command("evolve <target-skill-dir>").description("Self-evolve a skill via SkillOpt-Sleep, re-gate the proposal, then promote/hold/reject (Pro)").option("--project <dir>", "project dir passed to SkillOpt-Sleep as --project (defaults to cwd)").option("--dry-run", "SkillOpt-Sleep dry-run: report only, nothing staged").option("--backend <name>", "SkillOpt-Sleep backend (default: mock, offline \u2014 a real backend requires --yes or interactive confirmation)").option("--lookback-hours <n>", "hours of session history for SkillOpt-Sleep to harvest", parseLookbackHours).option("-y, --yes", "skip interactive prompts; honor the re-gate verdict automatically").option("--json", "print the re-gate result as JSON instead of the terminal report").option("--force", "allow re-adopting a staging dir whose proposed content already matches the live skill").action(evolveCommand);
12935
- program.command("audit").option("--compare <file>", "compare with a prior full audit JSON; preserve all current findings").alias("doctor").description("Doctor-style health check over the configured skill/MCP set: inventory, hygiene findings, and consolidation/refinement opportunities").option("--json", "print the audit report as JSON instead of the terminal summary (non-interactive: skips the business-profile prompt)").option("-y, --yes", "skip interactive prompts (business profile, foundation, handoff); never implies --foundation or --handoff").option("--report <file>", "write the report to this path instead of the default ~/.skill-forge/reports/audit-<timestamp>.md (refuses an existing path)").option("--foundation", "authorizes exactly one skills-root write: scaffold a business-foundation skill from the captured business profile").option("--handoff", "authorizes launching the configured agent with the bundled curation prompt against the written report").option("--plugin-inventory <file>", "import a static host inventory JSON; audit one host at a time").option("--claude-plugins", "include enabled Claude Code plugins' declared skills in this audit (default: config.claudePlugins, else off)").option("--no-claude-plugins", "exclude enabled Claude Code plugins from this audit even when config.claudePlugins is on").option("--usage-snapshot <file>", "join per-skill usage counts from a skill-monitor snapshot JSON file or a directory of them (default: config.usageSnapshotPath)").option("--usage-window <days>", "restrict directory-input usage-snapshot selection to a snapshot with this window_days (integer)", parseUsageWindow).action(auditCommand);
13127
+ program.command("audit").option("--compare <file>", "compare with a prior full audit JSON; preserve all current findings").alias("doctor").description("Doctor-style health check over the configured skill/MCP set: inventory, hygiene findings, and consolidation/refinement opportunities").option("--json", "print the audit report as JSON instead of the terminal summary (non-interactive: skips the business-profile prompt)").option("--laya-shadow", "score current finding metadata with local Laya in shadow; never changes a gate").option("--laya-base-url <url>", "loopback Laya endpoint for --laya-shadow (default http://127.0.0.1:8000)").option("-y, --yes", "skip interactive prompts (business profile, foundation, handoff); never implies --foundation or --handoff").option("--report <file>", "write the report to this path instead of the default ~/.skill-forge/reports/audit-<timestamp>.md (refuses an existing path)").option("--foundation", "authorizes exactly one skills-root write: scaffold a business-foundation skill from the captured business profile").option("--handoff", "authorizes launching the configured agent with the bundled curation prompt against the written report").option("--plugin-inventory <file>", "import a static host inventory JSON; audit one host at a time").option("--claude-plugins", "include enabled Claude Code plugins' declared skills in this audit (default: config.claudePlugins, else off)").option("--no-claude-plugins", "exclude enabled Claude Code plugins from this audit even when config.claudePlugins is on").option("--usage-snapshot <file>", "join per-skill usage counts from a skill-monitor snapshot JSON file or a directory of them (default: config.usageSnapshotPath)").option("--usage-window <days>", "restrict directory-input usage-snapshot selection to a snapshot with this window_days (integer)", parseUsageWindow).action(auditCommand);
12936
13128
  var finding = program.command("finding").description("Review and acknowledge audit findings \u2014 human-only, never a gate input (see docs/accepted-findings-schema.md)");
12937
13129
  finding.command("accept <fingerprint-prefix>").description("Accept a currently-active LOW/MEDIUM finding (no override for HIGH/CRITICAL); re-runs the audit engine to resolve it").requiredOption("--reason <text>", "why this finding is accepted (mandatory, 1-200 chars, no control characters)").option("--json", "print the accepted record as JSON").action(findingAcceptCommand);
12938
13130
  finding.command("revoke <fingerprint-prefix>").description("Remove a stored acceptance so the finding reports again").option("--json", "print the removed record as JSON").action(findingRevokeCommand);