@rhize/skill-forge 0.20.1 → 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 +7 -3
- package/dist/cli.js +299 -164
- 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";
|
|
@@ -4669,7 +4669,7 @@ function runHandoff(config, installedPath, options = {}) {
|
|
|
4669
4669
|
// src/commands/init.ts
|
|
4670
4670
|
import { existsSync as existsSync19 } from "fs";
|
|
4671
4671
|
import { homedir as homedir5 } from "os";
|
|
4672
|
-
import { join as
|
|
4672
|
+
import { join as join26 } from "path";
|
|
4673
4673
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
4674
4674
|
|
|
4675
4675
|
// src/agents.ts
|
|
@@ -5093,7 +5093,7 @@ function readPluginInventory(path) {
|
|
|
5093
5093
|
|
|
5094
5094
|
// src/commands/audit.ts
|
|
5095
5095
|
import { existsSync as existsSync18 } from "fs";
|
|
5096
|
-
import { join as
|
|
5096
|
+
import { join as join25, resolve as resolvePath5 } from "path";
|
|
5097
5097
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
5098
5098
|
|
|
5099
5099
|
// src/decision.ts
|
|
@@ -5459,6 +5459,129 @@ function loadUsageSnapshot(pathOrDir, opts = {}) {
|
|
|
5459
5459
|
return toSnapshot(parsed, resolved);
|
|
5460
5460
|
}
|
|
5461
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
|
+
|
|
5462
5585
|
// src/commands/audit.ts
|
|
5463
5586
|
var CURATION_PROMPT_ASSET = "curation-prompt.md";
|
|
5464
5587
|
function resolvePluginAndUsageContext(config, options, deps = {}) {
|
|
@@ -5515,7 +5638,7 @@ async function captureBusinessProfile(rl, existing) {
|
|
|
5515
5638
|
function resolveReportPath(reportOption) {
|
|
5516
5639
|
if (reportOption) return resolvePath5(reportOption);
|
|
5517
5640
|
const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5518
|
-
return
|
|
5641
|
+
return join25(getHomeDir(), "reports", `audit-${iso}.md`);
|
|
5519
5642
|
}
|
|
5520
5643
|
function summarizePluginRecommendations(plugins2) {
|
|
5521
5644
|
const counts = { keep: 0, review: 0, unobserved: 0, unknown: 0 };
|
|
@@ -5603,6 +5726,18 @@ async function auditCommand(options = {}, deps = {}) {
|
|
|
5603
5726
|
const extraNotices = [...storeNotices, ...pluginUsageNotices];
|
|
5604
5727
|
if (extraNotices.length > 0) report.notices = [...report.notices, ...extraNotices];
|
|
5605
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
|
+
}
|
|
5606
5741
|
const reportPath = resolveReportPath(options.report);
|
|
5607
5742
|
writeNewFile(reportPath, renderAuditMarkdown(report), "--report");
|
|
5608
5743
|
try {
|
|
@@ -5931,7 +6066,7 @@ async function init(options = {}, deps = {}) {
|
|
|
5931
6066
|
"[skill-forge] No detected agent CLI was found on PATH \u2014 keeping every detected target selected."
|
|
5932
6067
|
);
|
|
5933
6068
|
}
|
|
5934
|
-
const installedPluginsPath = options.installedPluginsPath ??
|
|
6069
|
+
const installedPluginsPath = options.installedPluginsPath ?? join26(home, ".claude", "plugins", "installed_plugins.json");
|
|
5935
6070
|
const claudePluginsRegistryExists = existsSync19(installedPluginsPath);
|
|
5936
6071
|
if (options.defaults) {
|
|
5937
6072
|
const selectedRoots = narrow ? rootGroups.filter(hasInstalledMember) : rootGroups;
|
|
@@ -6142,7 +6277,7 @@ async function addMcp(source, config, options) {
|
|
|
6142
6277
|
envVars: detail.env ? Object.keys(detail.env) : void 0,
|
|
6143
6278
|
candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
|
|
6144
6279
|
};
|
|
6145
|
-
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ??
|
|
6280
|
+
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join27(process.cwd(), ".mcp.json");
|
|
6146
6281
|
const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
|
|
6147
6282
|
reject(entry);
|
|
6148
6283
|
logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
|
|
@@ -6238,7 +6373,7 @@ async function scan(source, options = {}) {
|
|
|
6238
6373
|
}
|
|
6239
6374
|
|
|
6240
6375
|
// src/commands/list.ts
|
|
6241
|
-
import { join as
|
|
6376
|
+
import { join as join28 } from "path";
|
|
6242
6377
|
async function list() {
|
|
6243
6378
|
await maybeOfferInit();
|
|
6244
6379
|
const config = loadConfig();
|
|
@@ -6251,14 +6386,14 @@ async function list() {
|
|
|
6251
6386
|
}
|
|
6252
6387
|
console.log(`[skill-forge] ${ids.length} held in quarantine (${quarantineDir}):`);
|
|
6253
6388
|
for (const id of ids) {
|
|
6254
|
-
const skillDirs = findSkillDirs(
|
|
6389
|
+
const skillDirs = findSkillDirs(join28(quarantineDir, id));
|
|
6255
6390
|
if (skillDirs.length === 0) {
|
|
6256
6391
|
console.log(` ${id} (no SKILL.md found)`);
|
|
6257
6392
|
continue;
|
|
6258
6393
|
}
|
|
6259
6394
|
const names = skillDirs.map((dir) => {
|
|
6260
6395
|
try {
|
|
6261
|
-
return readFrontmatter(
|
|
6396
|
+
return readFrontmatter(join28(dir, "SKILL.md")).name;
|
|
6262
6397
|
} catch {
|
|
6263
6398
|
return dir;
|
|
6264
6399
|
}
|
|
@@ -6272,13 +6407,13 @@ import { existsSync as existsSync21 } from "fs";
|
|
|
6272
6407
|
|
|
6273
6408
|
// src/commands/held.ts
|
|
6274
6409
|
import { existsSync as existsSync20, realpathSync as realpathSync6 } from "fs";
|
|
6275
|
-
import { basename as basename9, join as
|
|
6410
|
+
import { basename as basename9, join as join29, resolve as resolvePath6, sep as sep7 } from "path";
|
|
6276
6411
|
var PRO_FEATURE_LABEL2 = "overlap analysis, the provenance ledger, and the pending-ingestion queue";
|
|
6277
6412
|
function resolveHeldPath(quarantineDir, id) {
|
|
6278
6413
|
if (id === "" || id !== basename9(id) || id === "." || id === "..") {
|
|
6279
6414
|
throw new UserError(`invalid quarantine id "${id}" \u2014 pass a single id exactly as printed by \`skill-forge list\``);
|
|
6280
6415
|
}
|
|
6281
|
-
const candidate =
|
|
6416
|
+
const candidate = join29(quarantineDir, id);
|
|
6282
6417
|
if (!existsSync20(candidate)) {
|
|
6283
6418
|
throw new UserError(`no held entry with id "${id}" \u2014 run \`skill-forge list\` to see what is in quarantine`);
|
|
6284
6419
|
}
|
|
@@ -6307,7 +6442,7 @@ function resolveTargetRoot(config, target) {
|
|
|
6307
6442
|
function heldSkillNames(skillDirs) {
|
|
6308
6443
|
return skillDirs.map((dir) => {
|
|
6309
6444
|
try {
|
|
6310
|
-
return readFrontmatter(
|
|
6445
|
+
return readFrontmatter(join29(dir, "SKILL.md")).name;
|
|
6311
6446
|
} catch {
|
|
6312
6447
|
return basename9(dir);
|
|
6313
6448
|
}
|
|
@@ -6315,7 +6450,7 @@ function heldSkillNames(skillDirs) {
|
|
|
6315
6450
|
}
|
|
6316
6451
|
function looksLikeMcpSandbox(quarantinePath) {
|
|
6317
6452
|
const candidate = resolveMcpCandidatePath({ quarantinePath });
|
|
6318
|
-
return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync20(
|
|
6453
|
+
return [".mcp.json", "mcp.json", "package.json"].some((f) => existsSync20(join29(candidate, f)));
|
|
6319
6454
|
}
|
|
6320
6455
|
async function promoteHeldMcp(id, entry, config, options) {
|
|
6321
6456
|
const candidatePath = resolveMcpCandidatePath(entry);
|
|
@@ -6349,7 +6484,7 @@ async function promoteHeldMcp(id, entry, config, options) {
|
|
|
6349
6484
|
envVars: detail.env ? Object.keys(detail.env) : void 0,
|
|
6350
6485
|
candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : void 0
|
|
6351
6486
|
};
|
|
6352
|
-
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ??
|
|
6487
|
+
const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join29(process.cwd(), ".mcp.json");
|
|
6353
6488
|
const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });
|
|
6354
6489
|
reject(entry);
|
|
6355
6490
|
logOutcome(`[skill-forge] promoted -> ${result.target} (server "${result.serverName}")`, options.json);
|
|
@@ -6827,17 +6962,17 @@ async function status() {
|
|
|
6827
6962
|
|
|
6828
6963
|
// src/evolve.ts
|
|
6829
6964
|
import { createInterface as createInterface5 } from "readline/promises";
|
|
6830
|
-
import { createHash as
|
|
6831
|
-
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";
|
|
6832
6967
|
import { tmpdir as tmpdir2 } from "os";
|
|
6833
|
-
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";
|
|
6834
6969
|
var SKILLOPT_BIN = "skillopt-sleep";
|
|
6835
6970
|
var PRO_FEATURE_LABEL3 = "self-evolution via SkillOpt-Sleep (evolve) \u2014 re-gating, provenance, and the pending-ingestion queue";
|
|
6836
6971
|
async function evolve(targetSkillDir, options = {}, deps = {}) {
|
|
6837
6972
|
const exec = deps.exec ?? defaultExec;
|
|
6838
6973
|
const json = options.json;
|
|
6839
6974
|
const resolvedTargetDir = resolvePath7(targetSkillDir);
|
|
6840
|
-
const skillMdPath =
|
|
6975
|
+
const skillMdPath = join30(resolvedTargetDir, "SKILL.md");
|
|
6841
6976
|
if (!existsSync22(skillMdPath)) {
|
|
6842
6977
|
throw new UserError(`no SKILL.md found at ${skillMdPath} \u2014 evolve requires an existing, installed skill directory`);
|
|
6843
6978
|
}
|
|
@@ -6896,16 +7031,16 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
|
|
|
6896
7031
|
}
|
|
6897
7032
|
return;
|
|
6898
7033
|
}
|
|
6899
|
-
const stagingRoot =
|
|
7034
|
+
const stagingRoot = join30(project, ".skillopt-sleep", "staging");
|
|
6900
7035
|
const stagingDir = resolvePath7(parsed.staging_dir);
|
|
6901
7036
|
if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep8)) {
|
|
6902
7037
|
throw new UserError(
|
|
6903
7038
|
`skillopt-sleep reported a staging_dir outside the expected sandbox (${stagingRoot}): ${stagingDir} \u2014 refusing to use it`
|
|
6904
7039
|
);
|
|
6905
7040
|
}
|
|
6906
|
-
const proposedSkillMd =
|
|
6907
|
-
const proposedClaudeMd =
|
|
6908
|
-
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");
|
|
6909
7044
|
if (!existsSync22(proposedSkillMd)) {
|
|
6910
7045
|
throw new UserError(`skillopt-sleep reported an accepted proposal but ${proposedSkillMd} is missing`);
|
|
6911
7046
|
}
|
|
@@ -6916,13 +7051,13 @@ async function evolve(targetSkillDir, options = {}, deps = {}) {
|
|
|
6916
7051
|
assertNotSymlink(proposedClaudeMd, "proposed_CLAUDE.md");
|
|
6917
7052
|
const scannedSkillHash = sha256File(proposedSkillMd);
|
|
6918
7053
|
const scannedClaudeHash = sha256File(proposedClaudeMd);
|
|
6919
|
-
const tempDir = mkdtempSync3(
|
|
7054
|
+
const tempDir = mkdtempSync3(join30(tmpdir2(), "skill-forge-evolve-"));
|
|
6920
7055
|
let profile;
|
|
6921
7056
|
let safety;
|
|
6922
7057
|
try {
|
|
6923
|
-
cpSync3(proposedSkillMd,
|
|
7058
|
+
cpSync3(proposedSkillMd, join30(tempDir, "SKILL.md"));
|
|
6924
7059
|
if (existsSync22(proposedClaudeMd)) {
|
|
6925
|
-
cpSync3(proposedClaudeMd,
|
|
7060
|
+
cpSync3(proposedClaudeMd, join30(tempDir, "CLAUDE.md"));
|
|
6926
7061
|
}
|
|
6927
7062
|
profile = asSkillProfile(profileSkill(tempDir), "for the evolved proposal");
|
|
6928
7063
|
const strictness = loadConfig().strictness;
|
|
@@ -7063,7 +7198,7 @@ function validateRunOutput(raw) {
|
|
|
7063
7198
|
return { accepted: Boolean(obj.accepted), staging_dir: stagingDirRaw ?? null };
|
|
7064
7199
|
}
|
|
7065
7200
|
function readManifest(stagingDir) {
|
|
7066
|
-
const manifestPath =
|
|
7201
|
+
const manifestPath = join30(stagingDir, "manifest.json");
|
|
7067
7202
|
let raw;
|
|
7068
7203
|
try {
|
|
7069
7204
|
raw = readFileSync21(manifestPath, "utf-8");
|
|
@@ -7104,8 +7239,8 @@ function filesIdentical(a, b) {
|
|
|
7104
7239
|
return readFileSync21(a).equals(readFileSync21(b));
|
|
7105
7240
|
}
|
|
7106
7241
|
function isAlreadyAdopted(stagingDir, manifest) {
|
|
7107
|
-
const proposedSkillMd =
|
|
7108
|
-
const proposedClaudeMd =
|
|
7242
|
+
const proposedSkillMd = join30(stagingDir, "proposed_SKILL.md");
|
|
7243
|
+
const proposedClaudeMd = join30(stagingDir, "proposed_CLAUDE.md");
|
|
7109
7244
|
let sawComparison = false;
|
|
7110
7245
|
if (existsSync22(proposedSkillMd)) {
|
|
7111
7246
|
sawComparison = true;
|
|
@@ -7120,7 +7255,7 @@ function isAlreadyAdopted(stagingDir, manifest) {
|
|
|
7120
7255
|
function assertNotSymlink(path, label2) {
|
|
7121
7256
|
let stat;
|
|
7122
7257
|
try {
|
|
7123
|
-
stat =
|
|
7258
|
+
stat = lstatSync5(path);
|
|
7124
7259
|
} catch {
|
|
7125
7260
|
return;
|
|
7126
7261
|
}
|
|
@@ -7130,7 +7265,7 @@ function assertNotSymlink(path, label2) {
|
|
|
7130
7265
|
}
|
|
7131
7266
|
function sha256File(path) {
|
|
7132
7267
|
if (!existsSync22(path)) return null;
|
|
7133
|
-
return
|
|
7268
|
+
return createHash4("sha256").update(readFileSync21(path)).digest("hex");
|
|
7134
7269
|
}
|
|
7135
7270
|
|
|
7136
7271
|
// src/commands/evolve.ts
|
|
@@ -7249,7 +7384,7 @@ import { resolve as resolvePath9 } from "path";
|
|
|
7249
7384
|
|
|
7250
7385
|
// src/organize.ts
|
|
7251
7386
|
import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync10, realpathSync as realpathSync7, statSync as statSync10 } from "fs";
|
|
7252
|
-
import { basename as basename11, join as
|
|
7387
|
+
import { basename as basename11, join as join31, resolve as resolvePath8 } from "path";
|
|
7253
7388
|
var PRO_FEATURE_LABEL4 = "set-level organizer reports";
|
|
7254
7389
|
var TIERS = /* @__PURE__ */ new Set(["resource", "custom"]);
|
|
7255
7390
|
var MATURITIES = /* @__PURE__ */ new Set(["seedling", "stable", "deprecated", "experimental"]);
|
|
@@ -7262,7 +7397,7 @@ function listSubdirs2(dir) {
|
|
|
7262
7397
|
}
|
|
7263
7398
|
const out = [];
|
|
7264
7399
|
for (const entry of entries) {
|
|
7265
|
-
const full =
|
|
7400
|
+
const full = join31(dir, entry.name);
|
|
7266
7401
|
if (entry.isDirectory()) {
|
|
7267
7402
|
out.push(full);
|
|
7268
7403
|
continue;
|
|
@@ -7280,14 +7415,14 @@ function findSkillDirsUnderRoot2(root) {
|
|
|
7280
7415
|
const found = /* @__PURE__ */ new Set();
|
|
7281
7416
|
const topDirs = listSubdirs2(root);
|
|
7282
7417
|
for (const dir of topDirs) {
|
|
7283
|
-
if (existsSync24(
|
|
7418
|
+
if (existsSync24(join31(dir, "SKILL.md"))) found.add(dir);
|
|
7284
7419
|
}
|
|
7285
|
-
for (const dir of listSubdirs2(
|
|
7286
|
-
if (existsSync24(
|
|
7420
|
+
for (const dir of listSubdirs2(join31(root, "skills"))) {
|
|
7421
|
+
if (existsSync24(join31(dir, "SKILL.md"))) found.add(dir);
|
|
7287
7422
|
}
|
|
7288
7423
|
for (const topDir of topDirs) {
|
|
7289
|
-
for (const dir of listSubdirs2(
|
|
7290
|
-
if (existsSync24(
|
|
7424
|
+
for (const dir of listSubdirs2(join31(topDir, "skills"))) {
|
|
7425
|
+
if (existsSync24(join31(dir, "SKILL.md"))) found.add(dir);
|
|
7291
7426
|
}
|
|
7292
7427
|
}
|
|
7293
7428
|
return Array.from(found).filter((p) => !p.includes("/.git/"));
|
|
@@ -7490,7 +7625,7 @@ function buildRegistryEntries(candidates, warnings) {
|
|
|
7490
7625
|
}
|
|
7491
7626
|
if (seenRealPaths.has(realPath)) continue;
|
|
7492
7627
|
seenRealPaths.add(realPath);
|
|
7493
|
-
const skillMdPath =
|
|
7628
|
+
const skillMdPath = join31(realPath, "SKILL.md");
|
|
7494
7629
|
let text;
|
|
7495
7630
|
try {
|
|
7496
7631
|
text = readFileSync22(skillMdPath, "utf-8");
|
|
@@ -7900,14 +8035,14 @@ async function find(query, options = {}, deps = {}) {
|
|
|
7900
8035
|
|
|
7901
8036
|
// src/commands/watch.ts
|
|
7902
8037
|
import { realpathSync as realpathSync8 } from "fs";
|
|
7903
|
-
import { join as
|
|
8038
|
+
import { join as join32, resolve as resolvePath10 } from "path";
|
|
7904
8039
|
|
|
7905
8040
|
// src/gate/skillMapDrift.ts
|
|
7906
|
-
import { createHash as
|
|
8041
|
+
import { createHash as createHash5 } from "crypto";
|
|
7907
8042
|
import { readFileSync as readFileSync23, existsSync as existsSync25 } from "fs";
|
|
7908
8043
|
import { dirname as dirname10, isAbsolute as isAbsolute6, resolve as resolve8 } from "path";
|
|
7909
8044
|
function sha256(text) {
|
|
7910
|
-
return
|
|
8045
|
+
return createHash5("sha256").update(text, "utf-8").digest("hex");
|
|
7911
8046
|
}
|
|
7912
8047
|
function shortHash(hash) {
|
|
7913
8048
|
return hash.slice(0, 12);
|
|
@@ -8117,7 +8252,7 @@ async function runWatch(config, options = {}, deps = {}) {
|
|
|
8117
8252
|
ledger = readProvenanceLedger(resolvedRoot);
|
|
8118
8253
|
} catch (err) {
|
|
8119
8254
|
const message = err instanceof Error ? err.message : String(err);
|
|
8120
|
-
const ledgerPath =
|
|
8255
|
+
const ledgerPath = join32(resolvedRoot, "SOURCES.md");
|
|
8121
8256
|
roots.push({ root, realPath, exists, ledgerPath, ledgerExists: false });
|
|
8122
8257
|
warnings.push(`${ledgerPath}: could not read ledger: ${message}`);
|
|
8123
8258
|
continue;
|
|
@@ -8228,7 +8363,7 @@ async function watchCommand(options = {}) {
|
|
|
8228
8363
|
|
|
8229
8364
|
// src/commands/routine.ts
|
|
8230
8365
|
import { existsSync as existsSync26, rmSync as rmSync7, statSync as statSync11 } from "fs";
|
|
8231
|
-
import { join as
|
|
8366
|
+
import { join as join33 } from "path";
|
|
8232
8367
|
var PRO_FEATURE_LABEL6 = "the scheduled maintenance routine";
|
|
8233
8368
|
var SEVERITY_ORDER = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
|
|
8234
8369
|
function parseFailOnFloor(failOn) {
|
|
@@ -8249,7 +8384,7 @@ function exceedsFloor(findings, failOn) {
|
|
|
8249
8384
|
}
|
|
8250
8385
|
function resolveReportPath2(override, generatedAt) {
|
|
8251
8386
|
if (override) return override;
|
|
8252
|
-
return
|
|
8387
|
+
return join33(getHomeDir(), "reports", `routine-${generatedAt.replace(/[:.]/g, "-")}.md`);
|
|
8253
8388
|
}
|
|
8254
8389
|
function runHousekeeping(config, options) {
|
|
8255
8390
|
const actions = [];
|
|
@@ -8258,7 +8393,7 @@ function runHousekeeping(config, options) {
|
|
|
8258
8393
|
const pruneDays = options.pruneDays ?? 30;
|
|
8259
8394
|
const cutoff = Date.now() - pruneDays * 864e5;
|
|
8260
8395
|
for (const id of listQuarantineIds(config.quarantineDir)) {
|
|
8261
|
-
const path =
|
|
8396
|
+
const path = join33(config.quarantineDir, id);
|
|
8262
8397
|
let mtime;
|
|
8263
8398
|
try {
|
|
8264
8399
|
mtime = statSync11(path).mtimeMs;
|
|
@@ -8644,20 +8779,20 @@ async function queueClose(id, options = {}) {
|
|
|
8644
8779
|
import { createInterface as createInterface6 } from "readline/promises";
|
|
8645
8780
|
import {
|
|
8646
8781
|
chmodSync as chmodSync3,
|
|
8647
|
-
lstatSync as
|
|
8648
|
-
mkdirSync as
|
|
8782
|
+
lstatSync as lstatSync7,
|
|
8783
|
+
mkdirSync as mkdirSync10,
|
|
8649
8784
|
readFileSync as readFileSync27,
|
|
8650
8785
|
readSync as readSync3,
|
|
8651
|
-
writeFileSync as
|
|
8786
|
+
writeFileSync as writeFileSync9
|
|
8652
8787
|
} from "fs";
|
|
8653
|
-
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";
|
|
8654
8789
|
|
|
8655
8790
|
// src/insights/inventory.ts
|
|
8656
8791
|
import { readFileSync as readFileSync24 } from "fs";
|
|
8657
|
-
import { join as
|
|
8792
|
+
import { join as join34 } from "path";
|
|
8658
8793
|
|
|
8659
8794
|
// src/insights/models.ts
|
|
8660
|
-
import { createHash as
|
|
8795
|
+
import { createHash as createHash6 } from "crypto";
|
|
8661
8796
|
var INSIGHT_SCHEMA_VERSION = 1;
|
|
8662
8797
|
var MAX_EVIDENCE_EXCERPT_CHARS = 500;
|
|
8663
8798
|
var SOURCE_TYPES = ["url", "file", "directory", "connector", "stdin"];
|
|
@@ -8709,7 +8844,7 @@ function canonicalJson(value) {
|
|
|
8709
8844
|
return JSON.stringify(normalize(value));
|
|
8710
8845
|
}
|
|
8711
8846
|
function stableHash(value) {
|
|
8712
|
-
return
|
|
8847
|
+
return createHash6("sha256").update(canonicalJson(value)).digest("hex");
|
|
8713
8848
|
}
|
|
8714
8849
|
function stableId(prefix, value, length = 16) {
|
|
8715
8850
|
if (!/^[A-Z][A-Z0-9-]*$/.test(prefix)) {
|
|
@@ -8768,7 +8903,7 @@ function declaredMapCoverage(path, claim) {
|
|
|
8768
8903
|
}
|
|
8769
8904
|
function fingerprintInstalledSkill(path) {
|
|
8770
8905
|
try {
|
|
8771
|
-
return { fingerprint: stableHash(readFileSync24(
|
|
8906
|
+
return { fingerprint: stableHash(readFileSync24(join34(path, "SKILL.md"), "utf-8")) };
|
|
8772
8907
|
} catch (error) {
|
|
8773
8908
|
return { reason: `could not fingerprint SKILL.md: ${error.message}` };
|
|
8774
8909
|
}
|
|
@@ -8919,26 +9054,26 @@ function buildInsightInventory(config, options = {}) {
|
|
|
8919
9054
|
|
|
8920
9055
|
// src/insights/jira.ts
|
|
8921
9056
|
import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
|
|
8922
|
-
import { join as
|
|
9057
|
+
import { join as join37 } from "path";
|
|
8923
9058
|
|
|
8924
9059
|
// src/insights/store.ts
|
|
8925
|
-
import { chmodSync as chmodSync2, existsSync as existsSync27, mkdirSync as
|
|
8926
|
-
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";
|
|
8927
9062
|
|
|
8928
9063
|
// src/insights/sources.ts
|
|
8929
|
-
import { createHash as
|
|
9064
|
+
import { createHash as createHash7 } from "crypto";
|
|
8930
9065
|
import {
|
|
8931
|
-
closeSync as
|
|
8932
|
-
constants as
|
|
9066
|
+
closeSync as closeSync4,
|
|
9067
|
+
constants as constants3,
|
|
8933
9068
|
fstatSync as fstatSync2,
|
|
8934
|
-
lstatSync as
|
|
8935
|
-
openSync as
|
|
9069
|
+
lstatSync as lstatSync6,
|
|
9070
|
+
openSync as openSync4,
|
|
8936
9071
|
readSync as readSync2,
|
|
8937
9072
|
readdirSync as readdirSync11,
|
|
8938
9073
|
realpathSync as realpathSync10,
|
|
8939
9074
|
statSync as statSync12
|
|
8940
9075
|
} from "fs";
|
|
8941
|
-
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";
|
|
8942
9077
|
var DEFAULT_MAX_SOURCE_FILE_BYTES = 5 * 1024 * 1024;
|
|
8943
9078
|
var DEFAULT_MAX_SOURCE_TOTAL_BYTES = 25 * 1024 * 1024;
|
|
8944
9079
|
var SKIP_DIRS4 = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", ".next", "out", "__pycache__", ".venv", "venv"]);
|
|
@@ -9037,12 +9172,12 @@ function observeFile(path, relativePath, declaredMediaType, maxFileBytes, remain
|
|
|
9037
9172
|
}
|
|
9038
9173
|
const mediaType = mediaTypeFor(path, declaredMediaType);
|
|
9039
9174
|
if (!mediaType) return { path: relativePath, reason: "unsupported-media-type" };
|
|
9040
|
-
const noFollow = typeof
|
|
9175
|
+
const noFollow = typeof constants3.O_NOFOLLOW === "number" ? constants3.O_NOFOLLOW : 0;
|
|
9041
9176
|
let descriptor;
|
|
9042
9177
|
let bytes;
|
|
9043
9178
|
let descriptorStat;
|
|
9044
9179
|
try {
|
|
9045
|
-
descriptor =
|
|
9180
|
+
descriptor = openSync4(path, constants3.O_RDONLY | noFollow);
|
|
9046
9181
|
descriptorStat = fstatSync2(descriptor);
|
|
9047
9182
|
if (!descriptorStat.isFile()) return { path: relativePath, reason: "unsupported-media-type" };
|
|
9048
9183
|
if (descriptorStat.size > maxFileBytes) {
|
|
@@ -9069,7 +9204,7 @@ function observeFile(path, relativePath, declaredMediaType, maxFileBytes, remain
|
|
|
9069
9204
|
} catch (error) {
|
|
9070
9205
|
throw new UserError(`unable to read source file ${relativePath}: ${error.message}`);
|
|
9071
9206
|
} finally {
|
|
9072
|
-
if (descriptor !== void 0)
|
|
9207
|
+
if (descriptor !== void 0) closeSync4(descriptor);
|
|
9073
9208
|
}
|
|
9074
9209
|
if (!bytes || !descriptorStat) throw new UserError(`source file could not be observed: ${relativePath}`);
|
|
9075
9210
|
let realPath;
|
|
@@ -9089,7 +9224,7 @@ function observeFile(path, relativePath, declaredMediaType, maxFileBytes, remain
|
|
|
9089
9224
|
realPath,
|
|
9090
9225
|
relativePath,
|
|
9091
9226
|
byteLength: bytes.byteLength,
|
|
9092
|
-
sha256:
|
|
9227
|
+
sha256: createHash7("sha256").update(bytes).digest("hex"),
|
|
9093
9228
|
mediaType,
|
|
9094
9229
|
suspiciousInstructionFlags: suspiciousInstructionFlags(bytes, mediaType),
|
|
9095
9230
|
bytes
|
|
@@ -9173,7 +9308,7 @@ function normalizeStdin(input, retrievedAt, maxFileBytes, remainingBytes) {
|
|
|
9173
9308
|
const bytes = Buffer.from(input.content);
|
|
9174
9309
|
if (bytes.byteLength > maxFileBytes) throw new UserError(`stdin source exceeds the per-source byte limit (${maxFileBytes})`);
|
|
9175
9310
|
if (bytes.byteLength > remainingBytes) throw new UserError(`stdin source exceeds the total source byte limit`);
|
|
9176
|
-
const contentSha256 =
|
|
9311
|
+
const contentSha256 = createHash7("sha256").update(bytes).digest("hex");
|
|
9177
9312
|
const mediaType = input.mediaType ?? "text/plain";
|
|
9178
9313
|
const fingerprint = stableHash({ type: input.type, contentSha256, mediaType });
|
|
9179
9314
|
const common = normalizedCommon(input, retrievedAt);
|
|
@@ -9199,7 +9334,7 @@ function normalizeFile(input, retrievedAt, maxFileBytes, remainingBytes) {
|
|
|
9199
9334
|
const resolved = resolve9(input.path);
|
|
9200
9335
|
let lexicalStat;
|
|
9201
9336
|
try {
|
|
9202
|
-
lexicalStat =
|
|
9337
|
+
lexicalStat = lstatSync6(resolved);
|
|
9203
9338
|
} catch (err) {
|
|
9204
9339
|
throw new UserError(`unable to read source file ${resolved}: ${err.message}`);
|
|
9205
9340
|
}
|
|
@@ -9250,7 +9385,7 @@ function normalizeDirectory(input, retrievedAt, maxFileBytes, remainingBytes) {
|
|
|
9250
9385
|
const resolved = resolve9(input.path);
|
|
9251
9386
|
let lexicalStat;
|
|
9252
9387
|
try {
|
|
9253
|
-
lexicalStat =
|
|
9388
|
+
lexicalStat = lstatSync6(resolved);
|
|
9254
9389
|
} catch (err) {
|
|
9255
9390
|
throw new UserError(`unable to read source directory ${resolved}: ${err.message}`);
|
|
9256
9391
|
}
|
|
@@ -9270,7 +9405,7 @@ function normalizeDirectory(input, retrievedAt, maxFileBytes, remainingBytes) {
|
|
|
9270
9405
|
throw new UserError(`unable to enumerate source directory ${dir}: ${err.message}`);
|
|
9271
9406
|
}
|
|
9272
9407
|
for (const entry of entries) {
|
|
9273
|
-
const full =
|
|
9408
|
+
const full = join35(dir, entry.name);
|
|
9274
9409
|
const logicalPath = logicalDir ? `${logicalDir}/${entry.name}` : entry.name;
|
|
9275
9410
|
if (entry.isDirectory() && SKIP_DIRS4.has(entry.name)) {
|
|
9276
9411
|
skipped.push({ path: logicalPath, reason: "ignored-directory" });
|
|
@@ -9336,7 +9471,7 @@ function normalizeDirectory(input, retrievedAt, maxFileBytes, remainingBytes) {
|
|
|
9336
9471
|
walk2(realRoot, "");
|
|
9337
9472
|
observations.sort((a, b) => compareStrings2(a.relativePath, b.relativePath));
|
|
9338
9473
|
skipped.sort((a, b) => compareStrings2(a.path, b.path) || compareStrings2(a.reason, b.reason));
|
|
9339
|
-
const contentSha256 =
|
|
9474
|
+
const contentSha256 = createHash7("sha256");
|
|
9340
9475
|
for (const file of observations) contentSha256.update(`${file.relativePath}\0${file.sha256}
|
|
9341
9476
|
`);
|
|
9342
9477
|
const aggregateHash = contentSha256.digest("hex");
|
|
@@ -9422,17 +9557,17 @@ function normalizeSources(inputs, options = {}) {
|
|
|
9422
9557
|
// src/insights/store.ts
|
|
9423
9558
|
var STUDY_ID = /^STUDY-[0-9a-f]{16}$/;
|
|
9424
9559
|
function getInsightsDir() {
|
|
9425
|
-
return
|
|
9560
|
+
return join36(getHomeDir(), "insights");
|
|
9426
9561
|
}
|
|
9427
9562
|
function getStudyDir(studyId) {
|
|
9428
9563
|
assertStudyId(studyId);
|
|
9429
|
-
return
|
|
9564
|
+
return join36(getInsightsDir(), studyId);
|
|
9430
9565
|
}
|
|
9431
9566
|
function getStudyPath(studyId) {
|
|
9432
|
-
return
|
|
9567
|
+
return join36(getStudyDir(studyId), "study.json");
|
|
9433
9568
|
}
|
|
9434
9569
|
function getStudyEventsPath(studyId) {
|
|
9435
|
-
return
|
|
9570
|
+
return join36(getStudyDir(studyId), "events.jsonl");
|
|
9436
9571
|
}
|
|
9437
9572
|
function createStudyId(sourceInventoryHash, createdAt) {
|
|
9438
9573
|
return stableId("STUDY", { sourceInventoryHash, createdAt });
|
|
@@ -9441,7 +9576,7 @@ function assertStudyId(studyId) {
|
|
|
9441
9576
|
if (!STUDY_ID.test(studyId)) throw new UserError(`invalid insight study id: ${studyId}`);
|
|
9442
9577
|
}
|
|
9443
9578
|
function ensurePrivateDir(path) {
|
|
9444
|
-
|
|
9579
|
+
mkdirSync9(path, { recursive: true, mode: 448 });
|
|
9445
9580
|
chmodSync2(path, 448);
|
|
9446
9581
|
}
|
|
9447
9582
|
function isRecord2(value) {
|
|
@@ -10365,7 +10500,7 @@ function buildJiraManifest(study, projectKey, options = {}) {
|
|
|
10365
10500
|
return manifest;
|
|
10366
10501
|
}
|
|
10367
10502
|
function getJiraReceiptsPath(studyId) {
|
|
10368
|
-
return
|
|
10503
|
+
return join37(getStudyDir(studyId), "jira-receipts.jsonl");
|
|
10369
10504
|
}
|
|
10370
10505
|
function assertCurrentJiraManifest(study, value) {
|
|
10371
10506
|
if (study.status !== "jira-manifested" || !study.jira) {
|
|
@@ -10771,7 +10906,7 @@ function positionalSource(value) {
|
|
|
10771
10906
|
const resolved = resolvePath12(value);
|
|
10772
10907
|
let stat;
|
|
10773
10908
|
try {
|
|
10774
|
-
stat =
|
|
10909
|
+
stat = lstatSync7(resolved);
|
|
10775
10910
|
} catch (error) {
|
|
10776
10911
|
throw new UserError(`unable to read source ${resolved}: ${error.message}`);
|
|
10777
10912
|
}
|
|
@@ -10787,10 +10922,10 @@ function collectSourceInputs(sourceArgs, options) {
|
|
|
10787
10922
|
return inputs;
|
|
10788
10923
|
}
|
|
10789
10924
|
function contextPath(studyId) {
|
|
10790
|
-
return
|
|
10925
|
+
return join38(getStudyDir(studyId), CONTEXT_FILENAME);
|
|
10791
10926
|
}
|
|
10792
10927
|
function inventoryPath(studyId) {
|
|
10793
|
-
return
|
|
10928
|
+
return join38(getStudyDir(studyId), INVENTORY_FILENAME);
|
|
10794
10929
|
}
|
|
10795
10930
|
function writeCommandContext(studyId, skillMapPath, skillMapCoverage, inventory) {
|
|
10796
10931
|
const context = {
|
|
@@ -10834,17 +10969,17 @@ function retainedPath(root, relativePath) {
|
|
|
10834
10969
|
}
|
|
10835
10970
|
function retainObservedSources(study, observedFiles) {
|
|
10836
10971
|
if (observedFiles.length === 0) return study;
|
|
10837
|
-
const root =
|
|
10838
|
-
|
|
10972
|
+
const root = join38(getStudyDir(study.studyId), "retained-sources");
|
|
10973
|
+
mkdirSync10(root, { recursive: true, mode: 448 });
|
|
10839
10974
|
chmodSync3(root, 448);
|
|
10840
10975
|
const retainedSourceIds = /* @__PURE__ */ new Set();
|
|
10841
10976
|
for (const file of observedFiles) {
|
|
10842
|
-
const sourceRoot =
|
|
10843
|
-
|
|
10977
|
+
const sourceRoot = join38(root, file.sourceId);
|
|
10978
|
+
mkdirSync10(sourceRoot, { recursive: true, mode: 448 });
|
|
10844
10979
|
chmodSync3(sourceRoot, 448);
|
|
10845
10980
|
const destination = retainedPath(sourceRoot, file.relativePath);
|
|
10846
|
-
|
|
10847
|
-
|
|
10981
|
+
mkdirSync10(dirname13(destination), { recursive: true, mode: 448 });
|
|
10982
|
+
writeFileSync9(destination, file.bytes, { flag: "wx", mode: 384 });
|
|
10848
10983
|
chmodSync3(destination, 384);
|
|
10849
10984
|
retainedSourceIds.add(file.sourceId);
|
|
10850
10985
|
}
|
|
@@ -11077,7 +11212,7 @@ async function insightJira(studyId, options) {
|
|
|
11077
11212
|
throw new UserError(`study ${studyId} cannot generate a Jira manifest: ${validation.blockers.map((issue) => issue.message).join("; ")}`);
|
|
11078
11213
|
}
|
|
11079
11214
|
if (localMode) {
|
|
11080
|
-
const privatePath2 =
|
|
11215
|
+
const privatePath2 = join38(getStudyDir(studyId), JIRA_MANIFEST_FILENAME);
|
|
11081
11216
|
const manifest2 = assertCurrentJiraManifest(study, parseJsonFile(privatePath2, "stored Jira manifest"));
|
|
11082
11217
|
const receipt = options.receipt === void 0 ? void 0 : appendJiraReceipt(studyId, manifest2, parseJiraReceiptInput(parseJsonFile(options.receipt, "Jira receipt import")));
|
|
11083
11218
|
const resume = buildJiraResumeState(manifest2, readJiraReceipts(studyId, manifest2));
|
|
@@ -11089,7 +11224,7 @@ async function insightJira(studyId, options) {
|
|
|
11089
11224
|
return;
|
|
11090
11225
|
}
|
|
11091
11226
|
const manifest = buildJiraManifest(study, projectKey);
|
|
11092
|
-
const privatePath =
|
|
11227
|
+
const privatePath = join38(getStudyDir(studyId), JIRA_MANIFEST_FILENAME);
|
|
11093
11228
|
writeJsonAtomic(privatePath, manifest);
|
|
11094
11229
|
if (options.out) {
|
|
11095
11230
|
writeNewFile(resolvePath12(options.out), `${JSON.stringify(manifest, null, 2)}
|
|
@@ -11163,8 +11298,8 @@ async function insightClose(studyId, options) {
|
|
|
11163
11298
|
import { tmpdir as tmpdir3 } from "os";
|
|
11164
11299
|
|
|
11165
11300
|
// src/refine/activation.ts
|
|
11166
|
-
import { lstatSync as
|
|
11167
|
-
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";
|
|
11168
11303
|
|
|
11169
11304
|
// src/refine/models.ts
|
|
11170
11305
|
import { randomBytes } from "crypto";
|
|
@@ -11527,8 +11662,8 @@ function activationFiles(baseDir, overrideDir, name) {
|
|
|
11527
11662
|
function walk2(dir, prefix = "") {
|
|
11528
11663
|
for (const entry of readdirSync12(dir).sort()) {
|
|
11529
11664
|
if (entry === ".git" || entry === "node_modules") continue;
|
|
11530
|
-
const path =
|
|
11531
|
-
const stat =
|
|
11665
|
+
const path = join39(dir, entry), relativePath = prefix ? `${prefix}/${entry}` : entry;
|
|
11666
|
+
const stat = lstatSync8(path);
|
|
11532
11667
|
if (stat.isSymbolicLink()) throw new UserError(`Activation refuses symlinks: ${path}`);
|
|
11533
11668
|
if (stat.isDirectory()) {
|
|
11534
11669
|
walk2(path, relativePath);
|
|
@@ -11553,8 +11688,8 @@ function activationFiles(baseDir, overrideDir, name) {
|
|
|
11553
11688
|
}
|
|
11554
11689
|
let content = skill.bytes.toString("utf8");
|
|
11555
11690
|
for (const artifact of artifacts) {
|
|
11556
|
-
const path =
|
|
11557
|
-
const stat =
|
|
11691
|
+
const path = join39(overrideDir, artifact);
|
|
11692
|
+
const stat = lstatSync8(path);
|
|
11558
11693
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new UserError("Override artifact must be a regular file, not a symlink.");
|
|
11559
11694
|
if (stat.size > 2 * 1024 * 1024) throw new UserError("Override artifact exceeds 2 MiB.");
|
|
11560
11695
|
const text = readFileSync28(path, "utf8");
|
|
@@ -11584,32 +11719,32 @@ ${content.slice(frontmatter[0].length)}`;
|
|
|
11584
11719
|
}
|
|
11585
11720
|
|
|
11586
11721
|
// src/commands/refine.ts
|
|
11587
|
-
import { createHash as
|
|
11588
|
-
import { chmodSync as chmodSync4, existsSync as existsSync31, lstatSync as
|
|
11589
|
-
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";
|
|
11590
11725
|
import { createInterface as createInterface7 } from "readline/promises";
|
|
11591
11726
|
|
|
11592
11727
|
// src/refine/context.ts
|
|
11593
11728
|
import { existsSync as existsSync30, realpathSync as realpathSync13, statSync as statSync14 } from "fs";
|
|
11594
|
-
import { isAbsolute as isAbsolute10, join as
|
|
11729
|
+
import { isAbsolute as isAbsolute10, join as join41, relative as relative9, resolve as resolvePath14 } from "path";
|
|
11595
11730
|
|
|
11596
11731
|
// src/refine/store.ts
|
|
11597
|
-
import { createHash as
|
|
11732
|
+
import { createHash as createHash8 } from "crypto";
|
|
11598
11733
|
import { existsSync as existsSync29, readFileSync as readFileSync29, statSync as statSync13, realpathSync as realpathSync12 } from "fs";
|
|
11599
|
-
import { join as
|
|
11734
|
+
import { join as join40, resolve as resolvePath13 } from "path";
|
|
11600
11735
|
var SIMILARITY_JOIN_THRESHOLD = 0.7;
|
|
11601
11736
|
var READY_THRESHOLD = 2;
|
|
11602
11737
|
function getRefinementsDir() {
|
|
11603
|
-
return
|
|
11738
|
+
return join40(getHomeDir(), "refinements");
|
|
11604
11739
|
}
|
|
11605
11740
|
function getRefinementsPath() {
|
|
11606
|
-
return
|
|
11741
|
+
return join40(getRefinementsDir(), "refinements.json");
|
|
11607
11742
|
}
|
|
11608
11743
|
function getPatternsPath() {
|
|
11609
|
-
return
|
|
11744
|
+
return join40(getRefinementsDir(), "patterns.json");
|
|
11610
11745
|
}
|
|
11611
11746
|
function getBackupsDir() {
|
|
11612
|
-
return
|
|
11747
|
+
return join40(getRefinementsDir(), "backups");
|
|
11613
11748
|
}
|
|
11614
11749
|
function readJsonFile(path, label2) {
|
|
11615
11750
|
let raw;
|
|
@@ -11854,11 +11989,11 @@ function createBackup(filePaths, deps = {}) {
|
|
|
11854
11989
|
}
|
|
11855
11990
|
const bytes = readFileSync29(path);
|
|
11856
11991
|
const mode = statSync13(path).mode & 511;
|
|
11857
|
-
const sha2562 =
|
|
11992
|
+
const sha2562 = createHash8("sha256").update(bytes).digest("hex");
|
|
11858
11993
|
return { path, existed: true, sha256: sha2562, mode, contentBase64: bytes.toString("base64") };
|
|
11859
11994
|
});
|
|
11860
11995
|
const manifest = { id, createdAt: now.toISOString(), files };
|
|
11861
|
-
writeJsonAtomic(
|
|
11996
|
+
writeJsonAtomic(join40(getBackupsDir(), id, "manifest.json"), manifest);
|
|
11862
11997
|
return manifest;
|
|
11863
11998
|
}
|
|
11864
11999
|
function validateBackupManifestShape(value, path) {
|
|
@@ -11907,7 +12042,7 @@ function readBackupManifest(id) {
|
|
|
11907
12042
|
if (!isValidBackupId(id)) {
|
|
11908
12043
|
throw new UserError(`invalid backup id "${id}" \u2014 expected the format produced by \`skill-forge refine promote\` (e.g. 20260720T142305Z-a1b2)`);
|
|
11909
12044
|
}
|
|
11910
|
-
const manifestPath =
|
|
12045
|
+
const manifestPath = join40(getBackupsDir(), id, "manifest.json");
|
|
11911
12046
|
if (!existsSync29(manifestPath)) {
|
|
11912
12047
|
throw new UserError(`no backup with id: ${id}`);
|
|
11913
12048
|
}
|
|
@@ -11942,8 +12077,8 @@ function isUnderRoot2(candidate, root) {
|
|
|
11942
12077
|
function resolveScopes(config, cwd, skill, userRootFlag) {
|
|
11943
12078
|
assertPlainSkillName(skill);
|
|
11944
12079
|
const resolvedCwd = resolvePath14(cwd);
|
|
11945
|
-
const localDir =
|
|
11946
|
-
const sharedDir =
|
|
12080
|
+
const localDir = join41(resolvedCwd, ".claude", "skills", skill);
|
|
12081
|
+
const sharedDir = join41(resolvedCwd, "skills", skill);
|
|
11947
12082
|
const local = { scope: "local", dir: localDir, exists: existsSync30(localDir) };
|
|
11948
12083
|
const shared = { scope: "shared", dir: sharedDir, exists: existsSync30(sharedDir) };
|
|
11949
12084
|
const user = resolveUserScope(config, resolvedCwd, skill, userRootFlag);
|
|
@@ -11952,10 +12087,10 @@ function resolveScopes(config, cwd, skill, userRootFlag) {
|
|
|
11952
12087
|
function resolveUserScope(config, resolvedCwd, skill, userRootFlag) {
|
|
11953
12088
|
if (userRootFlag) {
|
|
11954
12089
|
const root = resolvePath14(userRootFlag);
|
|
11955
|
-
return { resolved: true, dir:
|
|
12090
|
+
return { resolved: true, dir: join41(root, skill), root: userRootFlag, reason: "flag" };
|
|
11956
12091
|
}
|
|
11957
12092
|
const cwdReal = realpathOrNull2(resolvedCwd) ?? resolvedCwd;
|
|
11958
|
-
const candidates = config.skillsRoots.map((root) => ({ root, dir:
|
|
12093
|
+
const candidates = config.skillsRoots.map((root) => ({ root, dir: join41(resolveConfiguredRoot2(root), skill) }));
|
|
11959
12094
|
for (const candidate of candidates) {
|
|
11960
12095
|
const skillReal = realpathOrNull2(candidate.dir);
|
|
11961
12096
|
if (skillReal) {
|
|
@@ -11990,12 +12125,12 @@ function inventoryScopeDir(scope2, dir) {
|
|
|
11990
12125
|
scope: scope2,
|
|
11991
12126
|
dir,
|
|
11992
12127
|
dirExists: isDirectory(dir),
|
|
11993
|
-
skillMd: isFile2(
|
|
11994
|
-
patchMd: isFile2(
|
|
11995
|
-
extendMd: isFile2(
|
|
11996
|
-
configJson: isFile2(
|
|
11997
|
-
hooksDir: isDirectory(
|
|
11998
|
-
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"))
|
|
11999
12134
|
};
|
|
12000
12135
|
}
|
|
12001
12136
|
function inventoryAllScopes(resolution) {
|
|
@@ -12181,14 +12316,14 @@ ${rendered}` : rendered;
|
|
|
12181
12316
|
function ensureScopeDirSafe(scopeDir, resolvedCwd) {
|
|
12182
12317
|
let lst;
|
|
12183
12318
|
try {
|
|
12184
|
-
lst =
|
|
12319
|
+
lst = lstatSync9(scopeDir);
|
|
12185
12320
|
} catch {
|
|
12186
12321
|
lst = void 0;
|
|
12187
12322
|
}
|
|
12188
12323
|
if (lst?.isSymbolicLink()) {
|
|
12189
12324
|
throw new UserError(`refusing to write under ${scopeDir}: scope directory is a symlink`);
|
|
12190
12325
|
}
|
|
12191
|
-
|
|
12326
|
+
mkdirSync12(scopeDir, { recursive: true });
|
|
12192
12327
|
const real = resolveConfiguredRoot3(scopeDir);
|
|
12193
12328
|
const lexical = resolvePath15(scopeDir);
|
|
12194
12329
|
let realResolved;
|
|
@@ -12207,23 +12342,23 @@ function ensureScopeDirSafe(scopeDir, resolvedCwd) {
|
|
|
12207
12342
|
}
|
|
12208
12343
|
function writeOverrideFile(scopeDir, relPath, content, resolvedCwd) {
|
|
12209
12344
|
const realScopeDir = ensureScopeDirSafe(scopeDir, resolvedCwd);
|
|
12210
|
-
const destPath =
|
|
12345
|
+
const destPath = join42(scopeDir, relPath);
|
|
12211
12346
|
const destDir = dirname15(destPath);
|
|
12212
|
-
|
|
12347
|
+
mkdirSync12(destDir, { recursive: true });
|
|
12213
12348
|
const realDestDir = realpathSync14(destDir);
|
|
12214
12349
|
if (!isUnderRoot3(realDestDir, realScopeDir)) {
|
|
12215
12350
|
throw new UserError(`refusing to write ${destPath}: resolves outside scope directory ${scopeDir}`);
|
|
12216
12351
|
}
|
|
12217
12352
|
let destLst;
|
|
12218
12353
|
try {
|
|
12219
|
-
destLst =
|
|
12354
|
+
destLst = lstatSync9(destPath);
|
|
12220
12355
|
} catch {
|
|
12221
12356
|
destLst = void 0;
|
|
12222
12357
|
}
|
|
12223
12358
|
if (destLst?.isSymbolicLink()) {
|
|
12224
12359
|
throw new UserError(`refusing to write ${destPath}: destination is a symlink`);
|
|
12225
12360
|
}
|
|
12226
|
-
|
|
12361
|
+
writeFileSync11(destPath, content, { mode: 384 });
|
|
12227
12362
|
chmodSync4(destPath, 384);
|
|
12228
12363
|
return destPath;
|
|
12229
12364
|
}
|
|
@@ -12303,11 +12438,11 @@ Write ${preview.destPath}? [y/N] `);
|
|
|
12303
12438
|
}
|
|
12304
12439
|
function renderCaptureArtifactPreview(opts, content, refinementId, generatedAt, scopeDir) {
|
|
12305
12440
|
const probeRelPath = opts.overrideType === "patch" ? "SKILL.patch.md" : opts.overrideType === "extend" || opts.overrideType === "new" ? "SKILL.extend.md" : void 0;
|
|
12306
|
-
const probePath = probeRelPath ?
|
|
12441
|
+
const probePath = probeRelPath ? join42(scopeDir, probeRelPath) : void 0;
|
|
12307
12442
|
const existingContent = probePath && existsSync31(probePath) ? readFileSync30(probePath, "utf-8") : "";
|
|
12308
12443
|
const rendered = renderCaptureArtifact(opts, content, refinementId, generatedAt, existingContent);
|
|
12309
12444
|
const relPath = rendered.relPath;
|
|
12310
|
-
const destPath =
|
|
12445
|
+
const destPath = join42(scopeDir, relPath);
|
|
12311
12446
|
const before = existsSync31(destPath) ? readFileSync30(destPath, "utf-8") : "";
|
|
12312
12447
|
const diff = previewDiff(before, rendered.content, destPath);
|
|
12313
12448
|
return { relPath, content: rendered.content, destPath, diff };
|
|
@@ -12323,8 +12458,8 @@ async function refineActivateCommand(options) {
|
|
|
12323
12458
|
const scopes = resolveScopes(loadConfig(), cwd, options.skill);
|
|
12324
12459
|
const overrideDir = options.scope === "shared" ? scopes.shared.dir : scopes.local.dir;
|
|
12325
12460
|
if (realpathSync14(overrideDir) !== overrideDir) throw new UserError("Activation refuses a symlinked override directory.");
|
|
12326
|
-
const hostRoot =
|
|
12327
|
-
const destination =
|
|
12461
|
+
const hostRoot = join42(cwd, PROJECT_SKILL_ROOTS[options.host]);
|
|
12462
|
+
const destination = join42(hostRoot, options.as);
|
|
12328
12463
|
if (existsSync31(destination) || resolveTargetRealPath(destination) !== destination) throw new UserError("Activation destination already exists or contains a symlink; choose a new --as name.");
|
|
12329
12464
|
const files = activationFiles(options.base, overrideDir, options.as);
|
|
12330
12465
|
const receiptName = ".skill-forge-activation.json";
|
|
@@ -12335,7 +12470,7 @@ async function refineActivateCommand(options) {
|
|
|
12335
12470
|
skill: options.as,
|
|
12336
12471
|
capture: options.skill,
|
|
12337
12472
|
source: realpathSync14(options.base),
|
|
12338
|
-
sourceSha256:
|
|
12473
|
+
sourceSha256: createHash9("sha256").update(readFileSync30(join42(options.base, "SKILL.md"))).digest("hex"),
|
|
12339
12474
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12340
12475
|
runtimeVerified: false
|
|
12341
12476
|
};
|
|
@@ -12343,15 +12478,15 @@ async function refineActivateCommand(options) {
|
|
|
12343
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) {
|
|
12344
12479
|
throw new UserError("Composed activation exceeds file or total size limits.");
|
|
12345
12480
|
}
|
|
12346
|
-
const stage = mkdtempSync4(
|
|
12481
|
+
const stage = mkdtempSync4(join42(tmpdir3(), "skill-forge-activate-"));
|
|
12347
12482
|
try {
|
|
12348
12483
|
for (const file of files) {
|
|
12349
|
-
const path =
|
|
12350
|
-
|
|
12351
|
-
|
|
12484
|
+
const path = join42(stage, file.relativePath);
|
|
12485
|
+
mkdirSync12(dirname15(path), { recursive: true });
|
|
12486
|
+
writeFileSync11(path, file.bytes, { mode: file.mode });
|
|
12352
12487
|
}
|
|
12353
|
-
const safety = await scanSafety(stage, "block-high", files.map((f) => ({ file:
|
|
12354
|
-
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)) }));
|
|
12355
12490
|
const preview = {
|
|
12356
12491
|
destination,
|
|
12357
12492
|
provenance,
|
|
@@ -12360,7 +12495,7 @@ async function refineActivateCommand(options) {
|
|
|
12360
12495
|
source: realpathSync14(options.base),
|
|
12361
12496
|
files: files.map((f) => f.relativePath),
|
|
12362
12497
|
safety,
|
|
12363
|
-
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"))
|
|
12364
12499
|
};
|
|
12365
12500
|
if (options.dryRun) {
|
|
12366
12501
|
emit2(options.json, { ...preview, dryRun: true, activated: false }, () => console.log(JSON.stringify(preview, null, 2)));
|
|
@@ -12374,15 +12509,15 @@ async function refineActivateCommand(options) {
|
|
|
12374
12509
|
}
|
|
12375
12510
|
if (resolveTargetRealPath(destination) !== destination) throw new UserError("Activation destination became symlinked.");
|
|
12376
12511
|
ensureScopeDirSafe(hostRoot, cwd);
|
|
12377
|
-
|
|
12378
|
-
const backup = createBackup(files.map((f) =>
|
|
12379
|
-
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") }));
|
|
12380
12515
|
writePostWriteSidecar(backup.id, hashes);
|
|
12381
12516
|
const projectConfig = { ...loadConfig(), skillsRoots: [hostRoot] };
|
|
12382
12517
|
try {
|
|
12383
|
-
for (const file of files) safeRestoreWrite(
|
|
12518
|
+
for (const file of files) safeRestoreWrite(join42(destination, file.relativePath), file.bytes, file.mode, projectConfig);
|
|
12384
12519
|
for (const file of hashes) {
|
|
12385
|
-
if (
|
|
12520
|
+
if (createHash9("sha256").update(readFileSync30(file.path)).digest("hex") !== file.sha256) throw new UserError("Activation read-back hash mismatch.");
|
|
12386
12521
|
}
|
|
12387
12522
|
} catch (error) {
|
|
12388
12523
|
throw new UserError(`Activation write failed; backup ${backup.id} records recovery paths. Inspect before using rollback --project --force: ${error.message}`);
|
|
@@ -12392,7 +12527,7 @@ async function refineActivateCommand(options) {
|
|
|
12392
12527
|
activated: true,
|
|
12393
12528
|
backupId: backup.id,
|
|
12394
12529
|
runtimeVerified: false,
|
|
12395
|
-
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")}.`,
|
|
12396
12531
|
rollback: `skill-forge refine rollback ${backup.id} --project --yes`
|
|
12397
12532
|
}, () => {
|
|
12398
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.`);
|
|
@@ -12495,12 +12630,12 @@ async function refineWhichCommand(skill, options = {}) {
|
|
|
12495
12630
|
}
|
|
12496
12631
|
function writePostWriteSidecar(backupId, files) {
|
|
12497
12632
|
const sidecar = { schemaVersion: 1, files };
|
|
12498
|
-
const dir =
|
|
12499
|
-
|
|
12500
|
-
|
|
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 });
|
|
12501
12636
|
}
|
|
12502
12637
|
function readPostWriteSidecar(backupId) {
|
|
12503
|
-
const path =
|
|
12638
|
+
const path = join42(getBackupsDir(), backupId, "post.json");
|
|
12504
12639
|
if (!existsSync31(path)) return { hashes: /* @__PURE__ */ new Map(), status: "missing" };
|
|
12505
12640
|
let raw;
|
|
12506
12641
|
try {
|
|
@@ -12562,7 +12697,7 @@ async function refinePromoteCommand(patternId, options = {}) {
|
|
|
12562
12697
|
if (!isUnderRoot3(realSkillDir, realUserRoot)) {
|
|
12563
12698
|
throw new UserError(`refusing to promote: resolved skill directory ${skillDir} is outside the configured user scope root ${scopes.user.root}`);
|
|
12564
12699
|
}
|
|
12565
|
-
const skillMdPath =
|
|
12700
|
+
const skillMdPath = join42(skillDir, "SKILL.md");
|
|
12566
12701
|
if (!existsSync31(skillMdPath)) {
|
|
12567
12702
|
throw new UserError(`user-scope SKILL.md not found at ${skillMdPath} \u2014 refusing to promote against a missing base skill.`);
|
|
12568
12703
|
}
|
|
@@ -12603,16 +12738,16 @@ Promote pattern ${patternId} into ${skillMdPath}? [y/N] `);
|
|
|
12603
12738
|
return;
|
|
12604
12739
|
}
|
|
12605
12740
|
const backup = createBackup([skillMdPath]);
|
|
12606
|
-
const tempPath = `${skillMdPath}.tmp-${
|
|
12741
|
+
const tempPath = `${skillMdPath}.tmp-${randomUUID6()}`;
|
|
12607
12742
|
try {
|
|
12608
|
-
|
|
12743
|
+
writeFileSync11(tempPath, after, "utf-8");
|
|
12609
12744
|
renameSync6(tempPath, skillMdPath);
|
|
12610
12745
|
} catch (err) {
|
|
12611
12746
|
rmSync9(tempPath, { force: true });
|
|
12612
12747
|
throw err;
|
|
12613
12748
|
}
|
|
12614
12749
|
try {
|
|
12615
|
-
const postSha256 =
|
|
12750
|
+
const postSha256 = createHash9("sha256").update(readFileSync30(skillMdPath)).digest("hex");
|
|
12616
12751
|
writePostWriteSidecar(backup.id, [{ path: skillMdPath, sha256: postSha256 }]);
|
|
12617
12752
|
markGeneralized(patternId, backup.id);
|
|
12618
12753
|
const skillsRoot = resolveConfiguredRoot3(scopes.user.root);
|
|
@@ -12640,7 +12775,7 @@ function assertBackupDirContained(backupId) {
|
|
|
12640
12775
|
const backupsDir = getBackupsDir();
|
|
12641
12776
|
if (!existsSync31(backupsDir)) return;
|
|
12642
12777
|
const realBackupsDir = realpathSync14(backupsDir);
|
|
12643
|
-
const backupDir =
|
|
12778
|
+
const backupDir = join42(backupsDir, backupId);
|
|
12644
12779
|
if (!existsSync31(backupDir)) return;
|
|
12645
12780
|
const realBackupDir = realpathSync14(backupDir);
|
|
12646
12781
|
if (!isUnderRoot3(realBackupDir, realBackupsDir)) {
|
|
@@ -12653,7 +12788,7 @@ function resolveTargetRealPath(path) {
|
|
|
12653
12788
|
try {
|
|
12654
12789
|
const real = realpathSync14(lexicalAncestor);
|
|
12655
12790
|
const remainder = relative10(lexicalAncestor, resolvePath15(path));
|
|
12656
|
-
return remainder === "" ? real :
|
|
12791
|
+
return remainder === "" ? real : join42(real, remainder);
|
|
12657
12792
|
} catch {
|
|
12658
12793
|
const parent = dirname15(lexicalAncestor);
|
|
12659
12794
|
if (parent === lexicalAncestor) return resolvePath15(path);
|
|
@@ -12685,7 +12820,7 @@ function assertNoSymlinkLeaves(manifest) {
|
|
|
12685
12820
|
for (const file of manifest.files) {
|
|
12686
12821
|
let lst;
|
|
12687
12822
|
try {
|
|
12688
|
-
lst =
|
|
12823
|
+
lst = lstatSync9(file.path);
|
|
12689
12824
|
} catch {
|
|
12690
12825
|
lst = void 0;
|
|
12691
12826
|
}
|
|
@@ -12722,10 +12857,10 @@ function walkRealComponents(filePath, config, createMissing) {
|
|
|
12722
12857
|
let current = rootReal;
|
|
12723
12858
|
for (const component of dirComponents) {
|
|
12724
12859
|
if (current === void 0) break;
|
|
12725
|
-
const next =
|
|
12860
|
+
const next = join42(current, component);
|
|
12726
12861
|
let lst;
|
|
12727
12862
|
try {
|
|
12728
|
-
lst =
|
|
12863
|
+
lst = lstatSync9(next);
|
|
12729
12864
|
} catch {
|
|
12730
12865
|
lst = void 0;
|
|
12731
12866
|
}
|
|
@@ -12734,7 +12869,7 @@ function walkRealComponents(filePath, config, createMissing) {
|
|
|
12734
12869
|
current = void 0;
|
|
12735
12870
|
continue;
|
|
12736
12871
|
}
|
|
12737
|
-
|
|
12872
|
+
mkdirSync12(next);
|
|
12738
12873
|
current = next;
|
|
12739
12874
|
continue;
|
|
12740
12875
|
}
|
|
@@ -12753,22 +12888,22 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
|
|
|
12753
12888
|
if (parentReal === void 0) {
|
|
12754
12889
|
throw new UserError(`refusing to restore ${filePath}: could not establish a real parent directory`);
|
|
12755
12890
|
}
|
|
12756
|
-
const finalPath =
|
|
12891
|
+
const finalPath = join42(parentReal, leafComponent);
|
|
12757
12892
|
if (!isUnderRoot3(finalPath, rootReal)) {
|
|
12758
12893
|
throw new UserError(`refusing to restore ${filePath}: resolves outside root ${rootReal}`);
|
|
12759
12894
|
}
|
|
12760
12895
|
let leafLst;
|
|
12761
12896
|
try {
|
|
12762
|
-
leafLst =
|
|
12897
|
+
leafLst = lstatSync9(finalPath);
|
|
12763
12898
|
} catch {
|
|
12764
12899
|
leafLst = void 0;
|
|
12765
12900
|
}
|
|
12766
12901
|
if (leafLst?.isSymbolicLink()) {
|
|
12767
12902
|
throw new UserError(`refusing to restore ${filePath}: it is now a symlink \u2014 refusing to follow or overwrite it.`);
|
|
12768
12903
|
}
|
|
12769
|
-
const tempPath = `${finalPath}.tmp-${
|
|
12904
|
+
const tempPath = `${finalPath}.tmp-${randomUUID6()}`;
|
|
12770
12905
|
try {
|
|
12771
|
-
|
|
12906
|
+
writeFileSync11(tempPath, contentBuffer);
|
|
12772
12907
|
chmodSync4(tempPath, mode);
|
|
12773
12908
|
renameSync6(tempPath, finalPath);
|
|
12774
12909
|
} catch (err) {
|
|
@@ -12779,13 +12914,13 @@ function safeRestoreWrite(filePath, contentBuffer, mode, config) {
|
|
|
12779
12914
|
function safeDeleteTombstone(filePath, config) {
|
|
12780
12915
|
const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, false);
|
|
12781
12916
|
if (parentReal === void 0) return;
|
|
12782
|
-
const finalPath =
|
|
12917
|
+
const finalPath = join42(parentReal, leafComponent);
|
|
12783
12918
|
if (!isUnderRoot3(finalPath, rootReal)) {
|
|
12784
12919
|
throw new UserError(`refusing to delete ${filePath}: resolves outside root ${rootReal}`);
|
|
12785
12920
|
}
|
|
12786
12921
|
let leafLst;
|
|
12787
12922
|
try {
|
|
12788
|
-
leafLst =
|
|
12923
|
+
leafLst = lstatSync9(finalPath);
|
|
12789
12924
|
} catch {
|
|
12790
12925
|
leafLst = void 0;
|
|
12791
12926
|
}
|
|
@@ -12804,7 +12939,7 @@ async function refineRollbackCommand(backupId, options = {}) {
|
|
|
12804
12939
|
assertBackupDirContained(backupId);
|
|
12805
12940
|
const manifest = readBackupManifest(backupId);
|
|
12806
12941
|
const config = loadConfig();
|
|
12807
|
-
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));
|
|
12808
12943
|
const assertProjectRoots = () => {
|
|
12809
12944
|
if (options.project && config.skillsRoots.some((root) => resolveTargetRealPath(root) !== root)) {
|
|
12810
12945
|
throw new UserError("refusing project rollback: a project skill root or its ancestor is symlinked.");
|
|
@@ -12831,7 +12966,7 @@ async function refineRollbackCommand(backupId, options = {}) {
|
|
|
12831
12966
|
for (const file of manifest.files) {
|
|
12832
12967
|
const expected = sidecar.hashes.get(file.path);
|
|
12833
12968
|
if (expected === void 0) continue;
|
|
12834
|
-
const currentSha256 = existsSync31(file.path) ?
|
|
12969
|
+
const currentSha256 = existsSync31(file.path) ? createHash9("sha256").update(readFileSync30(file.path)).digest("hex") : null;
|
|
12835
12970
|
if (currentSha256 !== expected) mismatches.push(file.path);
|
|
12836
12971
|
}
|
|
12837
12972
|
if (mismatches.length > 0 && !options.force) {
|
|
@@ -12989,7 +13124,7 @@ configCmd.command("propose <key> <value>").description("Queue a preference for r
|
|
|
12989
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);
|
|
12990
13125
|
program.command("guide [topic]").description("What this tool does, what state you are in, and the next command to run").action(guide);
|
|
12991
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);
|
|
12992
|
-
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);
|
|
12993
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)");
|
|
12994
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);
|
|
12995
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);
|