rulesync 8.31.0 → 8.32.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 -4
- package/dist/{chunk-NAO27T2K.js → chunk-3SMTK3M3.js} +5389 -2371
- package/dist/cli/index.cjs +7482 -4090
- package/dist/cli/index.js +1129 -755
- package/dist/index.cjs +6032 -3014
- package/dist/index.d.cts +27 -3
- package/dist/index.d.ts +27 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -101,7 +101,7 @@ import {
|
|
|
101
101
|
toolSkillFactories,
|
|
102
102
|
toolSubagentFactories,
|
|
103
103
|
writeFileContent
|
|
104
|
-
} from "../chunk-
|
|
104
|
+
} from "../chunk-3SMTK3M3.js";
|
|
105
105
|
|
|
106
106
|
// src/cli/index.ts
|
|
107
107
|
import { Command } from "commander";
|
|
@@ -1154,8 +1154,11 @@ function logFeatureResult(logger5, params) {
|
|
|
1154
1154
|
}
|
|
1155
1155
|
}
|
|
1156
1156
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1157
|
+
function resolveOutputRoots({
|
|
1158
|
+
logger: logger5,
|
|
1159
|
+
baseDir,
|
|
1160
|
+
outputRoots
|
|
1161
|
+
}) {
|
|
1159
1162
|
if (baseDir !== void 0) {
|
|
1160
1163
|
logger5.warn(
|
|
1161
1164
|
"--base-dir is deprecated; use --output-roots instead. It will be removed in a future major release."
|
|
@@ -1167,6 +1170,53 @@ async function generateCommand(logger5, options) {
|
|
|
1167
1170
|
`Both '--output-roots' and '--base-dir' were provided with differing values; using '--output-roots' (${JSON.stringify(outputRoots)}) and ignoring '--base-dir' (${JSON.stringify(baseDir)}).`
|
|
1168
1171
|
);
|
|
1169
1172
|
}
|
|
1173
|
+
return outputRootsResolved;
|
|
1174
|
+
}
|
|
1175
|
+
var FEATURE_DEBUG_MESSAGES = {
|
|
1176
|
+
ignore: "Generating ignore files...",
|
|
1177
|
+
mcp: "Generating MCP files...",
|
|
1178
|
+
commands: "Generating command files...",
|
|
1179
|
+
subagents: "Generating subagent files...",
|
|
1180
|
+
skills: "Generating skill files...",
|
|
1181
|
+
hooks: "Generating hooks...",
|
|
1182
|
+
rules: "Generating rule files..."
|
|
1183
|
+
};
|
|
1184
|
+
var FEATURE_DEBUG_ORDER = [
|
|
1185
|
+
"ignore",
|
|
1186
|
+
"mcp",
|
|
1187
|
+
"commands",
|
|
1188
|
+
"subagents",
|
|
1189
|
+
"skills",
|
|
1190
|
+
"hooks",
|
|
1191
|
+
"rules"
|
|
1192
|
+
];
|
|
1193
|
+
function logFeatureDebugMessages(logger5, features) {
|
|
1194
|
+
for (const feature of FEATURE_DEBUG_ORDER) {
|
|
1195
|
+
if (features.includes(feature)) {
|
|
1196
|
+
logger5.debug(FEATURE_DEBUG_MESSAGES[feature] ?? "");
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function buildSummaryParts(result) {
|
|
1201
|
+
const summarySpecs = [
|
|
1202
|
+
{ count: result.rulesCount, label: "rules" },
|
|
1203
|
+
{ count: result.ignoreCount, label: "ignore files" },
|
|
1204
|
+
{ count: result.mcpCount, label: "MCP files" },
|
|
1205
|
+
{ count: result.commandsCount, label: "commands" },
|
|
1206
|
+
{ count: result.subagentsCount, label: "subagents" },
|
|
1207
|
+
{ count: result.skillsCount, label: "skills" },
|
|
1208
|
+
{ count: result.hooksCount, label: "hooks" },
|
|
1209
|
+
{ count: result.permissionsCount, label: "permissions" }
|
|
1210
|
+
];
|
|
1211
|
+
const parts = [];
|
|
1212
|
+
for (const { count, label } of summarySpecs) {
|
|
1213
|
+
if (count > 0) parts.push(`${count} ${label}`);
|
|
1214
|
+
}
|
|
1215
|
+
return parts;
|
|
1216
|
+
}
|
|
1217
|
+
async function generateCommand(logger5, options) {
|
|
1218
|
+
const { baseDir, outputRoots, ...rest } = options;
|
|
1219
|
+
const outputRootsResolved = resolveOutputRoots({ logger: logger5, baseDir, outputRoots });
|
|
1170
1220
|
const config = await ConfigResolver.resolve(
|
|
1171
1221
|
{ ...rest, outputRoots: outputRootsResolved },
|
|
1172
1222
|
{ logger: logger5 }
|
|
@@ -1183,27 +1233,7 @@ async function generateCommand(logger5, options) {
|
|
|
1183
1233
|
}
|
|
1184
1234
|
logger5.debug(`Output roots: ${config.getOutputRoots().join(", ")}`);
|
|
1185
1235
|
const features = config.getFeatures();
|
|
1186
|
-
|
|
1187
|
-
logger5.debug("Generating ignore files...");
|
|
1188
|
-
}
|
|
1189
|
-
if (features.includes("mcp")) {
|
|
1190
|
-
logger5.debug("Generating MCP files...");
|
|
1191
|
-
}
|
|
1192
|
-
if (features.includes("commands")) {
|
|
1193
|
-
logger5.debug("Generating command files...");
|
|
1194
|
-
}
|
|
1195
|
-
if (features.includes("subagents")) {
|
|
1196
|
-
logger5.debug("Generating subagent files...");
|
|
1197
|
-
}
|
|
1198
|
-
if (features.includes("skills")) {
|
|
1199
|
-
logger5.debug("Generating skill files...");
|
|
1200
|
-
}
|
|
1201
|
-
if (features.includes("hooks")) {
|
|
1202
|
-
logger5.debug("Generating hooks...");
|
|
1203
|
-
}
|
|
1204
|
-
if (features.includes("rules")) {
|
|
1205
|
-
logger5.debug("Generating rule files...");
|
|
1206
|
-
}
|
|
1236
|
+
logFeatureDebugMessages(logger5, features);
|
|
1207
1237
|
const result = await generate({ config, logger: logger5 });
|
|
1208
1238
|
const totalGenerated = calculateTotalCount(result);
|
|
1209
1239
|
const featureResults = {
|
|
@@ -1256,15 +1286,7 @@ async function generateCommand(logger5, options) {
|
|
|
1256
1286
|
logger5.info(`\u2713 All files are up to date (${enabledFeatures})`);
|
|
1257
1287
|
return;
|
|
1258
1288
|
}
|
|
1259
|
-
const parts =
|
|
1260
|
-
if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
|
|
1261
|
-
if (result.ignoreCount > 0) parts.push(`${result.ignoreCount} ignore files`);
|
|
1262
|
-
if (result.mcpCount > 0) parts.push(`${result.mcpCount} MCP files`);
|
|
1263
|
-
if (result.commandsCount > 0) parts.push(`${result.commandsCount} commands`);
|
|
1264
|
-
if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
|
|
1265
|
-
if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
|
|
1266
|
-
if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
|
|
1267
|
-
if (result.permissionsCount > 0) parts.push(`${result.permissionsCount} permissions`);
|
|
1289
|
+
const parts = buildSummaryParts(result);
|
|
1268
1290
|
if (isPreview) {
|
|
1269
1291
|
logger5.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
|
|
1270
1292
|
} else {
|
|
@@ -2211,7 +2233,7 @@ function findApmLockDependency(lock, repoUrl) {
|
|
|
2211
2233
|
|
|
2212
2234
|
// src/lib/apm/apm-manifest.ts
|
|
2213
2235
|
import { join as join5 } from "path";
|
|
2214
|
-
import {
|
|
2236
|
+
import { load as load2 } from "js-yaml";
|
|
2215
2237
|
import { optional as optional2, z as z5 } from "zod/mini";
|
|
2216
2238
|
var APM_MANIFEST_FILE_NAME = "apm.yml";
|
|
2217
2239
|
var ApmObjectDependencySchema = z5.looseObject({
|
|
@@ -2419,34 +2441,7 @@ async function installApm(params) {
|
|
|
2419
2441
|
}
|
|
2420
2442
|
const existingLock = await readApmLock(projectRoot);
|
|
2421
2443
|
if (options.frozen) {
|
|
2422
|
-
|
|
2423
|
-
throw new Error(
|
|
2424
|
-
"Frozen install failed: rulesync-apm.lock.yaml is missing. Run 'rulesync install --mode apm' to create it."
|
|
2425
|
-
);
|
|
2426
|
-
}
|
|
2427
|
-
const missing = manifest.dependencies.filter(
|
|
2428
|
-
(dep) => !findApmLockDependency(existingLock, canonicalRepoUrl(dep))
|
|
2429
|
-
);
|
|
2430
|
-
if (missing.length > 0) {
|
|
2431
|
-
const names = missing.map((d) => d.gitUrl).join(", ");
|
|
2432
|
-
throw new Error(
|
|
2433
|
-
`Frozen install failed: rulesync-apm.lock.yaml is missing entries for: ${names}. Run 'rulesync install --mode apm' to update the lockfile.`
|
|
2434
|
-
);
|
|
2435
|
-
}
|
|
2436
|
-
const drifted = manifest.dependencies.filter((dep) => {
|
|
2437
|
-
if (dep.ref === void 0) return false;
|
|
2438
|
-
const locked = findApmLockDependency(existingLock, canonicalRepoUrl(dep));
|
|
2439
|
-
return locked?.resolved_ref !== void 0 && locked.resolved_ref !== dep.ref;
|
|
2440
|
-
});
|
|
2441
|
-
if (drifted.length > 0) {
|
|
2442
|
-
const names = drifted.map((d) => {
|
|
2443
|
-
const locked = findApmLockDependency(existingLock, canonicalRepoUrl(d));
|
|
2444
|
-
return `${d.gitUrl} (manifest=${d.ref}, lock=${locked?.resolved_ref})`;
|
|
2445
|
-
}).join(", ");
|
|
2446
|
-
throw new Error(
|
|
2447
|
-
`Frozen install failed: manifest ref does not match rulesync-apm.lock.yaml for: ${names}. Run 'rulesync install --mode apm' to update the lockfile.`
|
|
2448
|
-
);
|
|
2449
|
-
}
|
|
2444
|
+
assertFrozenLockCoversManifest({ existingLock, dependencies: manifest.dependencies });
|
|
2450
2445
|
}
|
|
2451
2446
|
const token = GitHubClient.resolveToken(options.token);
|
|
2452
2447
|
const client = new GitHubClient({ token });
|
|
@@ -2501,30 +2496,7 @@ async function installApm(params) {
|
|
|
2501
2496
|
}
|
|
2502
2497
|
}
|
|
2503
2498
|
if (existingLock) {
|
|
2504
|
-
|
|
2505
|
-
const toDelete = [];
|
|
2506
|
-
for (const prev of existingLock.dependencies) {
|
|
2507
|
-
for (const deployed of prev.deployed_files) {
|
|
2508
|
-
if (!newDeployedFiles.has(deployed)) {
|
|
2509
|
-
toDelete.push(deployed);
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
for (const relativePath of toDelete) {
|
|
2514
|
-
if (posix2.isAbsolute(relativePath) || relativePath.split(/[/\\]/).includes("..")) {
|
|
2515
|
-
logger5.warn(`Refusing to remove stale apm file with suspicious path: "${relativePath}".`);
|
|
2516
|
-
continue;
|
|
2517
|
-
}
|
|
2518
|
-
try {
|
|
2519
|
-
checkPathTraversal({ relativePath, intendedRootDir: projectRoot });
|
|
2520
|
-
} catch {
|
|
2521
|
-
logger5.warn(`Refusing to remove stale apm file outside projectRoot: "${relativePath}".`);
|
|
2522
|
-
continue;
|
|
2523
|
-
}
|
|
2524
|
-
const absolute = join6(projectRoot, relativePath);
|
|
2525
|
-
await removeFile(absolute);
|
|
2526
|
-
logger5.debug(`Removed stale apm file: ${relativePath}`);
|
|
2527
|
-
}
|
|
2499
|
+
await removeStaleApmFiles({ existingLock, newLock, projectRoot, logger: logger5 });
|
|
2528
2500
|
}
|
|
2529
2501
|
if (!frozen) {
|
|
2530
2502
|
newLock.generated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2543,6 +2515,64 @@ async function installApm(params) {
|
|
|
2543
2515
|
failedDependencyCount: failedCount
|
|
2544
2516
|
};
|
|
2545
2517
|
}
|
|
2518
|
+
function assertFrozenLockCoversManifest(params) {
|
|
2519
|
+
const { existingLock, dependencies } = params;
|
|
2520
|
+
if (!existingLock) {
|
|
2521
|
+
throw new Error(
|
|
2522
|
+
"Frozen install failed: rulesync-apm.lock.yaml is missing. Run 'rulesync install --mode apm' to create it."
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
const missing = dependencies.filter(
|
|
2526
|
+
(dep) => !findApmLockDependency(existingLock, canonicalRepoUrl(dep))
|
|
2527
|
+
);
|
|
2528
|
+
if (missing.length > 0) {
|
|
2529
|
+
const names = missing.map((d) => d.gitUrl).join(", ");
|
|
2530
|
+
throw new Error(
|
|
2531
|
+
`Frozen install failed: rulesync-apm.lock.yaml is missing entries for: ${names}. Run 'rulesync install --mode apm' to update the lockfile.`
|
|
2532
|
+
);
|
|
2533
|
+
}
|
|
2534
|
+
const drifted = dependencies.filter((dep) => {
|
|
2535
|
+
if (dep.ref === void 0) return false;
|
|
2536
|
+
const locked = findApmLockDependency(existingLock, canonicalRepoUrl(dep));
|
|
2537
|
+
return locked?.resolved_ref !== void 0 && locked.resolved_ref !== dep.ref;
|
|
2538
|
+
});
|
|
2539
|
+
if (drifted.length > 0) {
|
|
2540
|
+
const names = drifted.map((d) => {
|
|
2541
|
+
const locked = findApmLockDependency(existingLock, canonicalRepoUrl(d));
|
|
2542
|
+
return `${d.gitUrl} (manifest=${d.ref}, lock=${locked?.resolved_ref})`;
|
|
2543
|
+
}).join(", ");
|
|
2544
|
+
throw new Error(
|
|
2545
|
+
`Frozen install failed: manifest ref does not match rulesync-apm.lock.yaml for: ${names}. Run 'rulesync install --mode apm' to update the lockfile.`
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
async function removeStaleApmFiles(params) {
|
|
2550
|
+
const { existingLock, newLock, projectRoot, logger: logger5 } = params;
|
|
2551
|
+
const newDeployedFiles = new Set(newLock.dependencies.flatMap((d) => d.deployed_files));
|
|
2552
|
+
const toDelete = [];
|
|
2553
|
+
for (const prev of existingLock.dependencies) {
|
|
2554
|
+
for (const deployed of prev.deployed_files) {
|
|
2555
|
+
if (!newDeployedFiles.has(deployed)) {
|
|
2556
|
+
toDelete.push(deployed);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
for (const relativePath of toDelete) {
|
|
2561
|
+
if (posix2.isAbsolute(relativePath) || relativePath.split(/[/\\]/).includes("..")) {
|
|
2562
|
+
logger5.warn(`Refusing to remove stale apm file with suspicious path: "${relativePath}".`);
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
try {
|
|
2566
|
+
checkPathTraversal({ relativePath, intendedRootDir: projectRoot });
|
|
2567
|
+
} catch {
|
|
2568
|
+
logger5.warn(`Refusing to remove stale apm file outside projectRoot: "${relativePath}".`);
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
const absolute = join6(projectRoot, relativePath);
|
|
2572
|
+
await removeFile(absolute);
|
|
2573
|
+
logger5.debug(`Removed stale apm file: ${relativePath}`);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2546
2576
|
async function installDependency(params) {
|
|
2547
2577
|
const { dep, client, semaphore, projectRoot, existingLock, frozen, update, logger: logger5 } = params;
|
|
2548
2578
|
const repoUrl = canonicalRepoUrl(dep);
|
|
@@ -2571,58 +2601,25 @@ async function installDependency(params) {
|
|
|
2571
2601
|
logger: logger5
|
|
2572
2602
|
});
|
|
2573
2603
|
if (files.length === 0) continue;
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
const deployRelative = toPosixPath(join6(primitive.deployDir, relativeToBase));
|
|
2589
|
-
checkPathTraversal({
|
|
2590
|
-
relativePath: deployRelative,
|
|
2591
|
-
intendedRootDir: projectRoot
|
|
2592
|
-
});
|
|
2593
|
-
const content = await withSemaphore(
|
|
2594
|
-
semaphore,
|
|
2595
|
-
() => client.getFileContent(dep.owner, dep.repo, file.path, resolvedSha)
|
|
2596
|
-
);
|
|
2597
|
-
const byteLength = Buffer.byteLength(content, "utf8");
|
|
2598
|
-
if (byteLength > MAX_FILE_SIZE) {
|
|
2599
|
-
logger5.warn(
|
|
2600
|
-
`Skipping "${file.path}" from ${repoUrl}: fetched ${(byteLength / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`
|
|
2601
|
-
);
|
|
2602
|
-
continue;
|
|
2603
|
-
}
|
|
2604
|
-
deployed.push({ path: deployRelative, content });
|
|
2605
|
-
if (!frozen) {
|
|
2606
|
-
await writeFileContent(join6(projectRoot, deployRelative), content);
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2604
|
+
await collectPrimitiveDeployments({
|
|
2605
|
+
dep,
|
|
2606
|
+
client,
|
|
2607
|
+
semaphore,
|
|
2608
|
+
projectRoot,
|
|
2609
|
+
primitive,
|
|
2610
|
+
remoteBase,
|
|
2611
|
+
files,
|
|
2612
|
+
resolvedSha,
|
|
2613
|
+
repoUrl,
|
|
2614
|
+
frozen,
|
|
2615
|
+
deployed,
|
|
2616
|
+
logger: logger5
|
|
2617
|
+
});
|
|
2609
2618
|
}
|
|
2610
2619
|
deployed.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
2611
2620
|
const deployedFiles = deployed.map((d) => d.path);
|
|
2612
2621
|
const contentHash = computeContentHash(deployed);
|
|
2613
|
-
|
|
2614
|
-
if (RULESYNC_CONTENT_HASH_REGEX.test(locked.content_hash)) {
|
|
2615
|
-
if (locked.content_hash !== contentHash) {
|
|
2616
|
-
throw new Error(
|
|
2617
|
-
`content_hash mismatch for ${repoUrl}: lock=${locked.content_hash} computed=${contentHash}. Refuse to trust the deployment under --frozen.`
|
|
2618
|
-
);
|
|
2619
|
-
}
|
|
2620
|
-
} else {
|
|
2621
|
-
logger5.debug(
|
|
2622
|
-
`Skipping content_hash integrity check for ${repoUrl}: recorded hash "${locked.content_hash}" was not written by rulesync.`
|
|
2623
|
-
);
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2622
|
+
assertFrozenContentHashMatches({ frozen, locked, contentHash, repoUrl, logger: logger5 });
|
|
2626
2623
|
if (frozen) {
|
|
2627
2624
|
for (const { path: deployRelative, content } of deployed) {
|
|
2628
2625
|
await writeFileContent(join6(projectRoot, deployRelative), content);
|
|
@@ -2643,6 +2640,71 @@ async function installDependency(params) {
|
|
|
2643
2640
|
logger5.info(`Installed ${deployedFiles.length} file(s) from ${repoUrl}@${shortSha(resolvedSha)}`);
|
|
2644
2641
|
return { lockEntry, deployedFiles };
|
|
2645
2642
|
}
|
|
2643
|
+
async function collectPrimitiveDeployments(params) {
|
|
2644
|
+
const {
|
|
2645
|
+
dep,
|
|
2646
|
+
client,
|
|
2647
|
+
semaphore,
|
|
2648
|
+
projectRoot,
|
|
2649
|
+
primitive,
|
|
2650
|
+
remoteBase,
|
|
2651
|
+
files,
|
|
2652
|
+
resolvedSha,
|
|
2653
|
+
repoUrl,
|
|
2654
|
+
frozen,
|
|
2655
|
+
deployed,
|
|
2656
|
+
logger: logger5
|
|
2657
|
+
} = params;
|
|
2658
|
+
for (const file of files) {
|
|
2659
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
2660
|
+
logger5.warn(
|
|
2661
|
+
`Skipping "${file.path}" from ${repoUrl}: ${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`
|
|
2662
|
+
);
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
const relativeToBase = posix2.relative(remoteBase, toPosixPath(file.path));
|
|
2666
|
+
if (!relativeToBase || relativeToBase.startsWith("..") || posix2.isAbsolute(relativeToBase)) {
|
|
2667
|
+
logger5.warn(`Skipping "${file.path}" from ${repoUrl}: resolved outside of "${remoteBase}".`);
|
|
2668
|
+
continue;
|
|
2669
|
+
}
|
|
2670
|
+
const deployRelative = toPosixPath(join6(primitive.deployDir, relativeToBase));
|
|
2671
|
+
checkPathTraversal({
|
|
2672
|
+
relativePath: deployRelative,
|
|
2673
|
+
intendedRootDir: projectRoot
|
|
2674
|
+
});
|
|
2675
|
+
const content = await withSemaphore(
|
|
2676
|
+
semaphore,
|
|
2677
|
+
() => client.getFileContent(dep.owner, dep.repo, file.path, resolvedSha)
|
|
2678
|
+
);
|
|
2679
|
+
const byteLength = Buffer.byteLength(content, "utf8");
|
|
2680
|
+
if (byteLength > MAX_FILE_SIZE) {
|
|
2681
|
+
logger5.warn(
|
|
2682
|
+
`Skipping "${file.path}" from ${repoUrl}: fetched ${(byteLength / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`
|
|
2683
|
+
);
|
|
2684
|
+
continue;
|
|
2685
|
+
}
|
|
2686
|
+
deployed.push({ path: deployRelative, content });
|
|
2687
|
+
if (!frozen) {
|
|
2688
|
+
await writeFileContent(join6(projectRoot, deployRelative), content);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
function assertFrozenContentHashMatches(params) {
|
|
2693
|
+
const { frozen, locked, contentHash, repoUrl, logger: logger5 } = params;
|
|
2694
|
+
if (frozen && locked?.content_hash) {
|
|
2695
|
+
if (RULESYNC_CONTENT_HASH_REGEX.test(locked.content_hash)) {
|
|
2696
|
+
if (locked.content_hash !== contentHash) {
|
|
2697
|
+
throw new Error(
|
|
2698
|
+
`content_hash mismatch for ${repoUrl}: lock=${locked.content_hash} computed=${contentHash}. Refuse to trust the deployment under --frozen.`
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
} else {
|
|
2702
|
+
logger5.debug(
|
|
2703
|
+
`Skipping content_hash integrity check for ${repoUrl}: recorded hash "${locked.content_hash}" was not written by rulesync.`
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2646
2708
|
function computeContentHash(files) {
|
|
2647
2709
|
const hash = createHash("sha256");
|
|
2648
2710
|
for (const { path: path2, content } of files) {
|
|
@@ -2685,7 +2747,7 @@ import { basename, join as join9, posix as posix3 } from "path";
|
|
|
2685
2747
|
import { Semaphore as Semaphore3 } from "es-toolkit/promise";
|
|
2686
2748
|
|
|
2687
2749
|
// src/lib/gh/gh-frontmatter.ts
|
|
2688
|
-
import { dump as
|
|
2750
|
+
import { dump as dump2, load as load3 } from "js-yaml";
|
|
2689
2751
|
var FRONTMATTER_FENCE = "---";
|
|
2690
2752
|
function injectSourceMetadata(params) {
|
|
2691
2753
|
const { content, source, repository, ref } = params;
|
|
@@ -2700,7 +2762,7 @@ function injectSourceMetadata(params) {
|
|
|
2700
2762
|
} else if (content === FRONTMATTER_FENCE) {
|
|
2701
2763
|
openFenceLen = 3;
|
|
2702
2764
|
} else {
|
|
2703
|
-
const yaml2 =
|
|
2765
|
+
const yaml2 = dump2(provenance, { noRefs: true, lineWidth: -1, sortKeys: false });
|
|
2704
2766
|
return `${FRONTMATTER_FENCE}
|
|
2705
2767
|
${yaml2}${FRONTMATTER_FENCE}
|
|
2706
2768
|
${content}`;
|
|
@@ -2727,7 +2789,7 @@ ${content}`;
|
|
|
2727
2789
|
throw new Error("invalid frontmatter");
|
|
2728
2790
|
}
|
|
2729
2791
|
if (loaded === null || loaded === void 0) {
|
|
2730
|
-
const yaml2 =
|
|
2792
|
+
const yaml2 = dump2(provenance, { noRefs: true, lineWidth: -1, sortKeys: false });
|
|
2731
2793
|
return `${FRONTMATTER_FENCE}
|
|
2732
2794
|
${yaml2}${FRONTMATTER_FENCE}
|
|
2733
2795
|
${rest}`;
|
|
@@ -2740,7 +2802,7 @@ ${rest}`;
|
|
|
2740
2802
|
...existing,
|
|
2741
2803
|
...provenance
|
|
2742
2804
|
};
|
|
2743
|
-
const yaml =
|
|
2805
|
+
const yaml = dump2(merged, { noRefs: true, lineWidth: -1, sortKeys: false });
|
|
2744
2806
|
return `${FRONTMATTER_FENCE}
|
|
2745
2807
|
${yaml}${FRONTMATTER_FENCE}
|
|
2746
2808
|
${rest}`;
|
|
@@ -2748,7 +2810,7 @@ ${rest}`;
|
|
|
2748
2810
|
|
|
2749
2811
|
// src/lib/gh/gh-lock.ts
|
|
2750
2812
|
import { join as join7 } from "path";
|
|
2751
|
-
import { dump as
|
|
2813
|
+
import { dump as dump3, load as load4 } from "js-yaml";
|
|
2752
2814
|
import { optional as optional3, refine as refine2, z as z6 } from "zod/mini";
|
|
2753
2815
|
var GH_LOCKFILE_FILE_NAME = "rulesync-gh.lock.yaml";
|
|
2754
2816
|
var GH_LOCKFILE_VERSION = "1";
|
|
@@ -2820,7 +2882,7 @@ async function writeGhLock(params) {
|
|
|
2820
2882
|
await writeFileContent(path2, content);
|
|
2821
2883
|
}
|
|
2822
2884
|
function serializeGhLock(lock) {
|
|
2823
|
-
return
|
|
2885
|
+
return dump3(lock, { noRefs: true, lineWidth: -1, sortKeys: false });
|
|
2824
2886
|
}
|
|
2825
2887
|
function findGhLockInstallation(lock, params) {
|
|
2826
2888
|
const target = params.source.toLowerCase();
|
|
@@ -2871,39 +2933,7 @@ async function installGh(params) {
|
|
|
2871
2933
|
if (sources.length === 0) {
|
|
2872
2934
|
return { sourcesProcessed: 0, installedSkillCount: 0, failedSourceCount: 0 };
|
|
2873
2935
|
}
|
|
2874
|
-
const resolvedSources = sources.map(
|
|
2875
|
-
const parsed = parseSource(entry.source);
|
|
2876
|
-
if (parsed.provider !== "github") {
|
|
2877
|
-
throw new Error(
|
|
2878
|
-
`--mode gh only supports GitHub sources. "${entry.source}" resolves to provider "${parsed.provider}".`
|
|
2879
|
-
);
|
|
2880
|
-
}
|
|
2881
|
-
if (entry.transport !== void 0 && entry.transport !== "github") {
|
|
2882
|
-
throw new Error(
|
|
2883
|
-
`--mode gh: field "transport" is not supported (got "${entry.transport}" for source "${entry.source}"). Drop the field or switch to --mode rulesync.`
|
|
2884
|
-
);
|
|
2885
|
-
}
|
|
2886
|
-
if (entry.path !== void 0) {
|
|
2887
|
-
throw new Error(
|
|
2888
|
-
`--mode gh: field "path" is not supported for source "${entry.source}". The remote layout is fixed to "skills/<name>/SKILL.md".`
|
|
2889
|
-
);
|
|
2890
|
-
}
|
|
2891
|
-
const agent = entry.agent ?? "github-copilot";
|
|
2892
|
-
if (!GH_AGENTS.includes(agent)) {
|
|
2893
|
-
throw new Error(
|
|
2894
|
-
`--mode gh: unknown agent "${agent}" for source "${entry.source}". Valid agents: ${GH_AGENTS.join(", ")}.`
|
|
2895
|
-
);
|
|
2896
|
-
}
|
|
2897
|
-
const scope = entry.scope ?? "project";
|
|
2898
|
-
return {
|
|
2899
|
-
entry,
|
|
2900
|
-
owner: parsed.owner,
|
|
2901
|
-
repo: parsed.repo,
|
|
2902
|
-
ref: entry.ref ?? parsed.ref,
|
|
2903
|
-
agent,
|
|
2904
|
-
scope
|
|
2905
|
-
};
|
|
2906
|
-
});
|
|
2936
|
+
const resolvedSources = sources.map(resolveGhSource);
|
|
2907
2937
|
const existingLock = await readGhLock(projectRoot);
|
|
2908
2938
|
const frozen = options.frozen ?? false;
|
|
2909
2939
|
const update = options.update ?? false;
|
|
@@ -2913,38 +2943,7 @@ async function installGh(params) {
|
|
|
2913
2943
|
);
|
|
2914
2944
|
}
|
|
2915
2945
|
if (frozen && existingLock) {
|
|
2916
|
-
|
|
2917
|
-
for (const rs of resolvedSources) {
|
|
2918
|
-
const hasAny = existingLock.installations.some(
|
|
2919
|
-
(i) => i.source.toLowerCase() === rs.entry.source.toLowerCase() && i.agent === rs.agent && i.scope === rs.scope
|
|
2920
|
-
);
|
|
2921
|
-
if (!hasAny) {
|
|
2922
|
-
uncovered.push(`${rs.entry.source} (agent=${rs.agent}, scope=${rs.scope})`);
|
|
2923
|
-
}
|
|
2924
|
-
}
|
|
2925
|
-
if (uncovered.length > 0) {
|
|
2926
|
-
throw new Error(
|
|
2927
|
-
`Frozen install failed: rulesync-gh.lock.yaml is missing entries for: ${uncovered.join(", ")}. Run 'rulesync install --mode gh' to update the lockfile.`
|
|
2928
|
-
);
|
|
2929
|
-
}
|
|
2930
|
-
const drifted = [];
|
|
2931
|
-
for (const rs of resolvedSources) {
|
|
2932
|
-
if (!rs.ref) continue;
|
|
2933
|
-
const matches = existingLock.installations.filter(
|
|
2934
|
-
(i) => i.source.toLowerCase() === rs.entry.source.toLowerCase()
|
|
2935
|
-
);
|
|
2936
|
-
for (const m of matches) {
|
|
2937
|
-
if (m.requested_ref !== void 0 && m.requested_ref !== rs.ref) {
|
|
2938
|
-
drifted.push(`${rs.entry.source} (manifest=${rs.ref}, lock=${m.requested_ref})`);
|
|
2939
|
-
break;
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
if (drifted.length > 0) {
|
|
2944
|
-
throw new Error(
|
|
2945
|
-
`Frozen install failed: manifest ref does not match rulesync-gh.lock.yaml for: ${drifted.join(", ")}. Run 'rulesync install --mode gh' to update the lockfile.`
|
|
2946
|
-
);
|
|
2947
|
-
}
|
|
2946
|
+
assertFrozenLockCoversSources({ existingLock, resolvedSources });
|
|
2948
2947
|
}
|
|
2949
2948
|
const token = GitHubClient.resolveToken(options.token);
|
|
2950
2949
|
const client = new GitHubClient({ token });
|
|
@@ -2980,49 +2979,11 @@ async function installGh(params) {
|
|
|
2980
2979
|
})
|
|
2981
2980
|
);
|
|
2982
2981
|
if (frozen) {
|
|
2983
|
-
|
|
2984
|
-
if (result.status !== "ok") continue;
|
|
2985
|
-
for (const inst of result.installations) {
|
|
2986
|
-
for (const d of inst.deployed) {
|
|
2987
|
-
await writeFileContent(d.absolutePath, d.content);
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
let totalInstalled = 0;
|
|
2993
|
-
let failedCount = 0;
|
|
2994
|
-
for (const result of results) {
|
|
2995
|
-
if (result.status === "ok") {
|
|
2996
|
-
for (const inst of result.installations) {
|
|
2997
|
-
newLock.installations.push(inst.installation);
|
|
2998
|
-
}
|
|
2999
|
-
totalInstalled += result.installations.length;
|
|
3000
|
-
} else {
|
|
3001
|
-
failedCount += 1;
|
|
3002
|
-
for (const preserved of result.preserved) {
|
|
3003
|
-
newLock.installations.push(preserved);
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
2982
|
+
await writeDeferredFrozenFiles(results);
|
|
3006
2983
|
}
|
|
2984
|
+
const { totalInstalled, failedCount } = aggregateSourceResults({ results, newLock });
|
|
3007
2985
|
if (existingLock) {
|
|
3008
|
-
|
|
3009
|
-
for (const inst of newLock.installations) {
|
|
3010
|
-
for (const file of inst.deployed_files) {
|
|
3011
|
-
newDeployed.add(`${inst.scope}::${file}`);
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
for (const prev of existingLock.installations) {
|
|
3015
|
-
for (const deployed of prev.deployed_files) {
|
|
3016
|
-
const key = `${prev.scope}::${deployed}`;
|
|
3017
|
-
if (newDeployed.has(key)) continue;
|
|
3018
|
-
await removeStaleFile({
|
|
3019
|
-
relativePath: deployed,
|
|
3020
|
-
scope: prev.scope === "user" ? "user" : "project",
|
|
3021
|
-
projectRoot,
|
|
3022
|
-
logger: logger5
|
|
3023
|
-
});
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
2986
|
+
await removeStaleGhFiles({ existingLock, newLock, projectRoot, logger: logger5 });
|
|
3026
2987
|
}
|
|
3027
2988
|
if (!frozen) {
|
|
3028
2989
|
newLock.generated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3041,80 +3002,152 @@ async function installGh(params) {
|
|
|
3041
3002
|
failedSourceCount: failedCount
|
|
3042
3003
|
};
|
|
3043
3004
|
}
|
|
3044
|
-
|
|
3045
|
-
const
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
if (rs.ref) {
|
|
3051
|
-
resolvedRef = rs.ref;
|
|
3052
|
-
} else {
|
|
3053
|
-
try {
|
|
3054
|
-
const release = await client.getLatestRelease(owner, repo);
|
|
3055
|
-
resolvedRef = release.tag_name;
|
|
3056
|
-
usedTag = true;
|
|
3057
|
-
} catch (error) {
|
|
3058
|
-
if (is404(error)) {
|
|
3059
|
-
resolvedRef = await client.getDefaultBranch(owner, repo);
|
|
3060
|
-
} else {
|
|
3061
|
-
throw error;
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
const resolvedSha = await client.resolveRefToSha(owner, repo, resolvedRef);
|
|
3066
|
-
logger5.debug(`Resolved ${sourceKey} -> ref=${resolvedRef} sha=${resolvedSha}`);
|
|
3067
|
-
let topLevel;
|
|
3068
|
-
try {
|
|
3069
|
-
topLevel = await client.listDirectory(owner, repo, SKILLS_REMOTE_DIR, resolvedSha);
|
|
3070
|
-
} catch (error) {
|
|
3071
|
-
if (is404(error)) {
|
|
3072
|
-
logger5.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
|
|
3073
|
-
return [];
|
|
3074
|
-
}
|
|
3075
|
-
throw error;
|
|
3005
|
+
function resolveGhSource(entry) {
|
|
3006
|
+
const parsed = parseSource(entry.source);
|
|
3007
|
+
if (parsed.provider !== "github") {
|
|
3008
|
+
throw new Error(
|
|
3009
|
+
`--mode gh only supports GitHub sources. "${entry.source}" resolves to provider "${parsed.provider}".`
|
|
3010
|
+
);
|
|
3076
3011
|
}
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
const info = await withSemaphore(
|
|
3081
|
-
semaphore,
|
|
3082
|
-
() => client.getFileInfo(owner, repo, posix3.join(sk.path, SKILL_FILE_NAME2), resolvedSha)
|
|
3012
|
+
if (entry.transport !== void 0 && entry.transport !== "github") {
|
|
3013
|
+
throw new Error(
|
|
3014
|
+
`--mode gh: field "transport" is not supported (got "${entry.transport}" for source "${entry.source}"). Drop the field or switch to --mode rulesync.`
|
|
3083
3015
|
);
|
|
3084
|
-
|
|
3085
|
-
|
|
3016
|
+
}
|
|
3017
|
+
if (entry.path !== void 0) {
|
|
3018
|
+
throw new Error(
|
|
3019
|
+
`--mode gh: field "path" is not supported for source "${entry.source}". The remote layout is fixed to "skills/<name>/SKILL.md".`
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
const agent = entry.agent ?? "github-copilot";
|
|
3023
|
+
if (!GH_AGENTS.includes(agent)) {
|
|
3024
|
+
throw new Error(
|
|
3025
|
+
`--mode gh: unknown agent "${agent}" for source "${entry.source}". Valid agents: ${GH_AGENTS.join(", ")}.`
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
3028
|
+
const scope = entry.scope ?? "project";
|
|
3029
|
+
return {
|
|
3030
|
+
entry,
|
|
3031
|
+
owner: parsed.owner,
|
|
3032
|
+
repo: parsed.repo,
|
|
3033
|
+
ref: entry.ref ?? parsed.ref,
|
|
3034
|
+
agent,
|
|
3035
|
+
scope
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
function assertFrozenLockCoversSources(params) {
|
|
3039
|
+
const { existingLock, resolvedSources } = params;
|
|
3040
|
+
const uncovered = [];
|
|
3041
|
+
for (const rs of resolvedSources) {
|
|
3042
|
+
const hasAny = existingLock.installations.some(
|
|
3043
|
+
(i) => i.source.toLowerCase() === rs.entry.source.toLowerCase() && i.agent === rs.agent && i.scope === rs.scope
|
|
3044
|
+
);
|
|
3045
|
+
if (!hasAny) {
|
|
3046
|
+
uncovered.push(`${rs.entry.source} (agent=${rs.agent}, scope=${rs.scope})`);
|
|
3086
3047
|
}
|
|
3087
3048
|
}
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3049
|
+
if (uncovered.length > 0) {
|
|
3050
|
+
throw new Error(
|
|
3051
|
+
`Frozen install failed: rulesync-gh.lock.yaml is missing entries for: ${uncovered.join(", ")}. Run 'rulesync install --mode gh' to update the lockfile.`
|
|
3052
|
+
);
|
|
3053
|
+
}
|
|
3054
|
+
const drifted = [];
|
|
3055
|
+
for (const rs of resolvedSources) {
|
|
3056
|
+
if (!rs.ref) continue;
|
|
3057
|
+
const matches = existingLock.installations.filter(
|
|
3058
|
+
(i) => i.source.toLowerCase() === rs.entry.source.toLowerCase()
|
|
3059
|
+
);
|
|
3060
|
+
for (const m of matches) {
|
|
3061
|
+
if (m.requested_ref !== void 0 && m.requested_ref !== rs.ref) {
|
|
3062
|
+
drifted.push(`${rs.entry.source} (manifest=${rs.ref}, lock=${m.requested_ref})`);
|
|
3063
|
+
break;
|
|
3096
3064
|
}
|
|
3097
3065
|
}
|
|
3098
3066
|
}
|
|
3099
|
-
if (
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3067
|
+
if (drifted.length > 0) {
|
|
3068
|
+
throw new Error(
|
|
3069
|
+
`Frozen install failed: manifest ref does not match rulesync-gh.lock.yaml for: ${drifted.join(", ")}. Run 'rulesync install --mode gh' to update the lockfile.`
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
async function writeDeferredFrozenFiles(results) {
|
|
3074
|
+
for (const result of results) {
|
|
3075
|
+
if (result.status !== "ok") continue;
|
|
3076
|
+
for (const inst of result.installations) {
|
|
3077
|
+
for (const d of inst.deployed) {
|
|
3078
|
+
await writeFileContent(d.absolutePath, d.content);
|
|
3110
3079
|
}
|
|
3111
3080
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
function aggregateSourceResults(params) {
|
|
3084
|
+
const { results, newLock } = params;
|
|
3085
|
+
let totalInstalled = 0;
|
|
3086
|
+
let failedCount = 0;
|
|
3087
|
+
for (const result of results) {
|
|
3088
|
+
if (result.status === "ok") {
|
|
3089
|
+
for (const inst of result.installations) {
|
|
3090
|
+
newLock.installations.push(inst.installation);
|
|
3091
|
+
}
|
|
3092
|
+
totalInstalled += result.installations.length;
|
|
3093
|
+
} else {
|
|
3094
|
+
failedCount += 1;
|
|
3095
|
+
for (const preserved of result.preserved) {
|
|
3096
|
+
newLock.installations.push(preserved);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
return { totalInstalled, failedCount };
|
|
3101
|
+
}
|
|
3102
|
+
async function removeStaleGhFiles(params) {
|
|
3103
|
+
const { existingLock, newLock, projectRoot, logger: logger5 } = params;
|
|
3104
|
+
const newDeployed = /* @__PURE__ */ new Set();
|
|
3105
|
+
for (const inst of newLock.installations) {
|
|
3106
|
+
for (const file of inst.deployed_files) {
|
|
3107
|
+
newDeployed.add(`${inst.scope}::${file}`);
|
|
3116
3108
|
}
|
|
3117
3109
|
}
|
|
3110
|
+
for (const prev of existingLock.installations) {
|
|
3111
|
+
for (const deployed of prev.deployed_files) {
|
|
3112
|
+
const key = `${prev.scope}::${deployed}`;
|
|
3113
|
+
if (newDeployed.has(key)) continue;
|
|
3114
|
+
await removeStaleFile({
|
|
3115
|
+
relativePath: deployed,
|
|
3116
|
+
scope: prev.scope === "user" ? "user" : "project",
|
|
3117
|
+
projectRoot,
|
|
3118
|
+
logger: logger5
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
async function installSource(params) {
|
|
3124
|
+
const { rs, client, semaphore, projectRoot, existingLock, frozen, update, logger: logger5 } = params;
|
|
3125
|
+
const { entry, owner, repo, agent, scope } = rs;
|
|
3126
|
+
const sourceKey = entry.source;
|
|
3127
|
+
const { resolvedRef, resolvedSha, usedTag } = await resolveGhRef({
|
|
3128
|
+
rs,
|
|
3129
|
+
client,
|
|
3130
|
+
owner,
|
|
3131
|
+
repo,
|
|
3132
|
+
sourceKey,
|
|
3133
|
+
logger: logger5
|
|
3134
|
+
});
|
|
3135
|
+
const validatedSkills = await discoverValidatedSkills({
|
|
3136
|
+
client,
|
|
3137
|
+
semaphore,
|
|
3138
|
+
owner,
|
|
3139
|
+
repo,
|
|
3140
|
+
resolvedSha,
|
|
3141
|
+
sourceKey,
|
|
3142
|
+
logger: logger5
|
|
3143
|
+
});
|
|
3144
|
+
if (validatedSkills === null) {
|
|
3145
|
+
return [];
|
|
3146
|
+
}
|
|
3147
|
+
const selected = selectSkills({ validatedSkills, entry, sourceKey, logger: logger5 });
|
|
3148
|
+
if (frozen && existingLock) {
|
|
3149
|
+
assertFrozenSkillCoverage({ selected, existingLock, sourceKey, agent, scope });
|
|
3150
|
+
}
|
|
3118
3151
|
const results = [];
|
|
3119
3152
|
const installRelDir = relativeInstallDirFor({ agent, scope });
|
|
3120
3153
|
const scopeRoot = scope === "user" ? getHomeDirectory() : projectRoot;
|
|
@@ -3136,79 +3169,38 @@ async function installSource(params) {
|
|
|
3136
3169
|
ref: resolvedSha,
|
|
3137
3170
|
semaphore
|
|
3138
3171
|
});
|
|
3139
|
-
const deployed =
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
checkPathTraversal({ relativePath: withinInstallDir, intendedRootDir: installAbs });
|
|
3157
|
-
let content = await withSemaphore(
|
|
3158
|
-
semaphore,
|
|
3159
|
-
() => client.getFileContent(owner, repo, file.path, resolvedSha)
|
|
3160
|
-
);
|
|
3161
|
-
const byteLength = Buffer.byteLength(content, "utf8");
|
|
3162
|
-
if (byteLength > MAX_FILE_SIZE) {
|
|
3163
|
-
logger5.warn(
|
|
3164
|
-
`Skipping "${file.path}" from ${sourceKey}: fetched ${(byteLength / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`
|
|
3165
|
-
);
|
|
3166
|
-
continue;
|
|
3167
|
-
}
|
|
3168
|
-
if (basename(file.path) === SKILL_FILE_NAME2) {
|
|
3169
|
-
try {
|
|
3170
|
-
content = injectSourceMetadata({
|
|
3171
|
-
content,
|
|
3172
|
-
source: sourceUrl,
|
|
3173
|
-
repository,
|
|
3174
|
-
ref: provenanceRef
|
|
3175
|
-
});
|
|
3176
|
-
} catch {
|
|
3177
|
-
logger5.warn(
|
|
3178
|
-
`Frontmatter in ${file.path} (${sourceKey}) is invalid. Prepending a fresh provenance block.`
|
|
3179
|
-
);
|
|
3180
|
-
content = `---
|
|
3181
|
-
source: ${sourceUrl}
|
|
3182
|
-
repository: ${repository}
|
|
3183
|
-
ref: ${provenanceRef}
|
|
3184
|
-
---
|
|
3185
|
-
${content}`;
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
|
-
const absolutePath = join9(scopeRoot, deployRelative);
|
|
3189
|
-
deployed.push({ relativeToScopeRoot: deployRelative, absolutePath, content });
|
|
3190
|
-
if (!frozen) {
|
|
3191
|
-
await writeFileContent(absolutePath, content);
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3172
|
+
const deployed = await buildSkillDeployment({
|
|
3173
|
+
sk,
|
|
3174
|
+
allFiles,
|
|
3175
|
+
client,
|
|
3176
|
+
semaphore,
|
|
3177
|
+
owner,
|
|
3178
|
+
repo,
|
|
3179
|
+
resolvedSha,
|
|
3180
|
+
installRelDir,
|
|
3181
|
+
scopeRoot,
|
|
3182
|
+
sourceUrl,
|
|
3183
|
+
repository,
|
|
3184
|
+
provenanceRef,
|
|
3185
|
+
sourceKey,
|
|
3186
|
+
frozen,
|
|
3187
|
+
logger: logger5
|
|
3188
|
+
});
|
|
3194
3189
|
deployed.sort(
|
|
3195
3190
|
(a, b) => a.relativeToScopeRoot < b.relativeToScopeRoot ? -1 : a.relativeToScopeRoot > b.relativeToScopeRoot ? 1 : 0
|
|
3196
3191
|
);
|
|
3197
3192
|
const deployedFiles = deployed.map((d) => d.relativeToScopeRoot);
|
|
3198
3193
|
const contentHash = computeContentHash2(deployed);
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
);
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3194
|
+
assertFrozenSkillIntegrity({
|
|
3195
|
+
frozen,
|
|
3196
|
+
locked,
|
|
3197
|
+
contentHash,
|
|
3198
|
+
sourceKey,
|
|
3199
|
+
skillName: sk.name,
|
|
3200
|
+
agent,
|
|
3201
|
+
scope,
|
|
3202
|
+
logger: logger5
|
|
3203
|
+
});
|
|
3212
3204
|
const installation = {
|
|
3213
3205
|
source: sourceKey,
|
|
3214
3206
|
owner,
|
|
@@ -3232,6 +3224,180 @@ ${content}`;
|
|
|
3232
3224
|
}
|
|
3233
3225
|
return results;
|
|
3234
3226
|
}
|
|
3227
|
+
async function resolveGhRef(params) {
|
|
3228
|
+
const { rs, client, owner, repo, sourceKey, logger: logger5 } = params;
|
|
3229
|
+
let resolvedRef;
|
|
3230
|
+
let usedTag = false;
|
|
3231
|
+
if (rs.ref) {
|
|
3232
|
+
resolvedRef = rs.ref;
|
|
3233
|
+
} else {
|
|
3234
|
+
try {
|
|
3235
|
+
const release = await client.getLatestRelease(owner, repo);
|
|
3236
|
+
resolvedRef = release.tag_name;
|
|
3237
|
+
usedTag = true;
|
|
3238
|
+
} catch (error) {
|
|
3239
|
+
if (is404(error)) {
|
|
3240
|
+
resolvedRef = await client.getDefaultBranch(owner, repo);
|
|
3241
|
+
} else {
|
|
3242
|
+
throw error;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
const resolvedSha = await client.resolveRefToSha(owner, repo, resolvedRef);
|
|
3247
|
+
logger5.debug(`Resolved ${sourceKey} -> ref=${resolvedRef} sha=${resolvedSha}`);
|
|
3248
|
+
return { resolvedRef, resolvedSha, usedTag };
|
|
3249
|
+
}
|
|
3250
|
+
async function discoverValidatedSkills(params) {
|
|
3251
|
+
const { client, semaphore, owner, repo, resolvedSha, sourceKey, logger: logger5 } = params;
|
|
3252
|
+
let topLevel;
|
|
3253
|
+
try {
|
|
3254
|
+
topLevel = await client.listDirectory(owner, repo, SKILLS_REMOTE_DIR, resolvedSha);
|
|
3255
|
+
} catch (error) {
|
|
3256
|
+
if (is404(error)) {
|
|
3257
|
+
logger5.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
|
|
3258
|
+
return null;
|
|
3259
|
+
}
|
|
3260
|
+
throw error;
|
|
3261
|
+
}
|
|
3262
|
+
const skillDirs = topLevel.filter((e) => e.type === "dir").map((e) => ({ name: e.name, path: e.path }));
|
|
3263
|
+
const validatedSkills = [];
|
|
3264
|
+
for (const sk of skillDirs) {
|
|
3265
|
+
const info = await withSemaphore(
|
|
3266
|
+
semaphore,
|
|
3267
|
+
() => client.getFileInfo(owner, repo, posix3.join(sk.path, SKILL_FILE_NAME2), resolvedSha)
|
|
3268
|
+
);
|
|
3269
|
+
if (info) {
|
|
3270
|
+
validatedSkills.push(sk);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
return validatedSkills;
|
|
3274
|
+
}
|
|
3275
|
+
function selectSkills(params) {
|
|
3276
|
+
const { validatedSkills, entry, sourceKey, logger: logger5 } = params;
|
|
3277
|
+
if (!entry.skills || entry.skills.length === 0) {
|
|
3278
|
+
return validatedSkills;
|
|
3279
|
+
}
|
|
3280
|
+
const requested = new Set(entry.skills);
|
|
3281
|
+
const selected = validatedSkills.filter((s) => requested.has(s.name));
|
|
3282
|
+
const presentNames = new Set(validatedSkills.map((s) => s.name));
|
|
3283
|
+
for (const want of entry.skills) {
|
|
3284
|
+
if (!presentNames.has(want)) {
|
|
3285
|
+
logger5.warn(`Requested skill "${want}" not found in ${sourceKey} under skills/. Skipping.`);
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
return selected;
|
|
3289
|
+
}
|
|
3290
|
+
function assertFrozenSkillCoverage(params) {
|
|
3291
|
+
const { selected, existingLock, sourceKey, agent, scope } = params;
|
|
3292
|
+
const missing = [];
|
|
3293
|
+
for (const sk of selected) {
|
|
3294
|
+
const locked = findGhLockInstallation(existingLock, {
|
|
3295
|
+
source: sourceKey,
|
|
3296
|
+
agent,
|
|
3297
|
+
scope,
|
|
3298
|
+
skill: sk.name
|
|
3299
|
+
});
|
|
3300
|
+
if (!locked) {
|
|
3301
|
+
missing.push(sk.name);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
if (missing.length > 0) {
|
|
3305
|
+
throw new Error(
|
|
3306
|
+
`Frozen install failed: rulesync-gh.lock.yaml is missing entries for ${sourceKey} (agent=${agent}, scope=${scope}) skills: ${missing.join(", ")}. Run 'rulesync install --mode gh' to update the lockfile.`
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
async function buildSkillDeployment(params) {
|
|
3311
|
+
const {
|
|
3312
|
+
sk,
|
|
3313
|
+
allFiles,
|
|
3314
|
+
client,
|
|
3315
|
+
semaphore,
|
|
3316
|
+
owner,
|
|
3317
|
+
repo,
|
|
3318
|
+
resolvedSha,
|
|
3319
|
+
installRelDir,
|
|
3320
|
+
scopeRoot,
|
|
3321
|
+
sourceUrl,
|
|
3322
|
+
repository,
|
|
3323
|
+
provenanceRef,
|
|
3324
|
+
sourceKey,
|
|
3325
|
+
frozen,
|
|
3326
|
+
logger: logger5
|
|
3327
|
+
} = params;
|
|
3328
|
+
const deployed = [];
|
|
3329
|
+
for (const file of allFiles) {
|
|
3330
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
3331
|
+
logger5.warn(
|
|
3332
|
+
`Skipping "${file.path}" from ${sourceKey}: ${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`
|
|
3333
|
+
);
|
|
3334
|
+
continue;
|
|
3335
|
+
}
|
|
3336
|
+
const relativeToSkill = posix3.relative(sk.path, toPosixPath(file.path));
|
|
3337
|
+
if (!relativeToSkill || relativeToSkill.startsWith("..") || posix3.isAbsolute(relativeToSkill)) {
|
|
3338
|
+
logger5.warn(`Skipping "${file.path}" from ${sourceKey}: resolved outside of "${sk.path}".`);
|
|
3339
|
+
continue;
|
|
3340
|
+
}
|
|
3341
|
+
const deployRelative = toPosixPath(join9(installRelDir, sk.name, relativeToSkill));
|
|
3342
|
+
checkPathTraversal({ relativePath: deployRelative, intendedRootDir: scopeRoot });
|
|
3343
|
+
const installAbs = join9(scopeRoot, installRelDir);
|
|
3344
|
+
const withinInstallDir = toPosixPath(join9(sk.name, relativeToSkill));
|
|
3345
|
+
checkPathTraversal({ relativePath: withinInstallDir, intendedRootDir: installAbs });
|
|
3346
|
+
let content = await withSemaphore(
|
|
3347
|
+
semaphore,
|
|
3348
|
+
() => client.getFileContent(owner, repo, file.path, resolvedSha)
|
|
3349
|
+
);
|
|
3350
|
+
const byteLength = Buffer.byteLength(content, "utf8");
|
|
3351
|
+
if (byteLength > MAX_FILE_SIZE) {
|
|
3352
|
+
logger5.warn(
|
|
3353
|
+
`Skipping "${file.path}" from ${sourceKey}: fetched ${(byteLength / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`
|
|
3354
|
+
);
|
|
3355
|
+
continue;
|
|
3356
|
+
}
|
|
3357
|
+
if (basename(file.path) === SKILL_FILE_NAME2) {
|
|
3358
|
+
try {
|
|
3359
|
+
content = injectSourceMetadata({
|
|
3360
|
+
content,
|
|
3361
|
+
source: sourceUrl,
|
|
3362
|
+
repository,
|
|
3363
|
+
ref: provenanceRef
|
|
3364
|
+
});
|
|
3365
|
+
} catch {
|
|
3366
|
+
logger5.warn(
|
|
3367
|
+
`Frontmatter in ${file.path} (${sourceKey}) is invalid. Prepending a fresh provenance block.`
|
|
3368
|
+
);
|
|
3369
|
+
content = `---
|
|
3370
|
+
source: ${sourceUrl}
|
|
3371
|
+
repository: ${repository}
|
|
3372
|
+
ref: ${provenanceRef}
|
|
3373
|
+
---
|
|
3374
|
+
${content}`;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
const absolutePath = join9(scopeRoot, deployRelative);
|
|
3378
|
+
deployed.push({ relativeToScopeRoot: deployRelative, absolutePath, content });
|
|
3379
|
+
if (!frozen) {
|
|
3380
|
+
await writeFileContent(absolutePath, content);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
return deployed;
|
|
3384
|
+
}
|
|
3385
|
+
function assertFrozenSkillIntegrity(params) {
|
|
3386
|
+
const { frozen, locked, contentHash, sourceKey, skillName, agent, scope, logger: logger5 } = params;
|
|
3387
|
+
if (frozen && locked?.content_hash) {
|
|
3388
|
+
if (RULESYNC_CONTENT_HASH_REGEX2.test(locked.content_hash)) {
|
|
3389
|
+
if (locked.content_hash !== contentHash) {
|
|
3390
|
+
throw new Error(
|
|
3391
|
+
`content_hash mismatch for ${sourceKey} skill "${skillName}" (agent=${agent}, scope=${scope}): lock=${locked.content_hash} computed=${contentHash}. Refuse to trust the deployment under --frozen.`
|
|
3392
|
+
);
|
|
3393
|
+
}
|
|
3394
|
+
} else {
|
|
3395
|
+
logger5.debug(
|
|
3396
|
+
`Skipping content_hash integrity check for ${sourceKey} skill "${skillName}": recorded hash "${locked.content_hash}" was not written by rulesync.`
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3235
3401
|
async function removeStaleFile(params) {
|
|
3236
3402
|
const { relativePath, scope, projectRoot, logger: logger5 } = params;
|
|
3237
3403
|
if (posix3.isAbsolute(relativePath) || relativePath.split(/[/\\]/).includes("..")) {
|
|
@@ -3623,18 +3789,7 @@ async function resolveAndFetchSources(params) {
|
|
|
3623
3789
|
}
|
|
3624
3790
|
let lock = options.updateSources ? createEmptyLock() : await readLockFile({ projectRoot, logger: logger5 });
|
|
3625
3791
|
if (options.frozen) {
|
|
3626
|
-
|
|
3627
|
-
for (const source of sources) {
|
|
3628
|
-
const locked = getLockedSource(lock, source.source);
|
|
3629
|
-
if (!locked) {
|
|
3630
|
-
missingKeys.push(source.source);
|
|
3631
|
-
}
|
|
3632
|
-
}
|
|
3633
|
-
if (missingKeys.length > 0) {
|
|
3634
|
-
throw new Error(
|
|
3635
|
-
`Frozen install failed: lockfile is missing entries for: ${missingKeys.join(", ")}. Run 'rulesync install' to update the lockfile.`
|
|
3636
|
-
);
|
|
3637
|
-
}
|
|
3792
|
+
assertFrozenLockCoversSources2({ lock, sources });
|
|
3638
3793
|
}
|
|
3639
3794
|
const originalLockJson = JSON.stringify(lock);
|
|
3640
3795
|
const token = GitHubClient.resolveToken(options.token);
|
|
@@ -3644,31 +3799,17 @@ async function resolveAndFetchSources(params) {
|
|
|
3644
3799
|
const allFetchedSkillNames = /* @__PURE__ */ new Set();
|
|
3645
3800
|
for (const sourceEntry of sources) {
|
|
3646
3801
|
try {
|
|
3647
|
-
const
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
logger: logger5
|
|
3659
|
-
});
|
|
3660
|
-
} else {
|
|
3661
|
-
result = await fetchSource({
|
|
3662
|
-
sourceEntry,
|
|
3663
|
-
client,
|
|
3664
|
-
projectRoot,
|
|
3665
|
-
lock,
|
|
3666
|
-
localSkillNames,
|
|
3667
|
-
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
3668
|
-
updateSources: options.updateSources ?? false,
|
|
3669
|
-
logger: logger5
|
|
3670
|
-
});
|
|
3671
|
-
}
|
|
3802
|
+
const result = await fetchSourceByTransport({
|
|
3803
|
+
sourceEntry,
|
|
3804
|
+
client,
|
|
3805
|
+
projectRoot,
|
|
3806
|
+
lock,
|
|
3807
|
+
localSkillNames,
|
|
3808
|
+
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
3809
|
+
updateSources: options.updateSources ?? false,
|
|
3810
|
+
frozen: options.frozen ?? false,
|
|
3811
|
+
logger: logger5
|
|
3812
|
+
});
|
|
3672
3813
|
const { skillCount, fetchedSkillNames, updatedLock } = result;
|
|
3673
3814
|
lock = updatedLock;
|
|
3674
3815
|
totalSkillCount += skillCount;
|
|
@@ -3684,16 +3825,7 @@ async function resolveAndFetchSources(params) {
|
|
|
3684
3825
|
}
|
|
3685
3826
|
}
|
|
3686
3827
|
}
|
|
3687
|
-
|
|
3688
|
-
const prunedSources = {};
|
|
3689
|
-
for (const [key, value] of Object.entries(lock.sources)) {
|
|
3690
|
-
if (sourceKeys.has(normalizeSourceKey(key))) {
|
|
3691
|
-
prunedSources[key] = value;
|
|
3692
|
-
} else {
|
|
3693
|
-
logger5.debug(`Pruned stale lockfile entry: ${key}`);
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
lock = { lockfileVersion: lock.lockfileVersion, sources: prunedSources };
|
|
3828
|
+
lock = pruneStaleLockEntries({ lock, sources, logger: logger5 });
|
|
3697
3829
|
if (!options.frozen && JSON.stringify(lock) !== originalLockJson) {
|
|
3698
3830
|
await writeLockFile({ projectRoot, lock, logger: logger5 });
|
|
3699
3831
|
} else {
|
|
@@ -3709,6 +3841,70 @@ function logGitClientHints(params) {
|
|
|
3709
3841
|
logger5.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
|
|
3710
3842
|
}
|
|
3711
3843
|
}
|
|
3844
|
+
function assertFrozenLockCoversSources2(params) {
|
|
3845
|
+
const { lock, sources } = params;
|
|
3846
|
+
const missingKeys = [];
|
|
3847
|
+
for (const source of sources) {
|
|
3848
|
+
const locked = getLockedSource(lock, source.source);
|
|
3849
|
+
if (!locked) {
|
|
3850
|
+
missingKeys.push(source.source);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
if (missingKeys.length > 0) {
|
|
3854
|
+
throw new Error(
|
|
3855
|
+
`Frozen install failed: lockfile is missing entries for: ${missingKeys.join(", ")}. Run 'rulesync install' to update the lockfile.`
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
async function fetchSourceByTransport(params) {
|
|
3860
|
+
const {
|
|
3861
|
+
sourceEntry,
|
|
3862
|
+
client,
|
|
3863
|
+
projectRoot,
|
|
3864
|
+
lock,
|
|
3865
|
+
localSkillNames,
|
|
3866
|
+
alreadyFetchedSkillNames,
|
|
3867
|
+
updateSources,
|
|
3868
|
+
frozen,
|
|
3869
|
+
logger: logger5
|
|
3870
|
+
} = params;
|
|
3871
|
+
const transport = sourceEntry.transport ?? "github";
|
|
3872
|
+
if (transport === "git") {
|
|
3873
|
+
return fetchSourceViaGit({
|
|
3874
|
+
sourceEntry,
|
|
3875
|
+
projectRoot,
|
|
3876
|
+
lock,
|
|
3877
|
+
localSkillNames,
|
|
3878
|
+
alreadyFetchedSkillNames,
|
|
3879
|
+
updateSources,
|
|
3880
|
+
frozen,
|
|
3881
|
+
logger: logger5
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
return fetchSource({
|
|
3885
|
+
sourceEntry,
|
|
3886
|
+
client,
|
|
3887
|
+
projectRoot,
|
|
3888
|
+
lock,
|
|
3889
|
+
localSkillNames,
|
|
3890
|
+
alreadyFetchedSkillNames,
|
|
3891
|
+
updateSources,
|
|
3892
|
+
logger: logger5
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
function pruneStaleLockEntries(params) {
|
|
3896
|
+
const { lock, sources, logger: logger5 } = params;
|
|
3897
|
+
const sourceKeys = new Set(sources.map((s) => normalizeSourceKey(s.source)));
|
|
3898
|
+
const prunedSources = {};
|
|
3899
|
+
for (const [key, value] of Object.entries(lock.sources)) {
|
|
3900
|
+
if (sourceKeys.has(normalizeSourceKey(key))) {
|
|
3901
|
+
prunedSources[key] = value;
|
|
3902
|
+
} else {
|
|
3903
|
+
logger5.debug(`Pruned stale lockfile entry: ${key}`);
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
return { lockfileVersion: lock.lockfileVersion, sources: prunedSources };
|
|
3907
|
+
}
|
|
3712
3908
|
async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
3713
3909
|
if (skillNames.length === 0) return true;
|
|
3714
3910
|
for (const name of skillNames) {
|
|
@@ -3840,7 +4036,189 @@ function groupRemoteFilesBySkillRoot(params) {
|
|
|
3840
4036
|
grouped.set(singleSkillName, rootLevelFiles);
|
|
3841
4037
|
}
|
|
3842
4038
|
}
|
|
3843
|
-
return grouped;
|
|
4039
|
+
return grouped;
|
|
4040
|
+
}
|
|
4041
|
+
async function resolveGithubFetchRef(params) {
|
|
4042
|
+
const { parsed, locked, updateSources, sourceKey, client, logger: logger5 } = params;
|
|
4043
|
+
if (locked && !updateSources) {
|
|
4044
|
+
logger5.debug(`Using locked ref for ${sourceKey}: ${locked.resolvedRef}`);
|
|
4045
|
+
return {
|
|
4046
|
+
ref: locked.resolvedRef,
|
|
4047
|
+
resolvedSha: locked.resolvedRef,
|
|
4048
|
+
requestedRef: locked.requestedRef
|
|
4049
|
+
};
|
|
4050
|
+
}
|
|
4051
|
+
const requestedRef = parsed.ref ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
|
|
4052
|
+
const resolvedSha = await client.resolveRefToSha(parsed.owner, parsed.repo, requestedRef);
|
|
4053
|
+
logger5.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
|
|
4054
|
+
return { ref: resolvedSha, resolvedSha, requestedRef };
|
|
4055
|
+
}
|
|
4056
|
+
async function fetchRootLevelFallbackSkill(params) {
|
|
4057
|
+
const {
|
|
4058
|
+
entries,
|
|
4059
|
+
parsed,
|
|
4060
|
+
ref,
|
|
4061
|
+
resolvedSha,
|
|
4062
|
+
skillFilter,
|
|
4063
|
+
isWildcard,
|
|
4064
|
+
curatedDir,
|
|
4065
|
+
locked,
|
|
4066
|
+
sourceKey,
|
|
4067
|
+
localSkillNames,
|
|
4068
|
+
alreadyFetchedSkillNames,
|
|
4069
|
+
client,
|
|
4070
|
+
semaphore,
|
|
4071
|
+
fetchedSkills,
|
|
4072
|
+
logger: logger5
|
|
4073
|
+
} = params;
|
|
4074
|
+
const rootFiles = entries.filter((entry) => entry.type === "file");
|
|
4075
|
+
const rootSkillFiles = [];
|
|
4076
|
+
for (const file of rootFiles) {
|
|
4077
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
4078
|
+
logger5.warn(
|
|
4079
|
+
`Skipping file "${file.path}" (${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
4080
|
+
);
|
|
4081
|
+
continue;
|
|
4082
|
+
}
|
|
4083
|
+
const content = await withSemaphore(
|
|
4084
|
+
semaphore,
|
|
4085
|
+
() => client.getFileContent(parsed.owner, parsed.repo, file.path, ref)
|
|
4086
|
+
);
|
|
4087
|
+
rootSkillFiles.push({ relativePath: file.name, content });
|
|
4088
|
+
}
|
|
4089
|
+
const groupedRootFiles = groupRemoteFilesBySkillRoot({
|
|
4090
|
+
remoteFiles: rootSkillFiles,
|
|
4091
|
+
skillFilter,
|
|
4092
|
+
isWildcard
|
|
4093
|
+
});
|
|
4094
|
+
const [fallbackSkillName] = groupedRootFiles.keys();
|
|
4095
|
+
if (fallbackSkillName === void 0) {
|
|
4096
|
+
return { handled: false, remoteSkillNames: [] };
|
|
4097
|
+
}
|
|
4098
|
+
if (!shouldSkipSkill({
|
|
4099
|
+
skillName: fallbackSkillName,
|
|
4100
|
+
sourceKey,
|
|
4101
|
+
localSkillNames,
|
|
4102
|
+
alreadyFetchedSkillNames,
|
|
4103
|
+
logger: logger5
|
|
4104
|
+
})) {
|
|
4105
|
+
fetchedSkills[fallbackSkillName] = await writeSkillAndComputeIntegrity({
|
|
4106
|
+
skillName: fallbackSkillName,
|
|
4107
|
+
files: groupedRootFiles.get(fallbackSkillName) ?? [],
|
|
4108
|
+
curatedDir,
|
|
4109
|
+
locked,
|
|
4110
|
+
resolvedSha,
|
|
4111
|
+
sourceKey,
|
|
4112
|
+
logger: logger5
|
|
4113
|
+
});
|
|
4114
|
+
logger5.debug(`Fetched skill "${fallbackSkillName}" from ${sourceKey}`);
|
|
4115
|
+
}
|
|
4116
|
+
return { handled: true, remoteSkillNames: [fallbackSkillName] };
|
|
4117
|
+
}
|
|
4118
|
+
async function fetchGithubSkillDir(params) {
|
|
4119
|
+
const {
|
|
4120
|
+
skillDir,
|
|
4121
|
+
parsed,
|
|
4122
|
+
ref,
|
|
4123
|
+
resolvedSha,
|
|
4124
|
+
curatedDir,
|
|
4125
|
+
locked,
|
|
4126
|
+
sourceKey,
|
|
4127
|
+
client,
|
|
4128
|
+
semaphore,
|
|
4129
|
+
logger: logger5
|
|
4130
|
+
} = params;
|
|
4131
|
+
const allFiles = await listDirectoryRecursive({
|
|
4132
|
+
client,
|
|
4133
|
+
owner: parsed.owner,
|
|
4134
|
+
repo: parsed.repo,
|
|
4135
|
+
path: skillDir.path,
|
|
4136
|
+
ref,
|
|
4137
|
+
semaphore
|
|
4138
|
+
});
|
|
4139
|
+
const files = allFiles.filter((file) => {
|
|
4140
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
4141
|
+
logger5.warn(
|
|
4142
|
+
`Skipping file "${file.path}" (${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
4143
|
+
);
|
|
4144
|
+
return false;
|
|
4145
|
+
}
|
|
4146
|
+
return true;
|
|
4147
|
+
});
|
|
4148
|
+
const skillFiles = [];
|
|
4149
|
+
for (const file of files) {
|
|
4150
|
+
const relativeToSkill = file.path.substring(skillDir.path.length + 1);
|
|
4151
|
+
const content = await withSemaphore(
|
|
4152
|
+
semaphore,
|
|
4153
|
+
() => client.getFileContent(parsed.owner, parsed.repo, file.path, ref)
|
|
4154
|
+
);
|
|
4155
|
+
skillFiles.push({ relativePath: relativeToSkill, content });
|
|
4156
|
+
}
|
|
4157
|
+
return writeSkillAndComputeIntegrity({
|
|
4158
|
+
skillName: skillDir.name,
|
|
4159
|
+
files: skillFiles,
|
|
4160
|
+
curatedDir,
|
|
4161
|
+
locked,
|
|
4162
|
+
resolvedSha,
|
|
4163
|
+
sourceKey,
|
|
4164
|
+
logger: logger5
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4167
|
+
async function discoverGithubSkillDirs(params) {
|
|
4168
|
+
const {
|
|
4169
|
+
parsed,
|
|
4170
|
+
ref,
|
|
4171
|
+
resolvedSha,
|
|
4172
|
+
skillFilter,
|
|
4173
|
+
isWildcard,
|
|
4174
|
+
curatedDir,
|
|
4175
|
+
locked,
|
|
4176
|
+
sourceKey,
|
|
4177
|
+
localSkillNames,
|
|
4178
|
+
alreadyFetchedSkillNames,
|
|
4179
|
+
client,
|
|
4180
|
+
semaphore,
|
|
4181
|
+
fetchedSkills,
|
|
4182
|
+
logger: logger5
|
|
4183
|
+
} = params;
|
|
4184
|
+
const skillsBasePath = parsed.path ?? "skills";
|
|
4185
|
+
try {
|
|
4186
|
+
const entries = await client.listDirectory(parsed.owner, parsed.repo, skillsBasePath, ref);
|
|
4187
|
+
const remoteSkillDirs = entries.filter((e) => e.type === "dir").map((e) => ({ name: e.name, path: e.path }));
|
|
4188
|
+
if (remoteSkillDirs.length === 0 && !isWildcard && skillFilter.length === 1) {
|
|
4189
|
+
const fallback = await fetchRootLevelFallbackSkill({
|
|
4190
|
+
entries,
|
|
4191
|
+
parsed,
|
|
4192
|
+
ref,
|
|
4193
|
+
resolvedSha,
|
|
4194
|
+
skillFilter,
|
|
4195
|
+
isWildcard,
|
|
4196
|
+
curatedDir,
|
|
4197
|
+
locked,
|
|
4198
|
+
sourceKey,
|
|
4199
|
+
localSkillNames,
|
|
4200
|
+
alreadyFetchedSkillNames,
|
|
4201
|
+
client,
|
|
4202
|
+
semaphore,
|
|
4203
|
+
fetchedSkills,
|
|
4204
|
+
logger: logger5
|
|
4205
|
+
});
|
|
4206
|
+
if (fallback.handled) {
|
|
4207
|
+
return {
|
|
4208
|
+
status: "ok",
|
|
4209
|
+
remoteSkillDirs,
|
|
4210
|
+
fallbackHandled: true,
|
|
4211
|
+
remoteSkillNames: fallback.remoteSkillNames
|
|
4212
|
+
};
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
return { status: "ok", remoteSkillDirs, fallbackHandled: false, remoteSkillNames: [] };
|
|
4216
|
+
} catch (error) {
|
|
4217
|
+
if (error instanceof GitHubClientError && error.statusCode === 404) {
|
|
4218
|
+
return { status: "notFound" };
|
|
4219
|
+
}
|
|
4220
|
+
throw error;
|
|
4221
|
+
}
|
|
3844
4222
|
}
|
|
3845
4223
|
async function fetchSource(params) {
|
|
3846
4224
|
const {
|
|
@@ -3861,20 +4239,14 @@ async function fetchSource(params) {
|
|
|
3861
4239
|
const sourceKey = sourceEntry.source;
|
|
3862
4240
|
const locked = getLockedSource(lock, sourceKey);
|
|
3863
4241
|
const lockedSkillNames = locked ? getLockedSkillNames(locked) : [];
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
} else {
|
|
3873
|
-
requestedRef = parsed.ref ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
|
|
3874
|
-
resolvedSha = await client.resolveRefToSha(parsed.owner, parsed.repo, requestedRef);
|
|
3875
|
-
ref = resolvedSha;
|
|
3876
|
-
logger5.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
|
|
3877
|
-
}
|
|
4242
|
+
const { ref, resolvedSha, requestedRef } = await resolveGithubFetchRef({
|
|
4243
|
+
parsed,
|
|
4244
|
+
locked,
|
|
4245
|
+
updateSources,
|
|
4246
|
+
sourceKey,
|
|
4247
|
+
client,
|
|
4248
|
+
logger: logger5
|
|
4249
|
+
});
|
|
3878
4250
|
const curatedDir = join12(projectRoot, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
|
|
3879
4251
|
if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
|
|
3880
4252
|
const allExist = await checkLockedSkillsExist(curatedDir, lockedSkillNames);
|
|
@@ -3891,69 +4263,29 @@ async function fetchSource(params) {
|
|
|
3891
4263
|
const isWildcard = skillFilter.length === 1 && skillFilter[0] === "*";
|
|
3892
4264
|
const semaphore = new Semaphore4(FETCH_CONCURRENCY_LIMIT);
|
|
3893
4265
|
const fetchedSkills = {};
|
|
3894
|
-
const
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
() => client.getFileContent(parsed.owner, parsed.repo, file.path, ref)
|
|
3914
|
-
);
|
|
3915
|
-
rootSkillFiles.push({ relativePath: file.name, content });
|
|
3916
|
-
}
|
|
3917
|
-
const groupedRootFiles = groupRemoteFilesBySkillRoot({
|
|
3918
|
-
remoteFiles: rootSkillFiles,
|
|
3919
|
-
skillFilter,
|
|
3920
|
-
isWildcard
|
|
3921
|
-
});
|
|
3922
|
-
const [fallbackSkillName] = groupedRootFiles.keys();
|
|
3923
|
-
if (fallbackSkillName !== void 0) {
|
|
3924
|
-
fallbackHandled = true;
|
|
3925
|
-
remoteSkillNames = [fallbackSkillName];
|
|
3926
|
-
if (!shouldSkipSkill({
|
|
3927
|
-
skillName: fallbackSkillName,
|
|
3928
|
-
sourceKey,
|
|
3929
|
-
localSkillNames,
|
|
3930
|
-
alreadyFetchedSkillNames,
|
|
3931
|
-
logger: logger5
|
|
3932
|
-
})) {
|
|
3933
|
-
fetchedSkills[fallbackSkillName] = await writeSkillAndComputeIntegrity({
|
|
3934
|
-
skillName: fallbackSkillName,
|
|
3935
|
-
files: groupedRootFiles.get(fallbackSkillName) ?? [],
|
|
3936
|
-
curatedDir,
|
|
3937
|
-
locked,
|
|
3938
|
-
resolvedSha,
|
|
3939
|
-
sourceKey,
|
|
3940
|
-
logger: logger5
|
|
3941
|
-
});
|
|
3942
|
-
logger5.debug(`Fetched skill "${fallbackSkillName}" from ${sourceKey}`);
|
|
3943
|
-
}
|
|
3944
|
-
}
|
|
3945
|
-
}
|
|
3946
|
-
} catch (error) {
|
|
3947
|
-
if (error instanceof GitHubClientError && error.statusCode === 404) {
|
|
3948
|
-
logger5.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
|
|
3949
|
-
return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
|
|
3950
|
-
}
|
|
3951
|
-
throw error;
|
|
4266
|
+
const discovery = await discoverGithubSkillDirs({
|
|
4267
|
+
parsed,
|
|
4268
|
+
ref,
|
|
4269
|
+
resolvedSha,
|
|
4270
|
+
skillFilter,
|
|
4271
|
+
isWildcard,
|
|
4272
|
+
curatedDir,
|
|
4273
|
+
locked,
|
|
4274
|
+
sourceKey,
|
|
4275
|
+
localSkillNames,
|
|
4276
|
+
alreadyFetchedSkillNames,
|
|
4277
|
+
client,
|
|
4278
|
+
semaphore,
|
|
4279
|
+
fetchedSkills,
|
|
4280
|
+
logger: logger5
|
|
4281
|
+
});
|
|
4282
|
+
if (discovery.status === "notFound") {
|
|
4283
|
+
logger5.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
|
|
4284
|
+
return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
|
|
3952
4285
|
}
|
|
4286
|
+
const { remoteSkillDirs, fallbackHandled, remoteSkillNames: fallbackSkillNames } = discovery;
|
|
3953
4287
|
const filteredDirs = isWildcard ? remoteSkillDirs : remoteSkillDirs.filter((d) => skillFilter.includes(d.name));
|
|
3954
|
-
|
|
3955
|
-
remoteSkillNames = filteredDirs.map((d) => d.name);
|
|
3956
|
-
}
|
|
4288
|
+
const remoteSkillNames = fallbackHandled ? fallbackSkillNames : filteredDirs.map((d) => d.name);
|
|
3957
4289
|
if (locked) {
|
|
3958
4290
|
await cleanPreviousCuratedSkills({ curatedDir, lockedSkillNames, logger: logger5 });
|
|
3959
4291
|
}
|
|
@@ -3967,39 +4299,16 @@ async function fetchSource(params) {
|
|
|
3967
4299
|
})) {
|
|
3968
4300
|
continue;
|
|
3969
4301
|
}
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
repo: parsed.repo,
|
|
3974
|
-
path: skillDir.path,
|
|
4302
|
+
fetchedSkills[skillDir.name] = await fetchGithubSkillDir({
|
|
4303
|
+
skillDir,
|
|
4304
|
+
parsed,
|
|
3975
4305
|
ref,
|
|
3976
|
-
|
|
3977
|
-
});
|
|
3978
|
-
const files = allFiles.filter((file) => {
|
|
3979
|
-
if (file.size > MAX_FILE_SIZE) {
|
|
3980
|
-
logger5.warn(
|
|
3981
|
-
`Skipping file "${file.path}" (${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
3982
|
-
);
|
|
3983
|
-
return false;
|
|
3984
|
-
}
|
|
3985
|
-
return true;
|
|
3986
|
-
});
|
|
3987
|
-
const skillFiles = [];
|
|
3988
|
-
for (const file of files) {
|
|
3989
|
-
const relativeToSkill = file.path.substring(skillDir.path.length + 1);
|
|
3990
|
-
const content = await withSemaphore(
|
|
3991
|
-
semaphore,
|
|
3992
|
-
() => client.getFileContent(parsed.owner, parsed.repo, file.path, ref)
|
|
3993
|
-
);
|
|
3994
|
-
skillFiles.push({ relativePath: relativeToSkill, content });
|
|
3995
|
-
}
|
|
3996
|
-
fetchedSkills[skillDir.name] = await writeSkillAndComputeIntegrity({
|
|
3997
|
-
skillName: skillDir.name,
|
|
3998
|
-
files: skillFiles,
|
|
4306
|
+
resolvedSha,
|
|
3999
4307
|
curatedDir,
|
|
4000
4308
|
locked,
|
|
4001
|
-
resolvedSha,
|
|
4002
4309
|
sourceKey,
|
|
4310
|
+
client,
|
|
4311
|
+
semaphore,
|
|
4003
4312
|
logger: logger5
|
|
4004
4313
|
});
|
|
4005
4314
|
logger5.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
|
|
@@ -5908,6 +6217,174 @@ function ensureBody({ body, feature, operation }) {
|
|
|
5908
6217
|
}
|
|
5909
6218
|
return body;
|
|
5910
6219
|
}
|
|
6220
|
+
function requireContent({
|
|
6221
|
+
content,
|
|
6222
|
+
feature
|
|
6223
|
+
}) {
|
|
6224
|
+
if (!content) {
|
|
6225
|
+
throw new Error(`content is required for ${feature} put operation`);
|
|
6226
|
+
}
|
|
6227
|
+
return content;
|
|
6228
|
+
}
|
|
6229
|
+
function executeRule(parsed) {
|
|
6230
|
+
if (parsed.operation === "list") {
|
|
6231
|
+
return ruleTools.listRules.execute();
|
|
6232
|
+
}
|
|
6233
|
+
if (parsed.operation === "get") {
|
|
6234
|
+
return ruleTools.getRule.execute({ relativePathFromCwd: requireTargetPath(parsed) });
|
|
6235
|
+
}
|
|
6236
|
+
if (parsed.operation === "put") {
|
|
6237
|
+
return ruleTools.putRule.execute({
|
|
6238
|
+
relativePathFromCwd: requireTargetPath(parsed),
|
|
6239
|
+
frontmatter: parseFrontmatter({
|
|
6240
|
+
feature: "rule",
|
|
6241
|
+
frontmatter: parsed.frontmatter ?? {}
|
|
6242
|
+
}),
|
|
6243
|
+
body: ensureBody(parsed)
|
|
6244
|
+
});
|
|
6245
|
+
}
|
|
6246
|
+
return ruleTools.deleteRule.execute({ relativePathFromCwd: requireTargetPath(parsed) });
|
|
6247
|
+
}
|
|
6248
|
+
function executeCommand(parsed) {
|
|
6249
|
+
if (parsed.operation === "list") {
|
|
6250
|
+
return commandTools.listCommands.execute();
|
|
6251
|
+
}
|
|
6252
|
+
if (parsed.operation === "get") {
|
|
6253
|
+
return commandTools.getCommand.execute({
|
|
6254
|
+
relativePathFromCwd: requireTargetPath(parsed)
|
|
6255
|
+
});
|
|
6256
|
+
}
|
|
6257
|
+
if (parsed.operation === "put") {
|
|
6258
|
+
return commandTools.putCommand.execute({
|
|
6259
|
+
relativePathFromCwd: requireTargetPath(parsed),
|
|
6260
|
+
frontmatter: parseFrontmatter({
|
|
6261
|
+
feature: "command",
|
|
6262
|
+
frontmatter: parsed.frontmatter ?? {}
|
|
6263
|
+
}),
|
|
6264
|
+
body: ensureBody(parsed)
|
|
6265
|
+
});
|
|
6266
|
+
}
|
|
6267
|
+
return commandTools.deleteCommand.execute({
|
|
6268
|
+
relativePathFromCwd: requireTargetPath(parsed)
|
|
6269
|
+
});
|
|
6270
|
+
}
|
|
6271
|
+
function executeSubagent(parsed) {
|
|
6272
|
+
if (parsed.operation === "list") {
|
|
6273
|
+
return subagentTools.listSubagents.execute();
|
|
6274
|
+
}
|
|
6275
|
+
if (parsed.operation === "get") {
|
|
6276
|
+
return subagentTools.getSubagent.execute({
|
|
6277
|
+
relativePathFromCwd: requireTargetPath(parsed)
|
|
6278
|
+
});
|
|
6279
|
+
}
|
|
6280
|
+
if (parsed.operation === "put") {
|
|
6281
|
+
return subagentTools.putSubagent.execute({
|
|
6282
|
+
relativePathFromCwd: requireTargetPath(parsed),
|
|
6283
|
+
frontmatter: parseFrontmatter({
|
|
6284
|
+
feature: "subagent",
|
|
6285
|
+
frontmatter: parsed.frontmatter ?? {}
|
|
6286
|
+
}),
|
|
6287
|
+
body: ensureBody(parsed)
|
|
6288
|
+
});
|
|
6289
|
+
}
|
|
6290
|
+
return subagentTools.deleteSubagent.execute({
|
|
6291
|
+
relativePathFromCwd: requireTargetPath(parsed)
|
|
6292
|
+
});
|
|
6293
|
+
}
|
|
6294
|
+
function executeSkill(parsed) {
|
|
6295
|
+
if (parsed.operation === "list") {
|
|
6296
|
+
return skillTools.listSkills.execute();
|
|
6297
|
+
}
|
|
6298
|
+
if (parsed.operation === "get") {
|
|
6299
|
+
return skillTools.getSkill.execute({ relativeDirPathFromCwd: requireTargetPath(parsed) });
|
|
6300
|
+
}
|
|
6301
|
+
if (parsed.operation === "put") {
|
|
6302
|
+
return skillTools.putSkill.execute({
|
|
6303
|
+
relativeDirPathFromCwd: requireTargetPath(parsed),
|
|
6304
|
+
frontmatter: parseFrontmatter({
|
|
6305
|
+
feature: "skill",
|
|
6306
|
+
frontmatter: parsed.frontmatter ?? {}
|
|
6307
|
+
}),
|
|
6308
|
+
body: ensureBody(parsed),
|
|
6309
|
+
otherFiles: parsed.otherFiles ?? []
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
6312
|
+
return skillTools.deleteSkill.execute({
|
|
6313
|
+
relativeDirPathFromCwd: requireTargetPath(parsed)
|
|
6314
|
+
});
|
|
6315
|
+
}
|
|
6316
|
+
function executeIgnore(parsed) {
|
|
6317
|
+
if (parsed.operation === "get") {
|
|
6318
|
+
return ignoreTools.getIgnoreFile.execute();
|
|
6319
|
+
}
|
|
6320
|
+
if (parsed.operation === "put") {
|
|
6321
|
+
return ignoreTools.putIgnoreFile.execute({
|
|
6322
|
+
content: requireContent({ content: parsed.content, feature: "ignore" })
|
|
6323
|
+
});
|
|
6324
|
+
}
|
|
6325
|
+
return ignoreTools.deleteIgnoreFile.execute();
|
|
6326
|
+
}
|
|
6327
|
+
function executeMcp(parsed) {
|
|
6328
|
+
if (parsed.operation === "get") {
|
|
6329
|
+
return mcpTools.getMcpFile.execute();
|
|
6330
|
+
}
|
|
6331
|
+
if (parsed.operation === "put") {
|
|
6332
|
+
return mcpTools.putMcpFile.execute({
|
|
6333
|
+
content: requireContent({ content: parsed.content, feature: "mcp" })
|
|
6334
|
+
});
|
|
6335
|
+
}
|
|
6336
|
+
return mcpTools.deleteMcpFile.execute();
|
|
6337
|
+
}
|
|
6338
|
+
function executePermissions(parsed) {
|
|
6339
|
+
if (parsed.operation === "get") {
|
|
6340
|
+
return permissionsTools.getPermissionsFile.execute();
|
|
6341
|
+
}
|
|
6342
|
+
if (parsed.operation === "put") {
|
|
6343
|
+
return permissionsTools.putPermissionsFile.execute({
|
|
6344
|
+
content: requireContent({ content: parsed.content, feature: "permissions" })
|
|
6345
|
+
});
|
|
6346
|
+
}
|
|
6347
|
+
return permissionsTools.deletePermissionsFile.execute();
|
|
6348
|
+
}
|
|
6349
|
+
function executeHooks(parsed) {
|
|
6350
|
+
if (parsed.operation === "get") {
|
|
6351
|
+
return hooksTools.getHooksFile.execute();
|
|
6352
|
+
}
|
|
6353
|
+
if (parsed.operation === "put") {
|
|
6354
|
+
return hooksTools.putHooksFile.execute({
|
|
6355
|
+
content: requireContent({ content: parsed.content, feature: "hooks" })
|
|
6356
|
+
});
|
|
6357
|
+
}
|
|
6358
|
+
return hooksTools.deleteHooksFile.execute();
|
|
6359
|
+
}
|
|
6360
|
+
function executeGenerate2(parsed) {
|
|
6361
|
+
return generateTools.executeGenerate.execute(parsed.generateOptions ?? {});
|
|
6362
|
+
}
|
|
6363
|
+
function executeImport2(parsed) {
|
|
6364
|
+
if (!parsed.importOptions) {
|
|
6365
|
+
throw new Error("importOptions is required for import feature");
|
|
6366
|
+
}
|
|
6367
|
+
return importTools.executeImport.execute(parsed.importOptions);
|
|
6368
|
+
}
|
|
6369
|
+
function executeConvert2(parsed) {
|
|
6370
|
+
if (!parsed.convertOptions) {
|
|
6371
|
+
throw new Error("convertOptions is required for convert feature");
|
|
6372
|
+
}
|
|
6373
|
+
return convertTools.executeConvert.execute(parsed.convertOptions);
|
|
6374
|
+
}
|
|
6375
|
+
var featureExecutors = {
|
|
6376
|
+
rule: executeRule,
|
|
6377
|
+
command: executeCommand,
|
|
6378
|
+
subagent: executeSubagent,
|
|
6379
|
+
skill: executeSkill,
|
|
6380
|
+
ignore: executeIgnore,
|
|
6381
|
+
mcp: executeMcp,
|
|
6382
|
+
permissions: executePermissions,
|
|
6383
|
+
hooks: executeHooks,
|
|
6384
|
+
generate: executeGenerate2,
|
|
6385
|
+
import: executeImport2,
|
|
6386
|
+
convert: executeConvert2
|
|
6387
|
+
};
|
|
5911
6388
|
var rulesyncTool = {
|
|
5912
6389
|
name: "rulesyncTool",
|
|
5913
6390
|
description: "Manage Rulesync files through a single MCP tool. Features: rule/command/subagent/skill support list/get/put/delete; ignore/mcp/permissions/hooks support get/put/delete only; generate supports run only; import supports run only; convert supports run only. Parameters: list requires no targetPathFromCwd (lists all items); get/delete require targetPathFromCwd; put requires targetPathFromCwd, frontmatter, and body (or content for ignore/mcp/permissions/hooks); generate/run uses generateOptions to configure generation; import/run uses importOptions to configure import; convert/run uses convertOptions to configure conversion.",
|
|
@@ -5915,161 +6392,11 @@ var rulesyncTool = {
|
|
|
5915
6392
|
execute: async (args) => {
|
|
5916
6393
|
const parsed = rulesyncToolSchema.parse(args);
|
|
5917
6394
|
assertSupported({ feature: parsed.feature, operation: parsed.operation });
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
return ruleTools.listRules.execute();
|
|
5922
|
-
}
|
|
5923
|
-
if (parsed.operation === "get") {
|
|
5924
|
-
return ruleTools.getRule.execute({ relativePathFromCwd: requireTargetPath(parsed) });
|
|
5925
|
-
}
|
|
5926
|
-
if (parsed.operation === "put") {
|
|
5927
|
-
return ruleTools.putRule.execute({
|
|
5928
|
-
relativePathFromCwd: requireTargetPath(parsed),
|
|
5929
|
-
frontmatter: parseFrontmatter({
|
|
5930
|
-
feature: "rule",
|
|
5931
|
-
frontmatter: parsed.frontmatter ?? {}
|
|
5932
|
-
}),
|
|
5933
|
-
body: ensureBody(parsed)
|
|
5934
|
-
});
|
|
5935
|
-
}
|
|
5936
|
-
return ruleTools.deleteRule.execute({ relativePathFromCwd: requireTargetPath(parsed) });
|
|
5937
|
-
}
|
|
5938
|
-
case "command": {
|
|
5939
|
-
if (parsed.operation === "list") {
|
|
5940
|
-
return commandTools.listCommands.execute();
|
|
5941
|
-
}
|
|
5942
|
-
if (parsed.operation === "get") {
|
|
5943
|
-
return commandTools.getCommand.execute({
|
|
5944
|
-
relativePathFromCwd: requireTargetPath(parsed)
|
|
5945
|
-
});
|
|
5946
|
-
}
|
|
5947
|
-
if (parsed.operation === "put") {
|
|
5948
|
-
return commandTools.putCommand.execute({
|
|
5949
|
-
relativePathFromCwd: requireTargetPath(parsed),
|
|
5950
|
-
frontmatter: parseFrontmatter({
|
|
5951
|
-
feature: "command",
|
|
5952
|
-
frontmatter: parsed.frontmatter ?? {}
|
|
5953
|
-
}),
|
|
5954
|
-
body: ensureBody(parsed)
|
|
5955
|
-
});
|
|
5956
|
-
}
|
|
5957
|
-
return commandTools.deleteCommand.execute({
|
|
5958
|
-
relativePathFromCwd: requireTargetPath(parsed)
|
|
5959
|
-
});
|
|
5960
|
-
}
|
|
5961
|
-
case "subagent": {
|
|
5962
|
-
if (parsed.operation === "list") {
|
|
5963
|
-
return subagentTools.listSubagents.execute();
|
|
5964
|
-
}
|
|
5965
|
-
if (parsed.operation === "get") {
|
|
5966
|
-
return subagentTools.getSubagent.execute({
|
|
5967
|
-
relativePathFromCwd: requireTargetPath(parsed)
|
|
5968
|
-
});
|
|
5969
|
-
}
|
|
5970
|
-
if (parsed.operation === "put") {
|
|
5971
|
-
return subagentTools.putSubagent.execute({
|
|
5972
|
-
relativePathFromCwd: requireTargetPath(parsed),
|
|
5973
|
-
frontmatter: parseFrontmatter({
|
|
5974
|
-
feature: "subagent",
|
|
5975
|
-
frontmatter: parsed.frontmatter ?? {}
|
|
5976
|
-
}),
|
|
5977
|
-
body: ensureBody(parsed)
|
|
5978
|
-
});
|
|
5979
|
-
}
|
|
5980
|
-
return subagentTools.deleteSubagent.execute({
|
|
5981
|
-
relativePathFromCwd: requireTargetPath(parsed)
|
|
5982
|
-
});
|
|
5983
|
-
}
|
|
5984
|
-
case "skill": {
|
|
5985
|
-
if (parsed.operation === "list") {
|
|
5986
|
-
return skillTools.listSkills.execute();
|
|
5987
|
-
}
|
|
5988
|
-
if (parsed.operation === "get") {
|
|
5989
|
-
return skillTools.getSkill.execute({ relativeDirPathFromCwd: requireTargetPath(parsed) });
|
|
5990
|
-
}
|
|
5991
|
-
if (parsed.operation === "put") {
|
|
5992
|
-
return skillTools.putSkill.execute({
|
|
5993
|
-
relativeDirPathFromCwd: requireTargetPath(parsed),
|
|
5994
|
-
frontmatter: parseFrontmatter({
|
|
5995
|
-
feature: "skill",
|
|
5996
|
-
frontmatter: parsed.frontmatter ?? {}
|
|
5997
|
-
}),
|
|
5998
|
-
body: ensureBody(parsed),
|
|
5999
|
-
otherFiles: parsed.otherFiles ?? []
|
|
6000
|
-
});
|
|
6001
|
-
}
|
|
6002
|
-
return skillTools.deleteSkill.execute({
|
|
6003
|
-
relativeDirPathFromCwd: requireTargetPath(parsed)
|
|
6004
|
-
});
|
|
6005
|
-
}
|
|
6006
|
-
case "ignore": {
|
|
6007
|
-
if (parsed.operation === "get") {
|
|
6008
|
-
return ignoreTools.getIgnoreFile.execute();
|
|
6009
|
-
}
|
|
6010
|
-
if (parsed.operation === "put") {
|
|
6011
|
-
if (!parsed.content) {
|
|
6012
|
-
throw new Error("content is required for ignore put operation");
|
|
6013
|
-
}
|
|
6014
|
-
return ignoreTools.putIgnoreFile.execute({ content: parsed.content });
|
|
6015
|
-
}
|
|
6016
|
-
return ignoreTools.deleteIgnoreFile.execute();
|
|
6017
|
-
}
|
|
6018
|
-
case "mcp": {
|
|
6019
|
-
if (parsed.operation === "get") {
|
|
6020
|
-
return mcpTools.getMcpFile.execute();
|
|
6021
|
-
}
|
|
6022
|
-
if (parsed.operation === "put") {
|
|
6023
|
-
if (!parsed.content) {
|
|
6024
|
-
throw new Error("content is required for mcp put operation");
|
|
6025
|
-
}
|
|
6026
|
-
return mcpTools.putMcpFile.execute({ content: parsed.content });
|
|
6027
|
-
}
|
|
6028
|
-
return mcpTools.deleteMcpFile.execute();
|
|
6029
|
-
}
|
|
6030
|
-
case "permissions": {
|
|
6031
|
-
if (parsed.operation === "get") {
|
|
6032
|
-
return permissionsTools.getPermissionsFile.execute();
|
|
6033
|
-
}
|
|
6034
|
-
if (parsed.operation === "put") {
|
|
6035
|
-
if (!parsed.content) {
|
|
6036
|
-
throw new Error("content is required for permissions put operation");
|
|
6037
|
-
}
|
|
6038
|
-
return permissionsTools.putPermissionsFile.execute({ content: parsed.content });
|
|
6039
|
-
}
|
|
6040
|
-
return permissionsTools.deletePermissionsFile.execute();
|
|
6041
|
-
}
|
|
6042
|
-
case "hooks": {
|
|
6043
|
-
if (parsed.operation === "get") {
|
|
6044
|
-
return hooksTools.getHooksFile.execute();
|
|
6045
|
-
}
|
|
6046
|
-
if (parsed.operation === "put") {
|
|
6047
|
-
if (!parsed.content) {
|
|
6048
|
-
throw new Error("content is required for hooks put operation");
|
|
6049
|
-
}
|
|
6050
|
-
return hooksTools.putHooksFile.execute({ content: parsed.content });
|
|
6051
|
-
}
|
|
6052
|
-
return hooksTools.deleteHooksFile.execute();
|
|
6053
|
-
}
|
|
6054
|
-
case "generate": {
|
|
6055
|
-
return generateTools.executeGenerate.execute(parsed.generateOptions ?? {});
|
|
6056
|
-
}
|
|
6057
|
-
case "import": {
|
|
6058
|
-
if (!parsed.importOptions) {
|
|
6059
|
-
throw new Error("importOptions is required for import feature");
|
|
6060
|
-
}
|
|
6061
|
-
return importTools.executeImport.execute(parsed.importOptions);
|
|
6062
|
-
}
|
|
6063
|
-
case "convert": {
|
|
6064
|
-
if (!parsed.convertOptions) {
|
|
6065
|
-
throw new Error("convertOptions is required for convert feature");
|
|
6066
|
-
}
|
|
6067
|
-
return convertTools.executeConvert.execute(parsed.convertOptions);
|
|
6068
|
-
}
|
|
6069
|
-
default: {
|
|
6070
|
-
throw new Error(`Unknown feature: ${parsed.feature}`);
|
|
6071
|
-
}
|
|
6395
|
+
const executor = featureExecutors[parsed.feature];
|
|
6396
|
+
if (!executor) {
|
|
6397
|
+
throw new Error(`Unknown feature: ${parsed.feature}`);
|
|
6072
6398
|
}
|
|
6399
|
+
return executor(parsed);
|
|
6073
6400
|
}
|
|
6074
6401
|
};
|
|
6075
6402
|
|
|
@@ -6288,103 +6615,150 @@ function parseSha256Sums(content) {
|
|
|
6288
6615
|
}
|
|
6289
6616
|
return result;
|
|
6290
6617
|
}
|
|
6291
|
-
|
|
6292
|
-
const { force = false, token } = options;
|
|
6293
|
-
const updateCheck = await checkForUpdate(currentVersion, token);
|
|
6294
|
-
if (!updateCheck.hasUpdate && !force) {
|
|
6295
|
-
return `Already at the latest version (${currentVersion})`;
|
|
6296
|
-
}
|
|
6618
|
+
function resolveUpdateAssets(release) {
|
|
6297
6619
|
const assetName = getPlatformAssetName();
|
|
6298
6620
|
if (!assetName) {
|
|
6299
6621
|
throw new Error(
|
|
6300
6622
|
`Unsupported platform: ${os.platform()} ${os.arch()}. Please download manually from ${RELEASES_URL}`
|
|
6301
6623
|
);
|
|
6302
6624
|
}
|
|
6303
|
-
const binaryAsset = findAsset(
|
|
6625
|
+
const binaryAsset = findAsset(release, assetName);
|
|
6304
6626
|
if (!binaryAsset) {
|
|
6305
6627
|
throw new Error(
|
|
6306
6628
|
`Binary for ${assetName} not found in release. Please download manually from ${RELEASES_URL}`
|
|
6307
6629
|
);
|
|
6308
6630
|
}
|
|
6309
|
-
const checksumAsset = findAsset(
|
|
6631
|
+
const checksumAsset = findAsset(release, "SHA256SUMS");
|
|
6310
6632
|
if (!checksumAsset) {
|
|
6311
6633
|
throw new Error(
|
|
6312
6634
|
`SHA256SUMS not found in release. Cannot verify download integrity. Please download manually from ${RELEASES_URL}`
|
|
6313
6635
|
);
|
|
6314
6636
|
}
|
|
6315
|
-
|
|
6316
|
-
|
|
6637
|
+
return { assetName, binaryAsset, checksumAsset };
|
|
6638
|
+
}
|
|
6639
|
+
async function downloadAndVerifyBinary(params) {
|
|
6640
|
+
const { tempDir, assetName, binaryAsset, checksumAsset } = params;
|
|
6641
|
+
const tempBinaryPath = path.join(tempDir, assetName);
|
|
6642
|
+
await downloadFile(binaryAsset.browser_download_url, tempBinaryPath);
|
|
6643
|
+
const checksumsPath = path.join(tempDir, "SHA256SUMS");
|
|
6644
|
+
await downloadFile(checksumAsset.browser_download_url, checksumsPath);
|
|
6645
|
+
const checksumsContent = await fs.promises.readFile(checksumsPath, "utf-8");
|
|
6646
|
+
const checksums = parseSha256Sums(checksumsContent);
|
|
6647
|
+
const expectedChecksum = checksums.get(assetName);
|
|
6648
|
+
if (!expectedChecksum) {
|
|
6649
|
+
throw new Error(
|
|
6650
|
+
`Checksum entry for "${assetName}" not found in SHA256SUMS. Cannot verify download integrity.`
|
|
6651
|
+
);
|
|
6652
|
+
}
|
|
6653
|
+
const actualChecksum = await calculateSha256(tempBinaryPath);
|
|
6654
|
+
if (actualChecksum !== expectedChecksum) {
|
|
6655
|
+
throw new Error(
|
|
6656
|
+
`Checksum verification failed. Expected: ${expectedChecksum}, Got: ${actualChecksum}. The download may be corrupted.`
|
|
6657
|
+
);
|
|
6658
|
+
}
|
|
6659
|
+
return tempBinaryPath;
|
|
6660
|
+
}
|
|
6661
|
+
async function replaceCurrentBinary(params) {
|
|
6662
|
+
const { tempBinaryPath, currentExePath, currentDir } = params;
|
|
6663
|
+
const tempInPlace = path.join(currentDir, `.rulesync-update-${crypto.randomUUID()}`);
|
|
6317
6664
|
try {
|
|
6665
|
+
await fs.promises.copyFile(tempBinaryPath, tempInPlace);
|
|
6318
6666
|
if (os.platform() !== "win32") {
|
|
6319
|
-
await fs.promises.chmod(
|
|
6667
|
+
await fs.promises.chmod(tempInPlace, 493);
|
|
6320
6668
|
}
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
const checksums = parseSha256Sums(checksumsContent);
|
|
6327
|
-
const expectedChecksum = checksums.get(assetName);
|
|
6328
|
-
if (!expectedChecksum) {
|
|
6329
|
-
throw new Error(
|
|
6330
|
-
`Checksum entry for "${assetName}" not found in SHA256SUMS. Cannot verify download integrity.`
|
|
6331
|
-
);
|
|
6669
|
+
await fs.promises.rename(tempInPlace, currentExePath);
|
|
6670
|
+
} catch {
|
|
6671
|
+
try {
|
|
6672
|
+
await fs.promises.unlink(tempInPlace);
|
|
6673
|
+
} catch {
|
|
6332
6674
|
}
|
|
6333
|
-
|
|
6334
|
-
if (
|
|
6335
|
-
|
|
6336
|
-
`Checksum verification failed. Expected: ${expectedChecksum}, Got: ${actualChecksum}. The download may be corrupted.`
|
|
6337
|
-
);
|
|
6675
|
+
await fs.promises.copyFile(tempBinaryPath, currentExePath);
|
|
6676
|
+
if (os.platform() !== "win32") {
|
|
6677
|
+
await fs.promises.chmod(currentExePath, 493);
|
|
6338
6678
|
}
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
throw
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
async function installVerifiedBinary(params) {
|
|
6682
|
+
const { tempDir, tempBinaryPath, currentVersion, latestVersion } = params;
|
|
6683
|
+
const currentExePath = await fs.promises.realpath(process.execPath);
|
|
6684
|
+
const currentDir = path.dirname(currentExePath);
|
|
6685
|
+
const backupPath = path.join(tempDir, "rulesync.backup");
|
|
6686
|
+
try {
|
|
6687
|
+
await fs.promises.copyFile(currentExePath, backupPath);
|
|
6688
|
+
} catch (error) {
|
|
6689
|
+
if (isPermissionError(error)) {
|
|
6690
|
+
throw new UpdatePermissionError(
|
|
6691
|
+
`Permission denied: Cannot read ${currentExePath}. Try running with sudo.`
|
|
6692
|
+
);
|
|
6351
6693
|
}
|
|
6694
|
+
throw error;
|
|
6695
|
+
}
|
|
6696
|
+
try {
|
|
6697
|
+
await replaceCurrentBinary({ tempBinaryPath, currentExePath, currentDir });
|
|
6698
|
+
return {
|
|
6699
|
+
message: `Successfully updated from ${currentVersion} to ${latestVersion}`,
|
|
6700
|
+
restoreFailed: false
|
|
6701
|
+
};
|
|
6702
|
+
} catch (error) {
|
|
6352
6703
|
try {
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
await fs.promises.chmod(tempInPlace, 493);
|
|
6358
|
-
}
|
|
6359
|
-
await fs.promises.rename(tempInPlace, currentExePath);
|
|
6360
|
-
} catch {
|
|
6361
|
-
try {
|
|
6362
|
-
await fs.promises.unlink(tempInPlace);
|
|
6363
|
-
} catch {
|
|
6364
|
-
}
|
|
6365
|
-
await fs.promises.copyFile(tempBinaryPath, currentExePath);
|
|
6366
|
-
if (os.platform() !== "win32") {
|
|
6367
|
-
await fs.promises.chmod(currentExePath, 493);
|
|
6368
|
-
}
|
|
6369
|
-
}
|
|
6370
|
-
return `Successfully updated from ${currentVersion} to ${updateCheck.latestVersion}`;
|
|
6371
|
-
} catch (error) {
|
|
6372
|
-
try {
|
|
6373
|
-
await fs.promises.copyFile(backupPath, currentExePath);
|
|
6374
|
-
} catch {
|
|
6375
|
-
restoreFailed = true;
|
|
6376
|
-
throw new Error(
|
|
6704
|
+
await fs.promises.copyFile(backupPath, currentExePath);
|
|
6705
|
+
} catch {
|
|
6706
|
+
throw new RestoreFailedError(
|
|
6707
|
+
new Error(
|
|
6377
6708
|
`Failed to replace binary and restore failed. Backup is preserved at: ${backupPath} (in ${tempDir}). Please manually copy it to ${currentExePath}. Original error: ${error instanceof Error ? error.message : String(error)}`,
|
|
6378
6709
|
{ cause: error }
|
|
6379
|
-
)
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
)
|
|
6385
|
-
|
|
6386
|
-
|
|
6710
|
+
)
|
|
6711
|
+
);
|
|
6712
|
+
}
|
|
6713
|
+
if (isPermissionError(error)) {
|
|
6714
|
+
throw new UpdatePermissionError(
|
|
6715
|
+
`Permission denied: Cannot write to ${path.dirname(currentExePath)}. Try running with sudo.`
|
|
6716
|
+
);
|
|
6717
|
+
}
|
|
6718
|
+
throw error;
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
var RestoreFailedError = class extends Error {
|
|
6722
|
+
cause;
|
|
6723
|
+
constructor(cause) {
|
|
6724
|
+
super(cause.message);
|
|
6725
|
+
this.name = "RestoreFailedError";
|
|
6726
|
+
this.cause = cause;
|
|
6727
|
+
}
|
|
6728
|
+
};
|
|
6729
|
+
async function performBinaryUpdate(currentVersion, options = {}) {
|
|
6730
|
+
const { force = false, token } = options;
|
|
6731
|
+
const updateCheck = await checkForUpdate(currentVersion, token);
|
|
6732
|
+
if (!updateCheck.hasUpdate && !force) {
|
|
6733
|
+
return `Already at the latest version (${currentVersion})`;
|
|
6734
|
+
}
|
|
6735
|
+
const { assetName, binaryAsset, checksumAsset } = resolveUpdateAssets(updateCheck.release);
|
|
6736
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "rulesync-update-"));
|
|
6737
|
+
let restoreFailed = false;
|
|
6738
|
+
try {
|
|
6739
|
+
if (os.platform() !== "win32") {
|
|
6740
|
+
await fs.promises.chmod(tempDir, 448);
|
|
6741
|
+
}
|
|
6742
|
+
const tempBinaryPath = await downloadAndVerifyBinary({
|
|
6743
|
+
tempDir,
|
|
6744
|
+
assetName,
|
|
6745
|
+
binaryAsset,
|
|
6746
|
+
checksumAsset
|
|
6747
|
+
});
|
|
6748
|
+
const installed = await installVerifiedBinary({
|
|
6749
|
+
tempDir,
|
|
6750
|
+
tempBinaryPath,
|
|
6751
|
+
currentVersion,
|
|
6752
|
+
latestVersion: updateCheck.latestVersion
|
|
6753
|
+
});
|
|
6754
|
+
restoreFailed = installed.restoreFailed;
|
|
6755
|
+
return installed.message;
|
|
6756
|
+
} catch (error) {
|
|
6757
|
+
if (error instanceof RestoreFailedError) {
|
|
6758
|
+
restoreFailed = true;
|
|
6759
|
+
throw error.cause;
|
|
6387
6760
|
}
|
|
6761
|
+
throw error;
|
|
6388
6762
|
} finally {
|
|
6389
6763
|
if (!restoreFailed) {
|
|
6390
6764
|
try {
|
|
@@ -6515,7 +6889,7 @@ function wrapCommand({
|
|
|
6515
6889
|
}
|
|
6516
6890
|
|
|
6517
6891
|
// src/cli/index.ts
|
|
6518
|
-
var getVersion = () => "8.
|
|
6892
|
+
var getVersion = () => "8.32.0";
|
|
6519
6893
|
function wrapCommand2(name, errorCode, handler) {
|
|
6520
6894
|
return wrapCommand({ name, errorCode, handler, getVersion });
|
|
6521
6895
|
}
|