arcane-cli 0.31.0 → 0.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/dist/index.js +327 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,11 +16,17 @@ import chalk2 from "chalk";
|
|
|
16
16
|
// src/modules/copier.ts
|
|
17
17
|
import {
|
|
18
18
|
mkdir,
|
|
19
|
-
copyFile as fsCopyFile,
|
|
20
19
|
access,
|
|
21
|
-
readdir
|
|
20
|
+
readdir,
|
|
21
|
+
readFile,
|
|
22
|
+
writeFile
|
|
22
23
|
} from "fs/promises";
|
|
24
|
+
import { createHash } from "crypto";
|
|
23
25
|
import path from "path";
|
|
26
|
+
async function hashFile(filePath) {
|
|
27
|
+
const content = await readFile(filePath);
|
|
28
|
+
return createHash("sha256").update(content).digest("hex");
|
|
29
|
+
}
|
|
24
30
|
function validateTargetPath(targetDir, relativePath) {
|
|
25
31
|
const resolvedBase = path.resolve(targetDir);
|
|
26
32
|
const resolvedFull = path.resolve(targetDir, relativePath);
|
|
@@ -50,7 +56,10 @@ async function copyFile(srcPath, targetDir, relativePath, opts = {}) {
|
|
|
50
56
|
`Destination already exists: "${relativePath}". Use --force to overwrite.`
|
|
51
57
|
);
|
|
52
58
|
}
|
|
53
|
-
await
|
|
59
|
+
const content = await readFile(srcPath);
|
|
60
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
61
|
+
await writeFile(destPath, content);
|
|
62
|
+
return hash;
|
|
54
63
|
}
|
|
55
64
|
async function copyDirectory(srcDir, targetDir, relativeDir, opts = {}) {
|
|
56
65
|
validateTargetPath(targetDir, relativeDir);
|
|
@@ -63,15 +72,15 @@ async function copyDirectory(srcDir, targetDir, relativeDir, opts = {}) {
|
|
|
63
72
|
const subFiles = await copyDirectory(srcEntryPath, targetDir, relEntryPath, opts);
|
|
64
73
|
copiedFiles.push(...subFiles);
|
|
65
74
|
} else {
|
|
66
|
-
await copyFile(srcEntryPath, targetDir, relEntryPath, opts);
|
|
67
|
-
copiedFiles.push(relEntryPath);
|
|
75
|
+
const hash = await copyFile(srcEntryPath, targetDir, relEntryPath, opts);
|
|
76
|
+
copiedFiles.push({ path: relEntryPath, hash });
|
|
68
77
|
}
|
|
69
78
|
}
|
|
70
79
|
return copiedFiles;
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
// src/modules/manifest.ts
|
|
74
|
-
import { readFile, writeFile } from "fs/promises";
|
|
83
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
75
84
|
import path2 from "path";
|
|
76
85
|
var ManifestNotFoundError = class extends Error {
|
|
77
86
|
constructor(manifestPath2) {
|
|
@@ -154,7 +163,7 @@ async function readManifest(targetDir) {
|
|
|
154
163
|
const filePath = manifestPath(targetDir);
|
|
155
164
|
let content;
|
|
156
165
|
try {
|
|
157
|
-
content = await
|
|
166
|
+
content = await readFile2(filePath, "utf-8");
|
|
158
167
|
} catch (err) {
|
|
159
168
|
if (err.code === "ENOENT") {
|
|
160
169
|
throw new ManifestNotFoundError(filePath);
|
|
@@ -180,7 +189,7 @@ async function resolveSecretsScanExcludePrefixes(targetDir) {
|
|
|
180
189
|
} catch {
|
|
181
190
|
}
|
|
182
191
|
try {
|
|
183
|
-
const raw = await
|
|
192
|
+
const raw = await readFile2(path2.join(targetDir, "src", "assets", ".arcane.json"), "utf-8");
|
|
184
193
|
const parsed = JSON.parse(raw);
|
|
185
194
|
if (isRecord(parsed) && parsed["selfHosted"] === true && parsed["tracking_mode"] === "internal") {
|
|
186
195
|
const prefixes = parsed["secretsScanExcludePrefixes"];
|
|
@@ -192,7 +201,7 @@ async function resolveSecretsScanExcludePrefixes(targetDir) {
|
|
|
192
201
|
}
|
|
193
202
|
async function writeManifest(targetDir, manifest) {
|
|
194
203
|
const filePath = manifestPath(targetDir);
|
|
195
|
-
await
|
|
204
|
+
await writeFile2(filePath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
196
205
|
}
|
|
197
206
|
function addComponent(manifest, component) {
|
|
198
207
|
return {
|
|
@@ -866,7 +875,7 @@ function printWarning(message) {
|
|
|
866
875
|
|
|
867
876
|
// src/modules/agents.ts
|
|
868
877
|
import { select, checkbox, Separator } from "@inquirer/prompts";
|
|
869
|
-
import { mkdir as mkdir3, writeFile as
|
|
878
|
+
import { mkdir as mkdir3, writeFile as writeFile5, copyFile as copyFile2 } from "fs/promises";
|
|
870
879
|
import { join as join3 } from "path";
|
|
871
880
|
import { stringify } from "yaml";
|
|
872
881
|
|
|
@@ -1102,7 +1111,7 @@ async function applyNamingStrategy(strategy, roleIds) {
|
|
|
1102
1111
|
}
|
|
1103
1112
|
|
|
1104
1113
|
// src/modules/agent-loader.ts
|
|
1105
|
-
import { readFile as
|
|
1114
|
+
import { readFile as readFile3, readdir as readdir2, access as access2 } from "fs/promises";
|
|
1106
1115
|
import { join } from "path";
|
|
1107
1116
|
import { parse } from "yaml";
|
|
1108
1117
|
|
|
@@ -1295,7 +1304,7 @@ async function loadAgentDefinition(agentsDir, id) {
|
|
|
1295
1304
|
const filePath = join(agentsDir, `${id}.yaml`);
|
|
1296
1305
|
let content;
|
|
1297
1306
|
try {
|
|
1298
|
-
content = await
|
|
1307
|
+
content = await readFile3(filePath, "utf8");
|
|
1299
1308
|
} catch {
|
|
1300
1309
|
throw new AgentDefinitionNotFoundError(id, agentsDir);
|
|
1301
1310
|
}
|
|
@@ -1305,7 +1314,7 @@ async function loadRoster(targetDir) {
|
|
|
1305
1314
|
const rosterPath = join(targetDir, ".arcane", "agents.yaml");
|
|
1306
1315
|
let content;
|
|
1307
1316
|
try {
|
|
1308
|
-
content = await
|
|
1317
|
+
content = await readFile3(rosterPath, "utf8");
|
|
1309
1318
|
} catch {
|
|
1310
1319
|
throw new AgentRosterNotFoundError();
|
|
1311
1320
|
}
|
|
@@ -1324,12 +1333,12 @@ function projectAgentsDir(targetDir) {
|
|
|
1324
1333
|
}
|
|
1325
1334
|
|
|
1326
1335
|
// src/modules/agent-generator.ts
|
|
1327
|
-
import { writeFile as
|
|
1336
|
+
import { writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
|
|
1328
1337
|
import { join as join2 } from "path";
|
|
1329
1338
|
import { homedir } from "os";
|
|
1330
1339
|
|
|
1331
1340
|
// src/modules/merger.ts
|
|
1332
|
-
import { readFile as
|
|
1341
|
+
import { readFile as readFile4, writeFile as writeFile3, rm } from "fs/promises";
|
|
1333
1342
|
import path3 from "path";
|
|
1334
1343
|
var MARKER_START = "<!-- arcane:start -->";
|
|
1335
1344
|
var MARKER_END = "<!-- arcane:end -->";
|
|
@@ -1344,7 +1353,7 @@ async function mergeIntoFile(targetDir, relativePath, content, opts = {}) {
|
|
|
1344
1353
|
const destPath = path3.resolve(targetDir, relativePath);
|
|
1345
1354
|
let existing;
|
|
1346
1355
|
try {
|
|
1347
|
-
existing = await
|
|
1356
|
+
existing = await readFile4(destPath, "utf8");
|
|
1348
1357
|
} catch (err) {
|
|
1349
1358
|
const e = err;
|
|
1350
1359
|
if (e.code === "ENOENT") {
|
|
@@ -1360,7 +1369,7 @@ ${content}
|
|
|
1360
1369
|
${MARKER_END}
|
|
1361
1370
|
`;
|
|
1362
1371
|
if (!opts.dryRun) {
|
|
1363
|
-
await
|
|
1372
|
+
await writeFile3(destPath, newContent, "utf8");
|
|
1364
1373
|
}
|
|
1365
1374
|
return true;
|
|
1366
1375
|
}
|
|
@@ -1385,7 +1394,7 @@ ${MARKER_END}
|
|
|
1385
1394
|
${content}
|
|
1386
1395
|
${afterMarker}`;
|
|
1387
1396
|
if (!opts.dryRun) {
|
|
1388
|
-
await
|
|
1397
|
+
await writeFile3(destPath, updated, "utf8");
|
|
1389
1398
|
}
|
|
1390
1399
|
return true;
|
|
1391
1400
|
}
|
|
@@ -1395,7 +1404,7 @@ ${content}
|
|
|
1395
1404
|
${MARKER_END}
|
|
1396
1405
|
`;
|
|
1397
1406
|
if (!opts.dryRun) {
|
|
1398
|
-
await
|
|
1407
|
+
await writeFile3(destPath, appended, "utf8");
|
|
1399
1408
|
}
|
|
1400
1409
|
return true;
|
|
1401
1410
|
}
|
|
@@ -1403,7 +1412,7 @@ async function stripMarkerSection(targetDir, relativePath, opts = {}) {
|
|
|
1403
1412
|
const destPath = path3.resolve(targetDir, relativePath);
|
|
1404
1413
|
let existing;
|
|
1405
1414
|
try {
|
|
1406
|
-
existing = await
|
|
1415
|
+
existing = await readFile4(destPath, "utf8");
|
|
1407
1416
|
} catch {
|
|
1408
1417
|
return false;
|
|
1409
1418
|
}
|
|
@@ -1417,7 +1426,7 @@ async function stripMarkerSection(targetDir, relativePath, opts = {}) {
|
|
|
1417
1426
|
if (stripped.length === 0) {
|
|
1418
1427
|
await rm(destPath, { force: true });
|
|
1419
1428
|
} else {
|
|
1420
|
-
await
|
|
1429
|
+
await writeFile3(destPath, stripped + "\n", "utf8");
|
|
1421
1430
|
}
|
|
1422
1431
|
}
|
|
1423
1432
|
return true;
|
|
@@ -1571,17 +1580,17 @@ async function syncAgents(targetDir, assetsDir, roster, options = {}) {
|
|
|
1571
1580
|
try {
|
|
1572
1581
|
if (!options.dryRun) {
|
|
1573
1582
|
await mkdir2(workspaceDir, { recursive: true });
|
|
1574
|
-
await
|
|
1583
|
+
await writeFile4(
|
|
1575
1584
|
join2(workspaceDir, "IDENTITY.md"),
|
|
1576
1585
|
renderIdentity(def, displayName),
|
|
1577
1586
|
"utf8"
|
|
1578
1587
|
);
|
|
1579
|
-
await
|
|
1588
|
+
await writeFile4(
|
|
1580
1589
|
join2(workspaceDir, "SOUL.md"),
|
|
1581
1590
|
renderSoul(def, displayName),
|
|
1582
1591
|
"utf8"
|
|
1583
1592
|
);
|
|
1584
|
-
await
|
|
1593
|
+
await writeFile4(
|
|
1585
1594
|
join2(workspaceDir, "TOOLS.md"),
|
|
1586
1595
|
renderTools(def, displayName),
|
|
1587
1596
|
"utf8"
|
|
@@ -1610,7 +1619,7 @@ async function syncAgents(targetDir, assetsDir, roster, options = {}) {
|
|
|
1610
1619
|
const generatedDir = join2(targetDir, ".arcane", "generated");
|
|
1611
1620
|
if (!options.dryRun) {
|
|
1612
1621
|
await mkdir2(generatedDir, { recursive: true });
|
|
1613
|
-
await
|
|
1622
|
+
await writeFile4(
|
|
1614
1623
|
join2(generatedDir, "openclaw-roster.json"),
|
|
1615
1624
|
JSON.stringify(patch, null, 2) + "\n",
|
|
1616
1625
|
"utf8"
|
|
@@ -1632,7 +1641,7 @@ async function syncAgents(targetDir, assetsDir, roster, options = {}) {
|
|
|
1632
1641
|
const slug = displayName.toLowerCase().replace(/\s+/g, "-");
|
|
1633
1642
|
const fileName = `${slug}.agent.md`;
|
|
1634
1643
|
if (!options.dryRun) {
|
|
1635
|
-
await
|
|
1644
|
+
await writeFile4(
|
|
1636
1645
|
join2(agentsDir, fileName),
|
|
1637
1646
|
renderCopilotAgent(def, displayName),
|
|
1638
1647
|
"utf8"
|
|
@@ -1777,7 +1786,7 @@ All 12 roles across ${allCategories.join(", ")}` : "\nPick individual roles from
|
|
|
1777
1786
|
const arcaneDir = join3(targetDir, ".arcane", "agents");
|
|
1778
1787
|
if (!options.dryRun) {
|
|
1779
1788
|
await mkdir3(arcaneDir, { recursive: true });
|
|
1780
|
-
await
|
|
1789
|
+
await writeFile5(
|
|
1781
1790
|
join3(targetDir, ".arcane", "agents.yaml"),
|
|
1782
1791
|
stringify(roster),
|
|
1783
1792
|
"utf8"
|
|
@@ -1921,7 +1930,7 @@ async function runAgentsList(targetDir) {
|
|
|
1921
1930
|
}
|
|
1922
1931
|
|
|
1923
1932
|
// src/modules/push-safety.ts
|
|
1924
|
-
import { mkdir as mkdir4, readFile as
|
|
1933
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile6, chmod, access as access3, rm as rm2, readdir as readdir3 } from "fs/promises";
|
|
1925
1934
|
import { constants } from "fs";
|
|
1926
1935
|
import { join as join4, resolve, dirname, basename } from "path";
|
|
1927
1936
|
|
|
@@ -2261,7 +2270,7 @@ async function installHook(cwd, hookName, body) {
|
|
|
2261
2270
|
return { status: "already-ours" };
|
|
2262
2271
|
}
|
|
2263
2272
|
await mkdir4(join4(root, ARCANE_HOOKS_DIR), { recursive: true });
|
|
2264
|
-
await
|
|
2273
|
+
await writeFile6(hookPath, body, "utf-8");
|
|
2265
2274
|
await chmod(hookPath, 493);
|
|
2266
2275
|
await runGit(cwd, ["config", "--local", "core.hooksPath", await hooksPathValue(cwd)]);
|
|
2267
2276
|
return { status: "installed", path: hookPath };
|
|
@@ -2296,7 +2305,7 @@ async function hookFileMatches(cwd, hookName, body) {
|
|
|
2296
2305
|
const hookPath = await hookFilePath(cwd, hookName);
|
|
2297
2306
|
if (!await fileExists(hookPath)) return false;
|
|
2298
2307
|
try {
|
|
2299
|
-
if (await
|
|
2308
|
+
if (await readFile5(hookPath, "utf-8") !== body) return false;
|
|
2300
2309
|
} catch {
|
|
2301
2310
|
return false;
|
|
2302
2311
|
}
|
|
@@ -2755,13 +2764,14 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2755
2764
|
let fileCount = 0;
|
|
2756
2765
|
for (const component of components) {
|
|
2757
2766
|
const installedFiles = [];
|
|
2767
|
+
const fileHashes = {};
|
|
2758
2768
|
for (const file of component.files) {
|
|
2759
2769
|
const srcPath = join5(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
2760
2770
|
if (options.dryRun) {
|
|
2761
2771
|
printDryRun(`Would copy: ${file}`);
|
|
2762
2772
|
} else {
|
|
2763
2773
|
try {
|
|
2764
|
-
await copyFile(srcPath, targetDir, file, { force: options.force });
|
|
2774
|
+
fileHashes[file] = await copyFile(srcPath, targetDir, file, { force: options.force });
|
|
2765
2775
|
} catch (err) {
|
|
2766
2776
|
if (component.skipExisting && err instanceof Error && err.message.includes("already exists")) {
|
|
2767
2777
|
continue;
|
|
@@ -2778,7 +2788,10 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2778
2788
|
printDryRun(`Would copy directory: ${dir}/`);
|
|
2779
2789
|
} else {
|
|
2780
2790
|
const copied = await copyDirectory(srcDirPath, targetDir, dir, { force: options.force });
|
|
2781
|
-
|
|
2791
|
+
for (const { path: copiedPath, hash } of copied) {
|
|
2792
|
+
installedFiles.push(copiedPath);
|
|
2793
|
+
fileHashes[copiedPath] = hash;
|
|
2794
|
+
}
|
|
2782
2795
|
fileCount += copied.length;
|
|
2783
2796
|
}
|
|
2784
2797
|
}
|
|
@@ -2786,7 +2799,8 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2786
2799
|
installedComponents.push({
|
|
2787
2800
|
name: component.name,
|
|
2788
2801
|
files: installedFiles,
|
|
2789
|
-
installedVersion: packageVersion
|
|
2802
|
+
installedVersion: packageVersion,
|
|
2803
|
+
fileHashes
|
|
2790
2804
|
});
|
|
2791
2805
|
}
|
|
2792
2806
|
}
|
|
@@ -3067,12 +3081,13 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
3067
3081
|
}
|
|
3068
3082
|
let fileCount = 0;
|
|
3069
3083
|
const installedFiles = [];
|
|
3084
|
+
const fileHashes = {};
|
|
3070
3085
|
for (const file of component.files) {
|
|
3071
3086
|
const srcPath = join6(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
3072
3087
|
if (options.dryRun) {
|
|
3073
3088
|
console.log(` [dry-run] Would copy: ${file}`);
|
|
3074
3089
|
} else {
|
|
3075
|
-
await copyFile(srcPath, targetDir, file, { force: options.force });
|
|
3090
|
+
fileHashes[file] = await copyFile(srcPath, targetDir, file, { force: options.force });
|
|
3076
3091
|
}
|
|
3077
3092
|
installedFiles.push(file);
|
|
3078
3093
|
fileCount++;
|
|
@@ -3083,7 +3098,10 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
3083
3098
|
console.log(` [dry-run] Would copy directory: ${dir}/`);
|
|
3084
3099
|
} else {
|
|
3085
3100
|
const copied = await copyDirectory(srcDirPath, targetDir, dir, { force: options.force });
|
|
3086
|
-
|
|
3101
|
+
for (const { path: copiedPath, hash } of copied) {
|
|
3102
|
+
installedFiles.push(copiedPath);
|
|
3103
|
+
fileHashes[copiedPath] = hash;
|
|
3104
|
+
}
|
|
3087
3105
|
fileCount += copied.length;
|
|
3088
3106
|
}
|
|
3089
3107
|
}
|
|
@@ -3097,7 +3115,8 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
3097
3115
|
const newComponent = {
|
|
3098
3116
|
name,
|
|
3099
3117
|
files: installedFiles,
|
|
3100
|
-
installedVersion: packageVersion
|
|
3118
|
+
installedVersion: packageVersion,
|
|
3119
|
+
fileHashes
|
|
3101
3120
|
};
|
|
3102
3121
|
const updated = addComponent(manifest, newComponent);
|
|
3103
3122
|
await writeManifest(targetDir, updated);
|
|
@@ -3109,10 +3128,11 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
3109
3128
|
|
|
3110
3129
|
// src/commands/update.ts
|
|
3111
3130
|
import { join as join8 } from "path";
|
|
3131
|
+
import { readFile as readFile6, writeFile as writeFile8, rm as rm3 } from "fs/promises";
|
|
3112
3132
|
|
|
3113
3133
|
// src/modules/hub.ts
|
|
3114
3134
|
import { confirm as confirm2, select as select3, input as input3 } from "@inquirer/prompts";
|
|
3115
|
-
import { readdir as readdir4, writeFile as
|
|
3135
|
+
import { readdir as readdir4, writeFile as writeFile7 } from "fs/promises";
|
|
3116
3136
|
import { join as join7 } from "path";
|
|
3117
3137
|
var MANIFEST_RETROFITS = [
|
|
3118
3138
|
{
|
|
@@ -3292,12 +3312,154 @@ async function offerRegistryScaffold(targetDir, businessRoot) {
|
|
|
3292
3312
|
updated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
3293
3313
|
ventures
|
|
3294
3314
|
};
|
|
3295
|
-
await
|
|
3315
|
+
await writeFile7(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
|
|
3296
3316
|
console.log(
|
|
3297
3317
|
` \u2713 Scaffolded ${businessRoot}/registry.json with ${Object.keys(ventures).length} venture(s).`
|
|
3298
3318
|
);
|
|
3299
3319
|
}
|
|
3300
3320
|
|
|
3321
|
+
// src/modules/merge3.ts
|
|
3322
|
+
var CONFLICT_START = "<<<<<<< yours (edited)";
|
|
3323
|
+
var CONFLICT_MID = "=======";
|
|
3324
|
+
var CONFLICT_END = ">>>>>>> incoming (vendor update)";
|
|
3325
|
+
function computeLcsDiff(base, other) {
|
|
3326
|
+
const n = base.length;
|
|
3327
|
+
const m = other.length;
|
|
3328
|
+
const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
|
|
3329
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
3330
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
3331
|
+
dp[i2][j2] = base[i2] === other[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
const ops = [];
|
|
3335
|
+
let i = 0;
|
|
3336
|
+
let j = 0;
|
|
3337
|
+
let pendingBaseStart = 0;
|
|
3338
|
+
let pendingOtherStart = 0;
|
|
3339
|
+
const flushReplace = (baseEnd, otherEnd) => {
|
|
3340
|
+
if (baseEnd > pendingBaseStart || otherEnd > pendingOtherStart) {
|
|
3341
|
+
ops.push({
|
|
3342
|
+
type: "replace",
|
|
3343
|
+
baseStart: pendingBaseStart,
|
|
3344
|
+
baseEnd,
|
|
3345
|
+
otherStart: pendingOtherStart,
|
|
3346
|
+
otherEnd
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
while (i < n && j < m) {
|
|
3351
|
+
if (base[i] === other[j]) {
|
|
3352
|
+
flushReplace(i, j);
|
|
3353
|
+
const eqBaseStart = i;
|
|
3354
|
+
const eqOtherStart = j;
|
|
3355
|
+
while (i < n && j < m && base[i] === other[j]) {
|
|
3356
|
+
i++;
|
|
3357
|
+
j++;
|
|
3358
|
+
}
|
|
3359
|
+
ops.push({ type: "equal", baseStart: eqBaseStart, baseEnd: i, otherStart: eqOtherStart, otherEnd: j });
|
|
3360
|
+
pendingBaseStart = i;
|
|
3361
|
+
pendingOtherStart = j;
|
|
3362
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
3363
|
+
i++;
|
|
3364
|
+
} else {
|
|
3365
|
+
j++;
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
flushReplace(n, m);
|
|
3369
|
+
return ops;
|
|
3370
|
+
}
|
|
3371
|
+
function arraysEqual(a, b) {
|
|
3372
|
+
if (a.length !== b.length) return false;
|
|
3373
|
+
return a.every((line, idx) => line === b[idx]);
|
|
3374
|
+
}
|
|
3375
|
+
var MAX_LINES_FOR_LCS = 4e3;
|
|
3376
|
+
function splitLines(text) {
|
|
3377
|
+
return text.split("\n");
|
|
3378
|
+
}
|
|
3379
|
+
function merge3(base, ours, theirs) {
|
|
3380
|
+
const baseLines = splitLines(base);
|
|
3381
|
+
const oursLines = splitLines(ours);
|
|
3382
|
+
const theirsLines = splitLines(theirs);
|
|
3383
|
+
if (baseLines.length > MAX_LINES_FOR_LCS || oursLines.length > MAX_LINES_FOR_LCS || theirsLines.length > MAX_LINES_FOR_LCS) {
|
|
3384
|
+
return {
|
|
3385
|
+
content: [CONFLICT_START, ours, CONFLICT_MID, theirs, CONFLICT_END].join("\n"),
|
|
3386
|
+
hasConflict: true
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
const oursOps = computeLcsDiff(baseLines, oursLines).filter((op) => op.type === "replace");
|
|
3390
|
+
const theirsOps = computeLcsDiff(baseLines, theirsLines).filter((op) => op.type === "replace");
|
|
3391
|
+
if (oursOps.length === 0 && theirsOps.length === 0) {
|
|
3392
|
+
return { content: base, hasConflict: false };
|
|
3393
|
+
}
|
|
3394
|
+
const intervals = [
|
|
3395
|
+
...oursOps.map((op) => ({
|
|
3396
|
+
baseStart: op.baseStart,
|
|
3397
|
+
baseEnd: op.baseEnd,
|
|
3398
|
+
source: "ours",
|
|
3399
|
+
lines: oursLines.slice(op.otherStart, op.otherEnd)
|
|
3400
|
+
})),
|
|
3401
|
+
...theirsOps.map((op) => ({
|
|
3402
|
+
baseStart: op.baseStart,
|
|
3403
|
+
baseEnd: op.baseEnd,
|
|
3404
|
+
source: "theirs",
|
|
3405
|
+
lines: theirsLines.slice(op.otherStart, op.otherEnd)
|
|
3406
|
+
}))
|
|
3407
|
+
].sort((a, b) => a.baseStart - b.baseStart || a.baseEnd - b.baseEnd);
|
|
3408
|
+
const groups = [];
|
|
3409
|
+
let groupMaxEnd = -1;
|
|
3410
|
+
for (const iv of intervals) {
|
|
3411
|
+
const current = groups[groups.length - 1];
|
|
3412
|
+
if (current && iv.baseStart < groupMaxEnd) {
|
|
3413
|
+
current.push(iv);
|
|
3414
|
+
groupMaxEnd = Math.max(groupMaxEnd, iv.baseEnd);
|
|
3415
|
+
} else {
|
|
3416
|
+
groups.push([iv]);
|
|
3417
|
+
groupMaxEnd = iv.baseEnd;
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
const resultLines = [];
|
|
3421
|
+
let hasConflict = false;
|
|
3422
|
+
let basePos = 0;
|
|
3423
|
+
for (const group of groups) {
|
|
3424
|
+
const groupStart = Math.min(...group.map((g) => g.baseStart));
|
|
3425
|
+
const groupEnd = Math.max(...group.map((g) => g.baseEnd));
|
|
3426
|
+
resultLines.push(...baseLines.slice(basePos, groupStart));
|
|
3427
|
+
const oursInGroup = group.filter((g) => g.source === "ours");
|
|
3428
|
+
const theirsInGroup = group.filter((g) => g.source === "theirs");
|
|
3429
|
+
if (oursInGroup.length > 0 && theirsInGroup.length === 0) {
|
|
3430
|
+
for (const g of oursInGroup) resultLines.push(...g.lines);
|
|
3431
|
+
} else if (theirsInGroup.length > 0 && oursInGroup.length === 0) {
|
|
3432
|
+
for (const g of theirsInGroup) resultLines.push(...g.lines);
|
|
3433
|
+
} else {
|
|
3434
|
+
const oursGroupLines = oursInGroup.flatMap((g) => g.lines);
|
|
3435
|
+
const theirsGroupLines = theirsInGroup.flatMap((g) => g.lines);
|
|
3436
|
+
if (arraysEqual(oursGroupLines, theirsGroupLines)) {
|
|
3437
|
+
resultLines.push(...oursGroupLines);
|
|
3438
|
+
} else {
|
|
3439
|
+
hasConflict = true;
|
|
3440
|
+
resultLines.push(CONFLICT_START, ...oursGroupLines, CONFLICT_MID, ...theirsGroupLines, CONFLICT_END);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
basePos = groupEnd;
|
|
3444
|
+
}
|
|
3445
|
+
resultLines.push(...baseLines.slice(basePos));
|
|
3446
|
+
return { content: resultLines.join("\n"), hasConflict };
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// src/modules/npm-registry.ts
|
|
3450
|
+
var NPM_PACKAGE_NAME = "arcane-cli";
|
|
3451
|
+
async function fetchPublishedFile(version, relativeAssetPath) {
|
|
3452
|
+
const normalized = relativeAssetPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3453
|
+
const url = `https://unpkg.com/${NPM_PACKAGE_NAME}@${version}/dist/assets/${normalized}`;
|
|
3454
|
+
try {
|
|
3455
|
+
const response = await fetch(url);
|
|
3456
|
+
if (!response.ok) return void 0;
|
|
3457
|
+
return await response.text();
|
|
3458
|
+
} catch {
|
|
3459
|
+
return void 0;
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3301
3463
|
// src/commands/update.ts
|
|
3302
3464
|
function migrateLegacyComponents(components) {
|
|
3303
3465
|
const out = [];
|
|
@@ -3318,6 +3480,23 @@ function migrateLegacyComponents(components) {
|
|
|
3318
3480
|
}
|
|
3319
3481
|
return out;
|
|
3320
3482
|
}
|
|
3483
|
+
async function resolveOrphan(targetDir, file, recordedHash, prune) {
|
|
3484
|
+
const filePath = join8(targetDir, file);
|
|
3485
|
+
if (!await fileExists(filePath)) return "not-found";
|
|
3486
|
+
if (!prune) return "reported";
|
|
3487
|
+
if (recordedHash === void 0) {
|
|
3488
|
+
console.log(` ! Orphaned, not pruned (no recorded hash to verify it's untouched): ${file}`);
|
|
3489
|
+
return "reported";
|
|
3490
|
+
}
|
|
3491
|
+
const currentHash = await hashFile(filePath);
|
|
3492
|
+
if (currentHash !== recordedHash) {
|
|
3493
|
+
console.log(` ! Orphaned but edited since install \u2014 not pruning: ${file}`);
|
|
3494
|
+
return "reported";
|
|
3495
|
+
}
|
|
3496
|
+
await rm3(filePath, { force: true });
|
|
3497
|
+
console.log(` Pruned orphaned file: ${file}`);
|
|
3498
|
+
return "pruned";
|
|
3499
|
+
}
|
|
3321
3500
|
async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
3322
3501
|
let manifest;
|
|
3323
3502
|
try {
|
|
@@ -3370,6 +3549,8 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
3370
3549
|
}
|
|
3371
3550
|
let fileCount = 0;
|
|
3372
3551
|
const updatedComponents = [];
|
|
3552
|
+
const conflictedFiles = [];
|
|
3553
|
+
const orphanReport = [];
|
|
3373
3554
|
const componentsToUpdate = migrateLegacyComponents(manifest.components);
|
|
3374
3555
|
for (const installed of componentsToUpdate) {
|
|
3375
3556
|
let component;
|
|
@@ -3378,12 +3559,25 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
3378
3559
|
} catch (err) {
|
|
3379
3560
|
if (err instanceof ComponentNotFoundError) {
|
|
3380
3561
|
console.log(` ! ${installed.name} not in registry \u2014 skipping.`);
|
|
3562
|
+
for (const file of installed.files) {
|
|
3563
|
+
const status = await resolveOrphan(
|
|
3564
|
+
targetDir,
|
|
3565
|
+
file,
|
|
3566
|
+
installed.fileHashes?.[file],
|
|
3567
|
+
Boolean(options.prune) && !options.dryRun
|
|
3568
|
+
);
|
|
3569
|
+
orphanReport.push({ file, status });
|
|
3570
|
+
}
|
|
3571
|
+
if (options.prune && !options.dryRun && installed.files.every((f) => orphanReport.find((o) => o.file === f)?.status !== "reported")) {
|
|
3572
|
+
continue;
|
|
3573
|
+
}
|
|
3381
3574
|
updatedComponents.push(installed);
|
|
3382
3575
|
continue;
|
|
3383
3576
|
}
|
|
3384
3577
|
throw err;
|
|
3385
3578
|
}
|
|
3386
3579
|
const updatedFiles = [];
|
|
3580
|
+
const fileHashes = {};
|
|
3387
3581
|
for (const file of component.files) {
|
|
3388
3582
|
const srcPath = join8(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
3389
3583
|
const targetExists = await fileExists(join8(targetDir, file));
|
|
@@ -3400,7 +3594,38 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
3400
3594
|
console.log(` [dry-run] Would update: ${file}`);
|
|
3401
3595
|
fileCount++;
|
|
3402
3596
|
} else {
|
|
3403
|
-
|
|
3597
|
+
const recordedHash = installed.fileHashes?.[file];
|
|
3598
|
+
let handled = false;
|
|
3599
|
+
if (recordedHash !== void 0 && targetExists) {
|
|
3600
|
+
const currentHash = await hashFile(join8(targetDir, file));
|
|
3601
|
+
if (currentHash !== recordedHash) {
|
|
3602
|
+
const oldVendorContent = await fetchPublishedFile(installed.installedVersion, file);
|
|
3603
|
+
if (oldVendorContent === void 0) {
|
|
3604
|
+
console.log(
|
|
3605
|
+
` ! Could not fetch the previously published version of ${file} to merge your edits \u2014 left your version untouched. Update it manually if you want the latest.`
|
|
3606
|
+
);
|
|
3607
|
+
handled = true;
|
|
3608
|
+
} else {
|
|
3609
|
+
const [currentContent, newContent] = await Promise.all([
|
|
3610
|
+
readFile6(join8(targetDir, file), "utf-8"),
|
|
3611
|
+
readFile6(srcPath, "utf-8")
|
|
3612
|
+
]);
|
|
3613
|
+
const result = merge3(oldVendorContent, currentContent, newContent);
|
|
3614
|
+
await writeFile8(join8(targetDir, file), result.content, "utf-8");
|
|
3615
|
+
fileHashes[file] = await hashFile(join8(targetDir, file));
|
|
3616
|
+
if (result.hasConflict) {
|
|
3617
|
+
conflictedFiles.push(file);
|
|
3618
|
+
console.log(` \u26A0 Merge conflict in ${file} \u2014 resolve the <<<<<<< markers before committing.`);
|
|
3619
|
+
} else {
|
|
3620
|
+
console.log(` Merged your edits into: ${file}`);
|
|
3621
|
+
}
|
|
3622
|
+
handled = true;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
if (!handled) {
|
|
3627
|
+
fileHashes[file] = await copyFile(srcPath, targetDir, file, { force: true });
|
|
3628
|
+
}
|
|
3404
3629
|
fileCount++;
|
|
3405
3630
|
}
|
|
3406
3631
|
if (!preserveExisting || installed.files.includes(file)) {
|
|
@@ -3413,16 +3638,45 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
3413
3638
|
console.log(` [dry-run] Would update directory: ${dir}/`);
|
|
3414
3639
|
} else {
|
|
3415
3640
|
const copied = await copyDirectory(srcDirPath, targetDir, dir, { force: true });
|
|
3416
|
-
|
|
3641
|
+
for (const { path: copiedPath, hash } of copied) {
|
|
3642
|
+
updatedFiles.push(copiedPath);
|
|
3643
|
+
fileHashes[copiedPath] = hash;
|
|
3644
|
+
}
|
|
3417
3645
|
fileCount += copied.length;
|
|
3418
3646
|
}
|
|
3419
3647
|
}
|
|
3648
|
+
for (const file of installed.files) {
|
|
3649
|
+
if (updatedFiles.includes(file)) continue;
|
|
3650
|
+
const status = await resolveOrphan(
|
|
3651
|
+
targetDir,
|
|
3652
|
+
file,
|
|
3653
|
+
installed.fileHashes?.[file],
|
|
3654
|
+
Boolean(options.prune) && !options.dryRun
|
|
3655
|
+
);
|
|
3656
|
+
orphanReport.push({ file, status });
|
|
3657
|
+
}
|
|
3420
3658
|
updatedComponents.push({
|
|
3421
3659
|
...installed,
|
|
3422
3660
|
files: updatedFiles,
|
|
3423
|
-
installedVersion: packageVersion
|
|
3661
|
+
installedVersion: packageVersion,
|
|
3662
|
+
fileHashes
|
|
3424
3663
|
});
|
|
3425
3664
|
}
|
|
3665
|
+
const orphansToReport = orphanReport.filter((o) => o.status !== "not-found");
|
|
3666
|
+
if (orphansToReport.length > 0) {
|
|
3667
|
+
const verb = options.dryRun ? "[dry-run] Found" : "Found";
|
|
3668
|
+
console.log(
|
|
3669
|
+
`
|
|
3670
|
+
${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update, no longer part of any current component):`
|
|
3671
|
+
);
|
|
3672
|
+
for (const { file, status } of orphansToReport) {
|
|
3673
|
+
const marker = status === "pruned" ? "pruned" : options.prune ? "kept (see reason above)" : "not removed";
|
|
3674
|
+
console.log(` ${file} \u2014 ${marker}`);
|
|
3675
|
+
}
|
|
3676
|
+
if (!options.prune) {
|
|
3677
|
+
console.log(" Run `spell update --prune` to remove the ones that are safe to delete.");
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3426
3680
|
if (options.dryRun) {
|
|
3427
3681
|
console.log(
|
|
3428
3682
|
`
|
|
@@ -3457,13 +3711,21 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
3457
3711
|
`
|
|
3458
3712
|
\u2713 Updated ${fileCount} files.`
|
|
3459
3713
|
);
|
|
3714
|
+
if (conflictedFiles.length > 0) {
|
|
3715
|
+
console.log(
|
|
3716
|
+
`
|
|
3717
|
+
\u26A0 ${conflictedFiles.length} file(s) have unresolved merge conflicts:
|
|
3718
|
+
` + conflictedFiles.map((f) => ` ${f}`).join("\n") + `
|
|
3719
|
+
Search for "<<<<<<< yours" and resolve before committing.`
|
|
3720
|
+
);
|
|
3721
|
+
}
|
|
3460
3722
|
if (manifest.role !== "hub" && updated.role === "hub") {
|
|
3461
3723
|
await offerRegistryScaffold(targetDir, updated.business_root ?? "ventures");
|
|
3462
3724
|
}
|
|
3463
3725
|
}
|
|
3464
3726
|
|
|
3465
3727
|
// src/modules/version-check.ts
|
|
3466
|
-
import { readFile as
|
|
3728
|
+
import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir5 } from "fs/promises";
|
|
3467
3729
|
import { dirname as dirname2, join as join9 } from "path";
|
|
3468
3730
|
import { homedir as homedir2 } from "os";
|
|
3469
3731
|
import { createRequire } from "module";
|
|
@@ -3482,7 +3744,7 @@ var DEFAULT_CACHE_PATH = join9(homedir2(), ".arcane", "version-cache.json");
|
|
|
3482
3744
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3483
3745
|
async function readCache(cachePath) {
|
|
3484
3746
|
try {
|
|
3485
|
-
const content = await
|
|
3747
|
+
const content = await readFile7(cachePath, "utf-8");
|
|
3486
3748
|
const parsed = JSON.parse(content);
|
|
3487
3749
|
if (typeof parsed.latest !== "string" || typeof parsed.checkedAt !== "string") {
|
|
3488
3750
|
return null;
|
|
@@ -3495,7 +3757,7 @@ async function readCache(cachePath) {
|
|
|
3495
3757
|
async function writeCache(cachePath, latest) {
|
|
3496
3758
|
await mkdir5(dirname2(cachePath), { recursive: true });
|
|
3497
3759
|
const entry = { latest, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3498
|
-
await
|
|
3760
|
+
await writeFile9(cachePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
3499
3761
|
}
|
|
3500
3762
|
async function getLatestVersion(feedUrl) {
|
|
3501
3763
|
const normalizedBase = feedUrl.endsWith("/") ? feedUrl : feedUrl + "/";
|
|
@@ -3654,7 +3916,7 @@ async function runStatus(targetDir, packageVersion) {
|
|
|
3654
3916
|
}
|
|
3655
3917
|
|
|
3656
3918
|
// src/commands/uninstall.ts
|
|
3657
|
-
import { rm as
|
|
3919
|
+
import { rm as rm4 } from "fs/promises";
|
|
3658
3920
|
import { join as join10 } from "path";
|
|
3659
3921
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
3660
3922
|
async function runUninstall(options, targetDir) {
|
|
@@ -3720,22 +3982,22 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
|
|
|
3720
3982
|
for (const file of component.files) {
|
|
3721
3983
|
const filePath = join10(targetDir, file);
|
|
3722
3984
|
try {
|
|
3723
|
-
await
|
|
3985
|
+
await rm4(filePath, { force: true });
|
|
3724
3986
|
removed++;
|
|
3725
3987
|
} catch {
|
|
3726
3988
|
}
|
|
3727
3989
|
}
|
|
3728
3990
|
}
|
|
3729
|
-
await
|
|
3991
|
+
await rm4(join10(targetDir, ".arcane.json"), { force: true });
|
|
3730
3992
|
const agentDirs = [
|
|
3731
3993
|
".github/agents",
|
|
3732
3994
|
".arcane/agents",
|
|
3733
3995
|
".arcane/generated"
|
|
3734
3996
|
];
|
|
3735
3997
|
for (const dir of agentDirs) {
|
|
3736
|
-
await
|
|
3998
|
+
await rm4(join10(targetDir, dir), { recursive: true, force: true });
|
|
3737
3999
|
}
|
|
3738
|
-
await
|
|
4000
|
+
await rm4(join10(targetDir, ".arcane/agents.yaml"), { force: true });
|
|
3739
4001
|
const markerFiles = [
|
|
3740
4002
|
"CLAUDE.md",
|
|
3741
4003
|
"AGENTS.md",
|
|
@@ -3753,7 +4015,7 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
|
|
|
3753
4015
|
}
|
|
3754
4016
|
|
|
3755
4017
|
// src/commands/doctor.ts
|
|
3756
|
-
import { access as access4, readFile as
|
|
4018
|
+
import { access as access4, readFile as readFile9, mkdir as mkdir6, copyFile as fsCopyFile } from "fs/promises";
|
|
3757
4019
|
import { join as join12, dirname as dirname3 } from "path";
|
|
3758
4020
|
import { execFile as execFile3 } from "child_process";
|
|
3759
4021
|
import { promisify as promisify2 } from "util";
|
|
@@ -3824,7 +4086,7 @@ function evaluateIncidentGate(records) {
|
|
|
3824
4086
|
}
|
|
3825
4087
|
|
|
3826
4088
|
// src/modules/denylist-scan.ts
|
|
3827
|
-
import { readFile as
|
|
4089
|
+
import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
|
|
3828
4090
|
import { join as join11, relative } from "path";
|
|
3829
4091
|
function escapeRegExp(value) {
|
|
3830
4092
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -3842,7 +4104,7 @@ async function scanFile(absolutePath, displayPath, rules) {
|
|
|
3842
4104
|
const findings = [];
|
|
3843
4105
|
let content;
|
|
3844
4106
|
try {
|
|
3845
|
-
content = await
|
|
4107
|
+
content = await readFile8(absolutePath, "utf8");
|
|
3846
4108
|
} catch {
|
|
3847
4109
|
return findings;
|
|
3848
4110
|
}
|
|
@@ -4129,7 +4391,7 @@ async function checkArcaneManifest(targetDir) {
|
|
|
4129
4391
|
} catch {
|
|
4130
4392
|
const selfHostedManifestPath = join12(targetDir, "src", "assets", ".arcane.json");
|
|
4131
4393
|
try {
|
|
4132
|
-
const raw = await
|
|
4394
|
+
const raw = await readFile9(selfHostedManifestPath, "utf8");
|
|
4133
4395
|
const manifest = JSON.parse(raw);
|
|
4134
4396
|
if (isRecord3(manifest) && manifest["selfHosted"] === true && manifest["tracking_mode"] === "internal") {
|
|
4135
4397
|
return {
|
|
@@ -4148,7 +4410,7 @@ async function checkArcaneManifest(targetDir) {
|
|
|
4148
4410
|
};
|
|
4149
4411
|
}
|
|
4150
4412
|
try {
|
|
4151
|
-
const raw = await
|
|
4413
|
+
const raw = await readFile9(manifestPath2, "utf8");
|
|
4152
4414
|
const manifest = JSON.parse(raw);
|
|
4153
4415
|
if (!isRecord3(manifest) || typeof manifest["version"] !== "string" || !manifest["version"] || !Array.isArray(manifest["components"])) {
|
|
4154
4416
|
return {
|
|
@@ -4257,7 +4519,7 @@ async function fixSessionContinuity(targetDir, assetsDir) {
|
|
|
4257
4519
|
} catch {
|
|
4258
4520
|
const srcPath = join12(assetsDir, file);
|
|
4259
4521
|
await mkdir6(dirname3(destPath), { recursive: true });
|
|
4260
|
-
await
|
|
4522
|
+
await fsCopyFile(srcPath, destPath);
|
|
4261
4523
|
created.push(file);
|
|
4262
4524
|
}
|
|
4263
4525
|
}
|
|
@@ -4319,7 +4581,7 @@ async function checkMcpConfig(targetDir) {
|
|
|
4319
4581
|
const filePath = join12(targetDir, ".mcp.json");
|
|
4320
4582
|
let raw;
|
|
4321
4583
|
try {
|
|
4322
|
-
raw = await
|
|
4584
|
+
raw = await readFile9(filePath, "utf8");
|
|
4323
4585
|
} catch {
|
|
4324
4586
|
return { name, passed: true, blocking: false, message: "no .mcp.json \u2014 nothing to check" };
|
|
4325
4587
|
}
|
|
@@ -4375,7 +4637,7 @@ async function checkDelegations(targetDir) {
|
|
|
4375
4637
|
const filePath = join12(targetDir, ".arcane", "delegations.json");
|
|
4376
4638
|
let raw;
|
|
4377
4639
|
try {
|
|
4378
|
-
raw = await
|
|
4640
|
+
raw = await readFile9(filePath, "utf8");
|
|
4379
4641
|
} catch {
|
|
4380
4642
|
return { name, passed: true, blocking: false, message: "no delegations recorded" };
|
|
4381
4643
|
}
|
|
@@ -4523,13 +4785,13 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
4523
4785
|
}
|
|
4524
4786
|
|
|
4525
4787
|
// src/modules/ward.ts
|
|
4526
|
-
import { readdir as readdir6, readFile as
|
|
4788
|
+
import { readdir as readdir6, readFile as readFile10 } from "fs/promises";
|
|
4527
4789
|
import { join as join13, relative as relative2 } from "path";
|
|
4528
4790
|
async function scanFileAllRules(absolutePath, displayPath, rules) {
|
|
4529
4791
|
const findings = [];
|
|
4530
4792
|
let content;
|
|
4531
4793
|
try {
|
|
4532
|
-
content = await
|
|
4794
|
+
content = await readFile10(absolutePath, "utf8");
|
|
4533
4795
|
} catch {
|
|
4534
4796
|
return findings;
|
|
4535
4797
|
}
|
|
@@ -4624,7 +4886,7 @@ async function runWard(repoRoot, options = {}) {
|
|
|
4624
4886
|
const findings2 = await scanFileAllRules(absolutePath, displayPath, allRules);
|
|
4625
4887
|
if (findings2.length > 0) {
|
|
4626
4888
|
try {
|
|
4627
|
-
fileLines.set(displayPath, (await
|
|
4889
|
+
fileLines.set(displayPath, (await readFile10(absolutePath, "utf8")).split("\n"));
|
|
4628
4890
|
} catch {
|
|
4629
4891
|
}
|
|
4630
4892
|
}
|
|
@@ -4833,9 +5095,12 @@ program.command("add <component>").description("Add a component to an existing A
|
|
|
4833
5095
|
);
|
|
4834
5096
|
}
|
|
4835
5097
|
);
|
|
4836
|
-
program.command("update").description("Update installed components to the current package version").option("--dry-run", "Preview what would be updated without making changes").
|
|
5098
|
+
program.command("update").description("Update installed components to the current package version").option("--dry-run", "Preview what would be updated without making changes").option(
|
|
5099
|
+
"--prune",
|
|
5100
|
+
"Also delete orphaned managed files (tracked before this update, no longer part of any current component). Hash-checked: an edited file is reported, never silently deleted"
|
|
5101
|
+
).action(async (opts) => {
|
|
4837
5102
|
await runUpdate(
|
|
4838
|
-
{ dryRun: opts.dryRun },
|
|
5103
|
+
{ dryRun: opts.dryRun, prune: opts.prune },
|
|
4839
5104
|
process.cwd(),
|
|
4840
5105
|
ASSETS_DIR,
|
|
4841
5106
|
pkg.version
|