arcane-cli 1.10.0 → 1.10.2
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.
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Spell — Sync Pull Request
|
|
3
|
-
description: Safely sync an open PR's branch with its target when it has fallen behind — recoverable pre-rebase ref, mechanical
|
|
3
|
+
description: Safely sync an open PR's branch with its target when it has fallen behind — recoverable pre-rebase ref, mechanical / regenerable / ambiguous conflict handling, force-with-lease push, provider-verified landing. Use when a PR needs rebasing onto a moved target, or was routed here from spell-create-pull-request/spell-ship's own conflict-stop points.
|
|
4
4
|
claude_description: Use PROACTIVELY whenever an open PR has fallen behind its target branch and needs a safe rebase/sync, or when spell-create-pull-request/spell-ship stop on a conflict they route here.
|
|
5
5
|
argument-hint: '[PR number or branch name]'
|
|
6
6
|
agent: agent
|
|
7
|
-
last_updated: 2026-
|
|
7
|
+
last_updated: 2026-10-01
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## Executive Summary
|
|
@@ -13,7 +13,9 @@ last_updated: 2026-08-31
|
|
|
13
13
|
- It is the recovery path `spell-create-pull-request` and `spell-ship` route to when they detect a
|
|
14
14
|
conflict they deliberately don't try to resolve themselves.
|
|
15
15
|
- It draws a hard line between conflicts safe to resolve mechanically (identical changes on both sides)
|
|
16
|
-
and conflicts that need a human — it never guesses on the ambiguous side of that line.
|
|
16
|
+
and conflicts that need a human — it never guesses on the ambiguous side of that line. A third class,
|
|
17
|
+
a script-regenerable artifact, is neither merged nor handed off: it is regenerated against the rebased
|
|
18
|
+
tree and verified by its own check.
|
|
17
19
|
- Every history-rewriting step is preceded by a recoverable ref, and every push uses
|
|
18
20
|
`--force-with-lease`, never bare `--force`.
|
|
19
21
|
|
|
@@ -72,7 +74,7 @@ Resolve the repository's actual merge-strategy ladder from `git-conventions.md`
|
|
|
72
74
|
- **Never** produce a squash or semi-linear result as part of this sync — those are never sanctioned,
|
|
73
75
|
regardless of which of the two above applies.
|
|
74
76
|
|
|
75
|
-
## Step 3 — Classify any conflict: mechanical
|
|
77
|
+
## Step 3 — Classify any conflict: mechanical, regenerable, or ambiguous
|
|
76
78
|
|
|
77
79
|
If Step 2 completes with no conflicts, skip straight to Step 4 — **this is the common case and it must
|
|
78
80
|
not be treated as a special case of conflict handling.**
|
|
@@ -84,6 +86,25 @@ then classify **each conflicted hunk independently** — a single file can conta
|
|
|
84
86
|
same import added the same way), or one side's change is a strict superset of the other's with no
|
|
85
87
|
semantic divergence. Resolve by keeping the superset/identical content, verify no line from either
|
|
86
88
|
side's *unique* content was dropped, stage the file, and continue the rebase/merge.
|
|
89
|
+
- **Regenerable** — the file is a committed artifact that a script owns: a known command regenerates it
|
|
90
|
+
from other files in the tree and overwrites it unconditionally, ignoring any conflict markers already
|
|
91
|
+
in it. Neither side's content is the right answer, because both were computed from trees that no
|
|
92
|
+
longer exist; the correct content is a fresh regeneration against the post-rebase tree. **Do not
|
|
93
|
+
hand-merge the diff and do not pick a side.** Leave the conflicted file as it is, run its regeneration
|
|
94
|
+
command, verify with the file's own `--check` command, then stage the file and continue the
|
|
95
|
+
rebase/merge. Known artifacts and their commands:
|
|
96
|
+
- `docs/plans/*/show-report.{json,html}` — `npm run fix:report` in Arcane's own repository (`spell
|
|
97
|
+
report` in a consumer), verified by `npm run check:report`. On a program's own `completed:` day the
|
|
98
|
+
regeneration must go in a **trailer-free, report-only commit** that touches nothing else, because
|
|
99
|
+
the close-commit logic counts any other commit made that day (recorded in
|
|
100
|
+
`docs/plans/upstream-intake-2026-09/PLAN.md`, "What this program taught").
|
|
101
|
+
- Self-hosted parity copies and client shims under `.arcane/`, `.github/`, `.claude/` and `.agents/`
|
|
102
|
+
in Arcane's own repository — `npm run fix:self-host-parity`, verified by
|
|
103
|
+
`npm run check:self-host-parity`. A compiled asset with its own `fix:*`/`check:*` pair in
|
|
104
|
+
`package.json` follows the same rule.
|
|
105
|
+
- `package-lock.json` — `npm install` against the rebased `package.json`, verified by `npm ci`.
|
|
106
|
+
A file you cannot name a regeneration command for is not regenerable: classify its hunks as mechanical
|
|
107
|
+
or ambiguous like any other, and never promote a hand-edited file to this class to avoid a STOP.
|
|
87
108
|
- **Ambiguous** — the same lines changed with different intent, or you cannot establish with confidence
|
|
88
109
|
that one side's content is a strict superset of the other's. **STOP.** Do not guess, do not pick a
|
|
89
110
|
side, do not attempt a "reasonable-looking" merge of the two. Abort the in-progress rebase/merge
|
|
@@ -177,6 +198,8 @@ report the discrepancy rather than declaring the sync done.
|
|
|
177
198
|
```
|
|
178
199
|
|
|
179
200
|
- If Step 3 resolved any mechanical conflicts, list exactly which files and what was kept from each side.
|
|
201
|
+
- If Step 3 regenerated any artifact, name the file, the regeneration command that was run and the
|
|
202
|
+
`--check` command that verified the result.
|
|
180
203
|
- If Step 3 aborted on an ambiguous conflict, this report never runs — Step 3's own STOP report is final.
|
|
181
204
|
- Never delete the Step 1 recoverable ref as part of this report — that is the human's call.
|
|
182
205
|
|
|
@@ -185,6 +208,9 @@ report the discrepancy rather than declaring the sync done.
|
|
|
185
208
|
- Never push with bare `--force` — only `--force-with-lease`, and never retry a rejected lease blindly.
|
|
186
209
|
- Never guess on an ambiguous conflict — abort and hand off, every time, with no exception for
|
|
187
210
|
"obviously trivial-looking" content on either side that this spell cannot independently verify.
|
|
211
|
+
- Never hand-merge a script-regenerable artifact — regenerate it against the rebased tree and verify it
|
|
212
|
+
with its own check; and never classify a file as regenerable without naming the command that
|
|
213
|
+
regenerates it.
|
|
188
214
|
- Never skip the recoverable ref (Step 1), even for a sync expected to be clean — the ref costs nothing
|
|
189
215
|
and the alternative is an unrecoverable mistake on the case that turns out not to be clean.
|
|
190
216
|
- Never report a sync as complete on a push success alone — provider verification (Step 5) is mandatory.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Spell — Sync Pull Request
|
|
3
|
-
description: Safely sync an open PR's branch with its target when it has fallen behind — recoverable pre-rebase ref, mechanical
|
|
3
|
+
description: Safely sync an open PR's branch with its target when it has fallen behind — recoverable pre-rebase ref, mechanical / regenerable / ambiguous conflict handling, force-with-lease push, provider-verified landing. Use when a PR needs rebasing onto a moved target, or was routed here from spell-create-pull-request/spell-ship's own conflict-stop points.
|
|
4
4
|
claude_description: Use PROACTIVELY whenever an open PR has fallen behind its target branch and needs a safe rebase/sync, or when spell-create-pull-request/spell-ship stop on a conflict they route here.
|
|
5
5
|
argument-hint: '[PR number or branch name]'
|
|
6
6
|
agent: agent
|
|
7
|
-
last_updated: 2026-
|
|
7
|
+
last_updated: 2026-10-01
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
This prompt is the Arcane `spell-sync-pull-request` spell. Read [`.arcane/spells/spell-sync-pull-request.md`](../../.arcane/spells/spell-sync-pull-request.md) and follow it as the complete workflow.
|
package/dist/index.js
CHANGED
|
@@ -4417,14 +4417,14 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
|
|
|
4417
4417
|
}
|
|
4418
4418
|
|
|
4419
4419
|
// src/modules/component-install.ts
|
|
4420
|
-
import { stat } from "fs/promises";
|
|
4420
|
+
import { readFile as readFile7, stat, writeFile as writeFile9 } from "fs/promises";
|
|
4421
4421
|
import { join as join8 } from "path";
|
|
4422
4422
|
async function planComponentInstall(component, targetDir, assetsDir, opts = {}) {
|
|
4423
4423
|
const files = [];
|
|
4424
4424
|
const stateOf = async (file, src) => {
|
|
4425
4425
|
const dest = join8(targetDir, file);
|
|
4426
4426
|
if (!await fileExists(dest)) return { file, src, state: "missing" };
|
|
4427
|
-
if ((await stat(dest)).isDirectory()) return { file, src, state: "
|
|
4427
|
+
if ((await stat(dest)).isDirectory()) return { file, src, state: "directory" };
|
|
4428
4428
|
const same = await hashFile(dest) === await hashFile(src);
|
|
4429
4429
|
return { file, src, state: same ? "identical" : "differs" };
|
|
4430
4430
|
};
|
|
@@ -4437,37 +4437,76 @@ async function planComponentInstall(component, targetDir, assetsDir, opts = {})
|
|
|
4437
4437
|
files.push(await stateOf(file, join8(assetsDir, file)));
|
|
4438
4438
|
}
|
|
4439
4439
|
}
|
|
4440
|
-
const
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4440
|
+
const of = (...states) => files.filter((f) => states.includes(f.state));
|
|
4441
|
+
if (opts.force) {
|
|
4442
|
+
return { component, files, toCopy: of("missing", "identical", "differs"), kept: [], conflicts: of("directory") };
|
|
4443
|
+
}
|
|
4444
|
+
if (component.skipExisting) {
|
|
4445
|
+
return { component, files, toCopy: of("missing"), kept: of("identical", "differs", "directory"), conflicts: [] };
|
|
4446
|
+
}
|
|
4447
|
+
return { component, files, toCopy: of("missing", "identical"), kept: [], conflicts: of("differs", "directory") };
|
|
4444
4448
|
}
|
|
4445
4449
|
async function executeInstallPlan(plan, targetDir) {
|
|
4446
4450
|
const files = [];
|
|
4447
4451
|
const fileHashes = {};
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4452
|
+
const written = [];
|
|
4453
|
+
try {
|
|
4454
|
+
for (const { file, src, state } of plan.toCopy) {
|
|
4455
|
+
const previous = state === "missing" ? null : await readFile7(join8(targetDir, file));
|
|
4456
|
+
fileHashes[file] = await copyFile(src, targetDir, file, { force: true });
|
|
4457
|
+
written.push({ file, previous });
|
|
4458
|
+
files.push(file);
|
|
4459
|
+
}
|
|
4460
|
+
} catch (err) {
|
|
4461
|
+
await rollback(written, targetDir);
|
|
4462
|
+
throw err;
|
|
4451
4463
|
}
|
|
4452
4464
|
return { files, fileHashes };
|
|
4453
4465
|
}
|
|
4466
|
+
async function rollback(written, targetDir) {
|
|
4467
|
+
for (const { file, previous } of [...written].reverse()) {
|
|
4468
|
+
try {
|
|
4469
|
+
if (previous === null) {
|
|
4470
|
+
await removeWithin(targetDir, file);
|
|
4471
|
+
await removeEmptyAncestors(targetDir, file);
|
|
4472
|
+
} else {
|
|
4473
|
+
await writeFile9(join8(targetDir, file), previous);
|
|
4474
|
+
}
|
|
4475
|
+
} catch {
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4454
4479
|
|
|
4455
4480
|
// src/commands/add.ts
|
|
4456
4481
|
function describeExisting(f) {
|
|
4482
|
+
if (f.state === "directory") return "is a directory";
|
|
4457
4483
|
return f.state === "identical" ? "identical to the packaged file" : "differs from the packaged file";
|
|
4458
4484
|
}
|
|
4485
|
+
function directoryConflict(plan) {
|
|
4486
|
+
return plan.conflicts.find((f) => f.state === "directory");
|
|
4487
|
+
}
|
|
4459
4488
|
function printDryRun2(name, plan, options) {
|
|
4460
4489
|
for (const f of plan.files) {
|
|
4461
4490
|
if (f.state === "missing") {
|
|
4462
4491
|
console.log(` [dry-run] Would copy: ${f.file}`);
|
|
4492
|
+
} else if (f.state === "directory" && !(plan.component.skipExisting && !options.force)) {
|
|
4493
|
+
console.log(` [dry-run] Would refuse: ${f.file} (exists, is a directory) \u2014 remove it first`);
|
|
4463
4494
|
} else if (options.force) {
|
|
4464
4495
|
console.log(` [dry-run] Would overwrite: ${f.file} (exists, ${describeExisting(f)})`);
|
|
4465
4496
|
} else if (plan.component.skipExisting) {
|
|
4466
4497
|
console.log(` [dry-run] Would keep: ${f.file} (exists, yours, ${describeExisting(f)})`);
|
|
4498
|
+
} else if (f.state === "identical") {
|
|
4499
|
+
console.log(` [dry-run] Would adopt: ${f.file} (exists, identical to the packaged file)`);
|
|
4467
4500
|
} else {
|
|
4468
4501
|
console.log(` [dry-run] Would refuse: ${f.file} (exists, ${describeExisting(f)}) \u2014 needs --force`);
|
|
4469
4502
|
}
|
|
4470
4503
|
}
|
|
4504
|
+
const dir = directoryConflict(plan);
|
|
4505
|
+
if (dir) {
|
|
4506
|
+
console.log(`
|
|
4507
|
+
[dry-run] Would refuse component "${name}": a directory is at "${dir.file}", which --force does not replace. Remove it first.`);
|
|
4508
|
+
return;
|
|
4509
|
+
}
|
|
4471
4510
|
if (plan.conflicts.length > 0) {
|
|
4472
4511
|
console.log(
|
|
4473
4512
|
`
|
|
@@ -4501,6 +4540,13 @@ async function addOne(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
4501
4540
|
printDryRun2(name, plan, options);
|
|
4502
4541
|
return plan.conflicts.length > 0 ? "failed" : "added";
|
|
4503
4542
|
}
|
|
4543
|
+
const dir = directoryConflict(plan);
|
|
4544
|
+
if (dir) {
|
|
4545
|
+
console.error(
|
|
4546
|
+
`Cannot add "${name}": "${dir.file}" is a directory, which --force does not replace. Nothing was written. Remove it first.`
|
|
4547
|
+
);
|
|
4548
|
+
return "failed";
|
|
4549
|
+
}
|
|
4504
4550
|
if (plan.conflicts.length > 0) {
|
|
4505
4551
|
const others = plan.conflicts.length - 1;
|
|
4506
4552
|
console.error(
|
|
@@ -4561,7 +4607,7 @@ async function runAdd(names, options, targetDir, assetsDir, packageVersion) {
|
|
|
4561
4607
|
|
|
4562
4608
|
// src/commands/update.ts
|
|
4563
4609
|
import { dirname as dirname5, join as join10 } from "path";
|
|
4564
|
-
import { readFile as
|
|
4610
|
+
import { readFile as readFile8, writeFile as writeFile10 } from "fs/promises";
|
|
4565
4611
|
|
|
4566
4612
|
// src/modules/merge3.ts
|
|
4567
4613
|
var CONFLICT_START = "<<<<<<< yours (edited)";
|
|
@@ -4819,7 +4865,7 @@ async function planUpdateInstalls(args) {
|
|
|
4819
4865
|
if (component.files.length === 0) continue;
|
|
4820
4866
|
const plan = await planComponentInstall(component, args.targetDir, args.assetsDir, {});
|
|
4821
4867
|
if (plan.conflicts.length > 0) {
|
|
4822
|
-
leftAlone.push({ name, kind: "prerequisite", reason:
|
|
4868
|
+
leftAlone.push({ name, kind: "prerequisite", reason: conflictReason(plan) });
|
|
4823
4869
|
continue;
|
|
4824
4870
|
}
|
|
4825
4871
|
items.push({ plan, kind: "prerequisite", requiredBy });
|
|
@@ -4829,13 +4875,19 @@ async function planUpdateInstalls(args) {
|
|
|
4829
4875
|
if (component.initOnly || component.files.length === 0) continue;
|
|
4830
4876
|
const plan = await planComponentInstall(component, args.targetDir, args.assetsDir, {});
|
|
4831
4877
|
if (plan.conflicts.length > 0) {
|
|
4832
|
-
leftAlone.push({ name: candidate.name, kind: "new", reason:
|
|
4878
|
+
leftAlone.push({ name: candidate.name, kind: "new", reason: conflictReason(plan) });
|
|
4833
4879
|
continue;
|
|
4834
4880
|
}
|
|
4835
4881
|
items.push({ plan, kind: "new", requiredBy: [] });
|
|
4836
4882
|
}
|
|
4837
4883
|
return { items, leftAlone };
|
|
4838
4884
|
}
|
|
4885
|
+
function conflictReason(plan) {
|
|
4886
|
+
const first = plan.conflicts[0];
|
|
4887
|
+
if (first.state === "directory") return `${first.file} is a directory, which update does not replace`;
|
|
4888
|
+
const more = plan.conflicts.length > 1 ? ` (and ${plan.conflicts.length - 1} more)` : "";
|
|
4889
|
+
return `${first.file} already exists${more}, and update does not overwrite`;
|
|
4890
|
+
}
|
|
4839
4891
|
function label(item) {
|
|
4840
4892
|
return item.kind === "prerequisite" ? `prerequisite of ${item.requiredBy.join(", ")}` : "newly available";
|
|
4841
4893
|
}
|
|
@@ -5348,11 +5400,11 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5348
5400
|
handled = true;
|
|
5349
5401
|
} else {
|
|
5350
5402
|
const [currentContent, newContent] = await Promise.all([
|
|
5351
|
-
|
|
5352
|
-
|
|
5403
|
+
readFile8(join10(targetDir, file), "utf-8"),
|
|
5404
|
+
readFile8(srcPath, "utf-8")
|
|
5353
5405
|
]);
|
|
5354
5406
|
const result = merge3(oldVendorContent, currentContent, newContent);
|
|
5355
|
-
await
|
|
5407
|
+
await writeFile10(join10(targetDir, file), result.content, "utf-8");
|
|
5356
5408
|
fileHashes[file] = await hashFile(srcPath);
|
|
5357
5409
|
if (result.hasConflict) {
|
|
5358
5410
|
conflictedFiles.push(file);
|
|
@@ -5537,7 +5589,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
|
|
|
5537
5589
|
import { dirname as dirname7 } from "path";
|
|
5538
5590
|
|
|
5539
5591
|
// src/modules/version-check.ts
|
|
5540
|
-
import { readFile as
|
|
5592
|
+
import { readFile as readFile9, writeFile as writeFile11, mkdir as mkdir6 } from "fs/promises";
|
|
5541
5593
|
import { dirname as dirname6, join as join11 } from "path";
|
|
5542
5594
|
import { homedir as homedir3 } from "os";
|
|
5543
5595
|
import { createRequire } from "module";
|
|
@@ -5556,7 +5608,7 @@ var DEFAULT_CACHE_PATH = join11(homedir3(), ".arcane", "version-cache.json");
|
|
|
5556
5608
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5557
5609
|
async function readCache(cachePath) {
|
|
5558
5610
|
try {
|
|
5559
|
-
const content = await
|
|
5611
|
+
const content = await readFile9(cachePath, "utf-8");
|
|
5560
5612
|
const parsed = JSON.parse(content);
|
|
5561
5613
|
if (typeof parsed.latest !== "string" || typeof parsed.checkedAt !== "string") {
|
|
5562
5614
|
return null;
|
|
@@ -5569,7 +5621,7 @@ async function readCache(cachePath) {
|
|
|
5569
5621
|
async function writeCache(cachePath, latest) {
|
|
5570
5622
|
await mkdir6(dirname6(cachePath), { recursive: true });
|
|
5571
5623
|
const entry = { latest, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5572
|
-
await
|
|
5624
|
+
await writeFile11(cachePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
5573
5625
|
}
|
|
5574
5626
|
async function getLatestVersion(feedUrl) {
|
|
5575
5627
|
const normalizedBase = feedUrl.endsWith("/") ? feedUrl : feedUrl + "/";
|
|
@@ -5940,7 +5992,7 @@ async function uninstallUserTier(options, storeRoot, manifest) {
|
|
|
5940
5992
|
}
|
|
5941
5993
|
|
|
5942
5994
|
// src/commands/doctor.ts
|
|
5943
|
-
import { access as access4, readdir as readdir9, readFile as
|
|
5995
|
+
import { access as access4, readdir as readdir9, readFile as readFile12, mkdir as mkdir7, copyFile as fsCopyFile } from "fs/promises";
|
|
5944
5996
|
import { join as join15, dirname as dirname9 } from "path";
|
|
5945
5997
|
|
|
5946
5998
|
// src/config/incidents.ts
|
|
@@ -6009,7 +6061,7 @@ function evaluateIncidentGate(records) {
|
|
|
6009
6061
|
}
|
|
6010
6062
|
|
|
6011
6063
|
// src/modules/denylist-scan.ts
|
|
6012
|
-
import { readFile as
|
|
6064
|
+
import { readFile as readFile10, readdir as readdir7 } from "fs/promises";
|
|
6013
6065
|
import { join as join13, relative } from "path";
|
|
6014
6066
|
function escapeRegExp(value) {
|
|
6015
6067
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -6027,7 +6079,7 @@ async function scanFile(absolutePath, displayPath, rules) {
|
|
|
6027
6079
|
const findings = [];
|
|
6028
6080
|
let content;
|
|
6029
6081
|
try {
|
|
6030
|
-
content = await
|
|
6082
|
+
content = await readFile10(absolutePath, "utf8");
|
|
6031
6083
|
} catch {
|
|
6032
6084
|
return findings;
|
|
6033
6085
|
}
|
|
@@ -6321,7 +6373,7 @@ function parseAdoRemote(url) {
|
|
|
6321
6373
|
}
|
|
6322
6374
|
|
|
6323
6375
|
// src/modules/placeholders.ts
|
|
6324
|
-
import { readdir as readdir8, readFile as
|
|
6376
|
+
import { readdir as readdir8, readFile as readFile11 } from "fs/promises";
|
|
6325
6377
|
import { join as join14 } from "path";
|
|
6326
6378
|
var PLACEHOLDER_STANDARD_FILE = "spell-authoring-standards.md";
|
|
6327
6379
|
var RUNTIME_PLACEHOLDERS_START = "<!-- runtime-placeholders:start -->";
|
|
@@ -6417,7 +6469,7 @@ function parseRuntimePlaceholders(standardContent) {
|
|
|
6417
6469
|
async function scanGovernancePlaceholders(governanceDir) {
|
|
6418
6470
|
let standard;
|
|
6419
6471
|
try {
|
|
6420
|
-
standard = await
|
|
6472
|
+
standard = await readFile11(join14(governanceDir, PLACEHOLDER_STANDARD_FILE), "utf8");
|
|
6421
6473
|
} catch (err) {
|
|
6422
6474
|
if (err.code === "ENOENT") {
|
|
6423
6475
|
return { status: "skipped", reason: `${PLACEHOLDER_STANDARD_FILE} is not installed` };
|
|
@@ -6436,7 +6488,7 @@ async function scanGovernancePlaceholders(governanceDir) {
|
|
|
6436
6488
|
const templateDocs = [];
|
|
6437
6489
|
const findings = [];
|
|
6438
6490
|
for (const file of files) {
|
|
6439
|
-
const content = await
|
|
6491
|
+
const content = await readFile11(join14(governanceDir, file), "utf8");
|
|
6440
6492
|
const status = parseFrontmatterStatus(content);
|
|
6441
6493
|
if (status === "template") templateDocs.push(file);
|
|
6442
6494
|
if (status !== "active") continue;
|
|
@@ -6502,7 +6554,7 @@ async function checkArcaneManifest(targetDir) {
|
|
|
6502
6554
|
} catch {
|
|
6503
6555
|
const selfHostedManifestPath = join15(targetDir, "src", "assets", ".arcane.json");
|
|
6504
6556
|
try {
|
|
6505
|
-
const raw = await
|
|
6557
|
+
const raw = await readFile12(selfHostedManifestPath, "utf8");
|
|
6506
6558
|
const manifest = JSON.parse(raw);
|
|
6507
6559
|
if (isRecord3(manifest) && manifest["selfHosted"] === true && manifest["tracking_mode"] === "internal") {
|
|
6508
6560
|
return {
|
|
@@ -6521,7 +6573,7 @@ async function checkArcaneManifest(targetDir) {
|
|
|
6521
6573
|
};
|
|
6522
6574
|
}
|
|
6523
6575
|
try {
|
|
6524
|
-
const raw = await
|
|
6576
|
+
const raw = await readFile12(manifestPath2, "utf8");
|
|
6525
6577
|
const manifest = JSON.parse(raw);
|
|
6526
6578
|
if (!isRecord3(manifest) || typeof manifest["version"] !== "string" || !manifest["version"] || !Array.isArray(manifest["components"])) {
|
|
6527
6579
|
return {
|
|
@@ -6845,7 +6897,7 @@ async function checkMcpConfig(targetDir) {
|
|
|
6845
6897
|
const filePath = join15(targetDir, ".mcp.json");
|
|
6846
6898
|
let raw;
|
|
6847
6899
|
try {
|
|
6848
|
-
raw = await
|
|
6900
|
+
raw = await readFile12(filePath, "utf8");
|
|
6849
6901
|
} catch {
|
|
6850
6902
|
return { name, passed: true, blocking: false, message: "no .mcp.json \u2014 nothing to check" };
|
|
6851
6903
|
}
|
|
@@ -6917,7 +6969,7 @@ async function checkDelegations(targetDir) {
|
|
|
6917
6969
|
const filePath = join15(targetDir, ".arcane", "delegations.json");
|
|
6918
6970
|
let raw;
|
|
6919
6971
|
try {
|
|
6920
|
-
raw = await
|
|
6972
|
+
raw = await readFile12(filePath, "utf8");
|
|
6921
6973
|
} catch {
|
|
6922
6974
|
return { name, passed: true, blocking: false, message: "no delegations recorded" };
|
|
6923
6975
|
}
|
|
@@ -7137,16 +7189,16 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
7137
7189
|
}
|
|
7138
7190
|
|
|
7139
7191
|
// src/commands/ward.ts
|
|
7140
|
-
import { readFile as
|
|
7192
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
7141
7193
|
|
|
7142
7194
|
// src/modules/ward.ts
|
|
7143
|
-
import { readdir as readdir10, readFile as
|
|
7195
|
+
import { readdir as readdir10, readFile as readFile13 } from "fs/promises";
|
|
7144
7196
|
import { join as join16, relative as relative2 } from "path";
|
|
7145
7197
|
async function scanFileAllRules(absolutePath, displayPath, rules) {
|
|
7146
7198
|
const findings = [];
|
|
7147
7199
|
let content;
|
|
7148
7200
|
try {
|
|
7149
|
-
content = await
|
|
7201
|
+
content = await readFile13(absolutePath, "utf8");
|
|
7150
7202
|
} catch {
|
|
7151
7203
|
return findings;
|
|
7152
7204
|
}
|
|
@@ -7241,7 +7293,7 @@ async function runWard(repoRoot, options = {}) {
|
|
|
7241
7293
|
const findings2 = await scanFileAllRules(absolutePath, displayPath, allRules);
|
|
7242
7294
|
if (findings2.length > 0) {
|
|
7243
7295
|
try {
|
|
7244
|
-
fileLines.set(displayPath, (await
|
|
7296
|
+
fileLines.set(displayPath, (await readFile13(absolutePath, "utf8")).split("\n"));
|
|
7245
7297
|
} catch {
|
|
7246
7298
|
}
|
|
7247
7299
|
}
|
|
@@ -7269,7 +7321,7 @@ function parseTermsFileContent(raw) {
|
|
|
7269
7321
|
}
|
|
7270
7322
|
async function readTermsFile(filePath) {
|
|
7271
7323
|
try {
|
|
7272
|
-
return parseTermsFileContent(await
|
|
7324
|
+
return parseTermsFileContent(await readFile14(filePath, "utf8"));
|
|
7273
7325
|
} catch (error) {
|
|
7274
7326
|
console.warn(
|
|
7275
7327
|
`\u26A0 arcane: could not read --terms-file "${filePath}": ${error.message}`
|
|
@@ -7484,11 +7536,11 @@ import { access as access5 } from "fs/promises";
|
|
|
7484
7536
|
import { join as join19 } from "path";
|
|
7485
7537
|
|
|
7486
7538
|
// src/modules/show-report/generate.ts
|
|
7487
|
-
import { mkdir as mkdir8, readdir as readdir11, readFile as
|
|
7539
|
+
import { mkdir as mkdir8, readdir as readdir11, readFile as readFile16, writeFile as writeFile12 } from "fs/promises";
|
|
7488
7540
|
import { basename as basename3, dirname as dirname10, isAbsolute, join as join18, relative as relative3, resolve as resolve4 } from "path";
|
|
7489
7541
|
|
|
7490
7542
|
// src/modules/show-report/model.ts
|
|
7491
|
-
import { readFile as
|
|
7543
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
7492
7544
|
import { join as join17 } from "path";
|
|
7493
7545
|
|
|
7494
7546
|
// src/modules/show-report/plan-parser.ts
|
|
@@ -7762,7 +7814,7 @@ async function getCast(cwd, fromRef, toRef, generatedOutputs = []) {
|
|
|
7762
7814
|
// src/modules/show-report/model.ts
|
|
7763
7815
|
async function readOptional(path4) {
|
|
7764
7816
|
try {
|
|
7765
|
-
return await
|
|
7817
|
+
return await readFile15(path4, "utf8");
|
|
7766
7818
|
} catch {
|
|
7767
7819
|
return null;
|
|
7768
7820
|
}
|
|
@@ -7864,7 +7916,7 @@ async function buildShowReportModel(options) {
|
|
|
7864
7916
|
const ledgerAbsPath = join17(options.rootDir, ledgerRelPath);
|
|
7865
7917
|
const decisionsAbsPath = join17(options.rootDir, "DECISIONS.md");
|
|
7866
7918
|
const [planContent, queueContent, ledgerContent, decisionsContent] = await Promise.all([
|
|
7867
|
-
|
|
7919
|
+
readFile15(planAbsPath, "utf8"),
|
|
7868
7920
|
readOptional(queueAbsPath),
|
|
7869
7921
|
readOptional(ledgerAbsPath),
|
|
7870
7922
|
readOptional(decisionsAbsPath)
|
|
@@ -8060,7 +8112,7 @@ function toPosix(path4) {
|
|
|
8060
8112
|
async function programFromPlan(rootDir, slug, planRelPath) {
|
|
8061
8113
|
let planContent;
|
|
8062
8114
|
try {
|
|
8063
|
-
planContent = await
|
|
8115
|
+
planContent = await readFile16(join18(rootDir, planRelPath), "utf8");
|
|
8064
8116
|
} catch {
|
|
8065
8117
|
return { skipped: { slug, reason: `no PLAN.md at ${planRelPath}` } };
|
|
8066
8118
|
}
|
|
@@ -8105,14 +8157,14 @@ function normalizeLineEndings2(content) {
|
|
|
8105
8157
|
}
|
|
8106
8158
|
async function readOptionalFile(path4) {
|
|
8107
8159
|
try {
|
|
8108
|
-
return await
|
|
8160
|
+
return await readFile16(path4, "utf8");
|
|
8109
8161
|
} catch (error) {
|
|
8110
8162
|
if (error.code === "ENOENT") return null;
|
|
8111
8163
|
throw error;
|
|
8112
8164
|
}
|
|
8113
8165
|
}
|
|
8114
8166
|
async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
|
|
8115
|
-
const planContent = await
|
|
8167
|
+
const planContent = await readFile16(join18(rootDir, program2.planRelPath), "utf8");
|
|
8116
8168
|
const frontmatter = parseFrontmatter(planContent);
|
|
8117
8169
|
const model = await buildShowReportModel({
|
|
8118
8170
|
rootDir,
|
|
@@ -8132,7 +8184,7 @@ async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
|
|
|
8132
8184
|
};
|
|
8133
8185
|
}
|
|
8134
8186
|
async function runReportGeneration(options) {
|
|
8135
|
-
const templateHtml = await
|
|
8187
|
+
const templateHtml = await readFile16(options.templatePath, "utf8");
|
|
8136
8188
|
const theme = options.theme ?? "auto";
|
|
8137
8189
|
const drifted = [];
|
|
8138
8190
|
const repaired = [];
|
|
@@ -8155,7 +8207,7 @@ async function runReportGeneration(options) {
|
|
|
8155
8207
|
drifted.push(`${relPath} does not match a regeneration from its sources.`);
|
|
8156
8208
|
if (options.mode === "write") {
|
|
8157
8209
|
await mkdir8(outAbs, { recursive: true });
|
|
8158
|
-
await
|
|
8210
|
+
await writeFile12(join18(options.rootDir, relPath), expected, "utf8");
|
|
8159
8211
|
repaired.push(relPath);
|
|
8160
8212
|
}
|
|
8161
8213
|
}
|