@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/README.md +11 -4
- package/dist/cli.js +370 -178
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
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
|
|
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
|
|
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
|
|
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 ??
|
|
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] ??
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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] ??
|
|
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
|
|
6774
|
-
import { cpSync as cpSync3, existsSync as existsSync22, lstatSync as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6850
|
-
const proposedClaudeMd =
|
|
6851
|
-
const reportMdPath =
|
|
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(
|
|
7054
|
+
const tempDir = mkdtempSync3(join30(tmpdir2(), "skill-forge-evolve-"));
|
|
6863
7055
|
let profile;
|
|
6864
7056
|
let safety;
|
|
6865
7057
|
try {
|
|
6866
|
-
cpSync3(proposedSkillMd,
|
|
7058
|
+
cpSync3(proposedSkillMd, join30(tempDir, "SKILL.md"));
|
|
6867
7059
|
if (existsSync22(proposedClaudeMd)) {
|
|
6868
|
-
cpSync3(proposedClaudeMd,
|
|
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 =
|
|
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 =
|
|
7051
|
-
const proposedClaudeMd =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
7418
|
+
if (existsSync24(join31(dir, "SKILL.md"))) found.add(dir);
|
|
7227
7419
|
}
|
|
7228
|
-
for (const dir of listSubdirs2(
|
|
7229
|
-
if (existsSync24(
|
|
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(
|
|
7233
|
-
if (existsSync24(
|
|
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 =
|
|
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
|
|
8038
|
+
import { join as join32, resolve as resolvePath10 } from "path";
|
|
7847
8039
|
|
|
7848
8040
|
// src/gate/skillMapDrift.ts
|
|
7849
|
-
import { createHash as
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
8591
|
-
mkdirSync as
|
|
8782
|
+
lstatSync as lstatSync7,
|
|
8783
|
+
mkdirSync as mkdirSync10,
|
|
8592
8784
|
readFileSync as readFileSync27,
|
|
8593
8785
|
readSync as readSync3,
|
|
8594
|
-
writeFileSync as
|
|
8786
|
+
writeFileSync as writeFileSync9
|
|
8595
8787
|
} from "fs";
|
|
8596
|
-
import { dirname as dirname13, isAbsolute as isAbsolute9, join as
|
|
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
|
|
8792
|
+
import { join as join34 } from "path";
|
|
8601
8793
|
|
|
8602
8794
|
// src/insights/models.ts
|
|
8603
|
-
import { createHash as
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
8869
|
-
import { join as
|
|
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
|
|
9064
|
+
import { createHash as createHash7 } from "crypto";
|
|
8873
9065
|
import {
|
|
8874
|
-
closeSync as
|
|
8875
|
-
constants as
|
|
9066
|
+
closeSync as closeSync4,
|
|
9067
|
+
constants as constants3,
|
|
8876
9068
|
fstatSync as fstatSync2,
|
|
8877
|
-
lstatSync as
|
|
8878
|
-
openSync as
|
|
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
|
|
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
|
|
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 =
|
|
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)
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
9560
|
+
return join36(getHomeDir(), "insights");
|
|
9369
9561
|
}
|
|
9370
9562
|
function getStudyDir(studyId) {
|
|
9371
9563
|
assertStudyId(studyId);
|
|
9372
|
-
return
|
|
9564
|
+
return join36(getInsightsDir(), studyId);
|
|
9373
9565
|
}
|
|
9374
9566
|
function getStudyPath(studyId) {
|
|
9375
|
-
return
|
|
9567
|
+
return join36(getStudyDir(studyId), "study.json");
|
|
9376
9568
|
}
|
|
9377
9569
|
function getStudyEventsPath(studyId) {
|
|
9378
|
-
return
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
10925
|
+
return join38(getStudyDir(studyId), CONTEXT_FILENAME);
|
|
10734
10926
|
}
|
|
10735
10927
|
function inventoryPath(studyId) {
|
|
10736
|
-
return
|
|
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 =
|
|
10781
|
-
|
|
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 =
|
|
10786
|
-
|
|
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
|
-
|
|
10790
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
11110
|
-
import { join as
|
|
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 =
|
|
11474
|
-
const stat =
|
|
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 =
|
|
11500
|
-
const stat =
|
|
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
|
|
11531
|
-
import { chmodSync as chmodSync4, existsSync as existsSync31, lstatSync as
|
|
11532
|
-
import { dirname as dirname15, isAbsolute as isAbsolute11, join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11738
|
+
return join40(getHomeDir(), "refinements");
|
|
11547
11739
|
}
|
|
11548
11740
|
function getRefinementsPath() {
|
|
11549
|
-
return
|
|
11741
|
+
return join40(getRefinementsDir(), "refinements.json");
|
|
11550
11742
|
}
|
|
11551
11743
|
function getPatternsPath() {
|
|
11552
|
-
return
|
|
11744
|
+
return join40(getRefinementsDir(), "patterns.json");
|
|
11553
11745
|
}
|
|
11554
11746
|
function getBackupsDir() {
|
|
11555
|
-
return
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
11889
|
-
const sharedDir =
|
|
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:
|
|
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:
|
|
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(
|
|
11937
|
-
patchMd: isFile2(
|
|
11938
|
-
extendMd: isFile2(
|
|
11939
|
-
configJson: isFile2(
|
|
11940
|
-
hooksDir: isDirectory(
|
|
11941
|
-
scriptsDir: isDirectory(
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
12345
|
+
const destPath = join42(scopeDir, relPath);
|
|
12154
12346
|
const destDir = dirname15(destPath);
|
|
12155
|
-
|
|
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 =
|
|
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
|
-
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
12270
|
-
const destination =
|
|
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:
|
|
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(
|
|
12481
|
+
const stage = mkdtempSync4(join42(tmpdir3(), "skill-forge-activate-"));
|
|
12290
12482
|
try {
|
|
12291
12483
|
for (const file of files) {
|
|
12292
|
-
const path =
|
|
12293
|
-
|
|
12294
|
-
|
|
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:
|
|
12297
|
-
safety.findings = safety.findings.map((f) => ({ ...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(
|
|
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
|
-
|
|
12321
|
-
const backup = createBackup(files.map((f) =>
|
|
12322
|
-
const hashes = files.map((f) => ({ path:
|
|
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(
|
|
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 (
|
|
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 ${
|
|
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 =
|
|
12442
|
-
|
|
12443
|
-
|
|
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 =
|
|
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 =
|
|
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-${
|
|
12741
|
+
const tempPath = `${skillMdPath}.tmp-${randomUUID6()}`;
|
|
12550
12742
|
try {
|
|
12551
|
-
|
|
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 =
|
|
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 =
|
|
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 :
|
|
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 =
|
|
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 =
|
|
12860
|
+
const next = join42(current, component);
|
|
12669
12861
|
let lst;
|
|
12670
12862
|
try {
|
|
12671
|
-
lst =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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-${
|
|
12904
|
+
const tempPath = `${finalPath}.tmp-${randomUUID6()}`;
|
|
12713
12905
|
try {
|
|
12714
|
-
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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) ?
|
|
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);
|