@c4a/context-cli 0.7.36 → 0.7.37
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/cli.js +941 -492
- package/indexers/contracts/profile-contract.json +245 -245
- package/indexers/release-manifest.json +1 -1
- package/package.json +12 -12
- package/plugins/VERSION +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/commands/context.md +3 -5
- package/plugins/codex/.codex-plugin/plugin.json +2 -2
- package/plugins/codex/skills/context/SKILL.md +3 -5
- package/plugins/cursor/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor/commands/c4a-context.md +3 -5
- package/plugins/skills/context/SKILL.md +3 -5
- package/providers/context/actions/apply-confirmed-review.yaml +5 -0
- package/providers/context/graphs/workspace.yaml +1 -1
- package/providers/context/manifest.json +17 -17
- package/providers/context/provider.yaml +1 -1
- package/providers/context/resources/dialogue/knowledge-review.md +55 -52
- package/providers/context/resources/manuals/guides/knowledge-updates.md +14 -10
- package/providers/context/resources/procedures/knowledge-review.md +29 -25
- package/providers/context/resources/procedures/knowledge-updates.md +14 -10
- package/providers/context/actions/apply-forced-review.yaml +0 -5
package/cli.js
CHANGED
|
@@ -92344,11 +92344,11 @@ function reviewApproveCommand(observation) {
|
|
|
92344
92344
|
}
|
|
92345
92345
|
return "context review apply '<review-decisions-file>' --format json";
|
|
92346
92346
|
}
|
|
92347
|
-
function
|
|
92347
|
+
function reviewConfirmedApproveCommand(observation) {
|
|
92348
92348
|
if (observation.draftCandidates <= 0 || observation.draftCollections.length === 0) {
|
|
92349
92349
|
return;
|
|
92350
92350
|
}
|
|
92351
|
-
return observation.draftCollections.length === 1 ? `context review approve-all ${observation.draftCollections[0]} --
|
|
92351
|
+
return observation.draftCollections.length === 1 ? `context review approve-all ${observation.draftCollections[0]} --confirmed --format json` : "context review approve-all --all --confirmed --format json";
|
|
92352
92352
|
}
|
|
92353
92353
|
function workspaceRepairPlan(observation) {
|
|
92354
92354
|
return { commands: [command("context verify --format json --compact", "read")] };
|
|
@@ -92472,8 +92472,8 @@ var init_workflowHostPlans = __esm(() => {
|
|
|
92472
92472
|
commands: next === undefined ? [] : [command(next, "write", "agent-required")]
|
|
92473
92473
|
};
|
|
92474
92474
|
},
|
|
92475
|
-
"context.review.
|
|
92476
|
-
const next =
|
|
92475
|
+
"context.review.confirm-current": (observation) => {
|
|
92476
|
+
const next = reviewConfirmedApproveCommand(observation);
|
|
92477
92477
|
return {
|
|
92478
92478
|
commands: next === undefined ? [] : [command(next, "write")]
|
|
92479
92479
|
};
|
|
@@ -98131,7 +98131,7 @@ var init_larkResourceCommand = __esm(() => {
|
|
|
98131
98131
|
|
|
98132
98132
|
// src/lib/larkMediaDownload.ts
|
|
98133
98133
|
import { readdir as readdir21, rm as rm20 } from "node:fs/promises";
|
|
98134
|
-
import { join as
|
|
98134
|
+
import { join as join95 } from "node:path";
|
|
98135
98135
|
async function downloadLarkMedia(input) {
|
|
98136
98136
|
const shared = ["--as", input.identity, "--token", input.token, "--output", "./resource", "--format", "json"];
|
|
98137
98137
|
try {
|
|
@@ -98141,7 +98141,7 @@ async function downloadLarkMedia(input) {
|
|
|
98141
98141
|
if (!input.allowPreview || !isLarkResourcePermissionDenied(error))
|
|
98142
98142
|
throw error;
|
|
98143
98143
|
for (const entry of await readdir21(input.cwd))
|
|
98144
|
-
await rm20(
|
|
98144
|
+
await rm20(join95(input.cwd, entry), { recursive: true, force: true });
|
|
98145
98145
|
await runLarkResourceCommand(input.runner, ["docs", "+media-preview", ...shared], { cwd: input.cwd });
|
|
98146
98146
|
return "preview";
|
|
98147
98147
|
}
|
|
@@ -98151,9 +98151,9 @@ var init_larkMediaDownload = __esm(() => {
|
|
|
98151
98151
|
});
|
|
98152
98152
|
|
|
98153
98153
|
// src/lib/larkWhiteboardExport.ts
|
|
98154
|
-
import { mkdtemp as mkdtemp4, readFile as
|
|
98154
|
+
import { mkdtemp as mkdtemp4, readFile as readFile75, rm as rm21 } from "node:fs/promises";
|
|
98155
98155
|
import { tmpdir } from "node:os";
|
|
98156
|
-
import { join as
|
|
98156
|
+
import { join as join96 } from "node:path";
|
|
98157
98157
|
function stableJson2(value) {
|
|
98158
98158
|
if (value === undefined)
|
|
98159
98159
|
return "null";
|
|
@@ -98183,7 +98183,7 @@ function canonicalWhiteboardPayload(value) {
|
|
|
98183
98183
|
};
|
|
98184
98184
|
}
|
|
98185
98185
|
async function exportLarkWhiteboardRaw(runner2, identity2, token) {
|
|
98186
|
-
const tempRoot = await mkdtemp4(
|
|
98186
|
+
const tempRoot = await mkdtemp4(join96(tmpdir(), "context-lark-whiteboard-"));
|
|
98187
98187
|
try {
|
|
98188
98188
|
await runLarkResourceCommand(runner2, [
|
|
98189
98189
|
"whiteboard",
|
|
@@ -98200,7 +98200,7 @@ async function exportLarkWhiteboardRaw(runner2, identity2, token) {
|
|
|
98200
98200
|
"--format",
|
|
98201
98201
|
"json"
|
|
98202
98202
|
], { cwd: tempRoot });
|
|
98203
|
-
const rawPayload = JSON.parse(await
|
|
98203
|
+
const rawPayload = JSON.parse(await readFile75(join96(tempRoot, "raw.json"), "utf8"));
|
|
98204
98204
|
return Buffer.from(`${stableJson2(canonicalWhiteboardPayload(rawPayload))}
|
|
98205
98205
|
`, "utf8");
|
|
98206
98206
|
} finally {
|
|
@@ -98239,8 +98239,8 @@ var init_larkImagePolicy = __esm(() => {
|
|
|
98239
98239
|
|
|
98240
98240
|
// src/lib/larkResourceMaterialization.ts
|
|
98241
98241
|
import { createHash as createHash28 } from "node:crypto";
|
|
98242
|
-
import { mkdtemp as mkdtemp5, readFile as
|
|
98243
|
-
import { extname as extname13, join as
|
|
98242
|
+
import { mkdtemp as mkdtemp5, readFile as readFile76, readdir as readdir22, rm as rm22 } from "node:fs/promises";
|
|
98243
|
+
import { extname as extname13, join as join97 } from "node:path";
|
|
98244
98244
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
98245
98245
|
function countByKind(items, status) {
|
|
98246
98246
|
const counts2 = new Map;
|
|
@@ -98345,17 +98345,17 @@ function findBooleanField(value, name3) {
|
|
|
98345
98345
|
}
|
|
98346
98346
|
async function downloadedFile(input) {
|
|
98347
98347
|
if (input.localPath !== undefined) {
|
|
98348
|
-
const bytes = await
|
|
98348
|
+
const bytes = await readFile76(input.localPath);
|
|
98349
98349
|
return { path: input.localPath, bytes, mediaType: mediaTypeFor(input.localPath, bytes), representation: "original" };
|
|
98350
98350
|
}
|
|
98351
|
-
const tempRoot = await mkdtemp5(
|
|
98351
|
+
const tempRoot = await mkdtemp5(join97(tmpdir2(), "context-lark-resource-"));
|
|
98352
98352
|
try {
|
|
98353
98353
|
const representation = await downloadLarkMedia({ ...input, cwd: tempRoot });
|
|
98354
98354
|
const entries2 = (await readdir22(tempRoot, { withFileTypes: true })).filter((entry) => entry.isFile());
|
|
98355
98355
|
if (entries2.length !== 1)
|
|
98356
98356
|
throw new Error(`media download produced ${entries2.length} files, expected exactly one`);
|
|
98357
98357
|
const path3 = entries2[0]?.name ?? "resource.bin";
|
|
98358
|
-
const bytes = await
|
|
98358
|
+
const bytes = await readFile76(join97(tempRoot, path3));
|
|
98359
98359
|
return { path: path3, bytes, mediaType: mediaTypeFor(path3, bytes), representation };
|
|
98360
98360
|
} finally {
|
|
98361
98361
|
await rm22(tempRoot, { recursive: true, force: true });
|
|
@@ -98427,9 +98427,9 @@ async function sheetMaterialization(resource, runner2, identity2) {
|
|
|
98427
98427
|
const sheetId = resource.attributes["sheet-id"];
|
|
98428
98428
|
if (token === undefined || sheetId === undefined)
|
|
98429
98429
|
throw new Error("embedded Sheet requires token and sheet-id");
|
|
98430
|
-
const tempRoot = await mkdtemp5(
|
|
98430
|
+
const tempRoot = await mkdtemp5(join97(tmpdir2(), "context-lark-sheet-"));
|
|
98431
98431
|
try {
|
|
98432
|
-
const outputPath =
|
|
98432
|
+
const outputPath = join97(tempRoot, "sheet.json");
|
|
98433
98433
|
const stdout = await runLarkResourceCommand(runner2, [
|
|
98434
98434
|
"sheets",
|
|
98435
98435
|
"+csv-get",
|
|
@@ -98449,7 +98449,7 @@ async function sheetMaterialization(resource, runner2, identity2) {
|
|
|
98449
98449
|
if (findBooleanField(receipt2, "truncated") === true || findBooleanField(receipt2, "complete") === false) {
|
|
98450
98450
|
throw new Error("embedded Sheet read was truncated");
|
|
98451
98451
|
}
|
|
98452
|
-
const payload = JSON.parse(await
|
|
98452
|
+
const payload = JSON.parse(await readFile76(outputPath, "utf8"));
|
|
98453
98453
|
const csv = findStringField(payload, new Set(["annotated_csv", "csv", "content", "text"]));
|
|
98454
98454
|
if (csv === undefined)
|
|
98455
98455
|
throw new Error("embedded Sheet response has no CSV payload");
|
|
@@ -99564,8 +99564,8 @@ var init_sensitiveSourceLiteral = __esm(() => {
|
|
|
99564
99564
|
|
|
99565
99565
|
// src/project/larkSnapshotBundle.ts
|
|
99566
99566
|
import { createHash as createHash29 } from "node:crypto";
|
|
99567
|
-
import { lstat as lstat10, mkdir as mkdir33, readFile as
|
|
99568
|
-
import { basename as basename10, dirname as dirname39, isAbsolute as isAbsolute17, join as
|
|
99567
|
+
import { lstat as lstat10, mkdir as mkdir33, readFile as readFile77, realpath as realpath10, rename as rename8, writeFile as writeFile26 } from "node:fs/promises";
|
|
99568
|
+
import { basename as basename10, dirname as dirname39, isAbsolute as isAbsolute17, join as join98, relative as relative24, resolve as resolve30, sep as sep8 } from "node:path";
|
|
99569
99569
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
99570
99570
|
function normalizedSnapshotResourcePolicy(policy) {
|
|
99571
99571
|
return { ...policy, images: policy.images ?? "bundle", gifs: policy.gifs ?? "bundle" };
|
|
@@ -99606,13 +99606,13 @@ async function readFileEntry(root2, entry) {
|
|
|
99606
99606
|
const parts = safePath(entry.path).split("/");
|
|
99607
99607
|
let current2 = root2;
|
|
99608
99608
|
for (const [index2, part] of parts.entries()) {
|
|
99609
|
-
current2 =
|
|
99609
|
+
current2 = join98(current2, part);
|
|
99610
99610
|
const stat11 = await lstat10(current2);
|
|
99611
99611
|
if (stat11.isSymbolicLink() || (index2 === parts.length - 1 ? !stat11.isFile() : !stat11.isDirectory())) {
|
|
99612
99612
|
throw larkSnapshotError(`Snapshot entry is not a regular file: ${entry.path}`, "path-invalid");
|
|
99613
99613
|
}
|
|
99614
99614
|
}
|
|
99615
|
-
const bytes = await
|
|
99615
|
+
const bytes = await readFile77(current2);
|
|
99616
99616
|
if (digest6(bytes) !== entry.sha256)
|
|
99617
99617
|
throw larkSnapshotError(`Snapshot content changed: ${entry.path}`, "digest-mismatch");
|
|
99618
99618
|
return bytes;
|
|
@@ -99675,13 +99675,13 @@ async function writeLarkSnapshotBundle(input) {
|
|
|
99675
99675
|
};
|
|
99676
99676
|
schema2.parse(manifest);
|
|
99677
99677
|
for (const [path3, bytes] of files) {
|
|
99678
|
-
await mkdir33(dirname39(
|
|
99679
|
-
await writeFile26(
|
|
99678
|
+
await mkdir33(dirname39(join98(root2, path3)), { recursive: true });
|
|
99679
|
+
await writeFile26(join98(root2, path3), bytes, { flag: "wx", mode: 384 });
|
|
99680
99680
|
}
|
|
99681
99681
|
await validateBundle(root2, manifest);
|
|
99682
|
-
await writeFile26(
|
|
99682
|
+
await writeFile26(join98(root2, `${FILE2}.pending`), `${JSON.stringify(manifest, null, 2)}
|
|
99683
99683
|
`, { flag: "wx", mode: 384 });
|
|
99684
|
-
await rename8(
|
|
99684
|
+
await rename8(join98(root2, `${FILE2}.pending`), join98(root2, FILE2));
|
|
99685
99685
|
return manifest;
|
|
99686
99686
|
}
|
|
99687
99687
|
async function validateBundle(root2, manifest) {
|
|
@@ -99754,11 +99754,11 @@ async function validateBundle(root2, manifest) {
|
|
|
99754
99754
|
async function readLarkSnapshotBundle(directory, target) {
|
|
99755
99755
|
try {
|
|
99756
99756
|
const root2 = await assertSnapshotDirectory(directory);
|
|
99757
|
-
const path3 =
|
|
99757
|
+
const path3 = join98(root2, FILE2);
|
|
99758
99758
|
const stat11 = await lstat10(path3);
|
|
99759
99759
|
if (stat11.isSymbolicLink() || !stat11.isFile())
|
|
99760
99760
|
throw larkSnapshotError("Snapshot receipt must be a regular file", "path-invalid");
|
|
99761
|
-
const manifest = schema2.parse(JSON.parse(await
|
|
99761
|
+
const manifest = schema2.parse(JSON.parse(await readFile77(path3, "utf8")));
|
|
99762
99762
|
assertTarget(manifest.url, target);
|
|
99763
99763
|
return await validateBundle(root2, manifest);
|
|
99764
99764
|
} catch (error) {
|
|
@@ -99782,8 +99782,8 @@ async function reserveSnapshotDirectory(directory, workspaceRoot) {
|
|
|
99782
99782
|
ancestor = dirname39(ancestor);
|
|
99783
99783
|
}
|
|
99784
99784
|
}
|
|
99785
|
-
const parent =
|
|
99786
|
-
const output =
|
|
99785
|
+
const parent = join98(ancestor, ...missing);
|
|
99786
|
+
const output = join98(parent, basename10(requested));
|
|
99787
99787
|
const { findContextProjectRoot: findContextProjectRoot2 } = await Promise.resolve().then(() => (init_workspace(), exports_workspace));
|
|
99788
99788
|
const owner = findContextProjectRoot2(parent)?.projectRoot ?? workspaceRoot;
|
|
99789
99789
|
if (owner !== undefined) {
|
|
@@ -99856,8 +99856,8 @@ var init_larkSnapshotBundle = __esm(() => {
|
|
|
99856
99856
|
});
|
|
99857
99857
|
|
|
99858
99858
|
// src/project/documentCaptureLark.ts
|
|
99859
|
-
import { readdir as readdir23, readFile as
|
|
99860
|
-
import { basename as basename11, extname as extname14, join as
|
|
99859
|
+
import { readdir as readdir23, readFile as readFile78 } from "node:fs/promises";
|
|
99860
|
+
import { basename as basename11, extname as extname14, join as join99 } from "node:path";
|
|
99861
99861
|
function titleFromMarkdown2(markdown, fallbackPath) {
|
|
99862
99862
|
const heading2 = markdown.split(`
|
|
99863
99863
|
`).find((line) => /^#\s+\S/u.test(line));
|
|
@@ -99876,7 +99876,7 @@ function countLines3(markdown) {
|
|
|
99876
99876
|
}
|
|
99877
99877
|
async function fileContentMatches(path3, content3) {
|
|
99878
99878
|
try {
|
|
99879
|
-
const current2 = await
|
|
99879
|
+
const current2 = await readFile78(path3);
|
|
99880
99880
|
const expected = typeof content3 === "string" ? Buffer.from(content3, "utf8") : Buffer.from(content3);
|
|
99881
99881
|
return current2.equals(expected);
|
|
99882
99882
|
} catch {
|
|
@@ -99884,7 +99884,7 @@ async function fileContentMatches(path3, content3) {
|
|
|
99884
99884
|
}
|
|
99885
99885
|
}
|
|
99886
99886
|
function sourceManifestPath2(entry) {
|
|
99887
|
-
return entry.snapshot?.manifest ??
|
|
99887
|
+
return entry.snapshot?.manifest ?? join99(entry.materializedAt, "manifest.json");
|
|
99888
99888
|
}
|
|
99889
99889
|
function larkRuntimeError(message, detail) {
|
|
99890
99890
|
return new ContextError(ExitCode.ExternalToolError, message, {
|
|
@@ -100011,7 +100011,7 @@ function assetManifestEntry(asset, assetRoot) {
|
|
|
100011
100011
|
};
|
|
100012
100012
|
}
|
|
100013
100013
|
async function listSnapshotAssetFiles(root2, assetRoot) {
|
|
100014
|
-
const assetsRoot =
|
|
100014
|
+
const assetsRoot = join99(root2, assetRoot);
|
|
100015
100015
|
const files = [];
|
|
100016
100016
|
const visit4 = async (dir, prefix = assetRoot) => {
|
|
100017
100017
|
let entries2;
|
|
@@ -100024,7 +100024,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
|
|
|
100024
100024
|
}
|
|
100025
100025
|
for (const entry of entries2) {
|
|
100026
100026
|
const relPath = `${prefix}/${entry.name}`;
|
|
100027
|
-
const absolutePath =
|
|
100027
|
+
const absolutePath = join99(dir, entry.name);
|
|
100028
100028
|
if (entry.isDirectory()) {
|
|
100029
100029
|
await visit4(absolutePath, relPath);
|
|
100030
100030
|
continue;
|
|
@@ -100039,7 +100039,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
|
|
|
100039
100039
|
}
|
|
100040
100040
|
async function staleSnapshotAssetPaths(input) {
|
|
100041
100041
|
const existingPaths = await listSnapshotAssetFiles(input.materializedAtAbsPath, input.assetRoot);
|
|
100042
|
-
return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) =>
|
|
100042
|
+
return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) => join99(input.materializedAtAbsPath, path3));
|
|
100043
100043
|
}
|
|
100044
100044
|
function normalizeLarkError(error, sourceName) {
|
|
100045
100045
|
if (error instanceof ContextError)
|
|
@@ -100160,9 +100160,9 @@ async function runCaptureLarkPhaseUnlocked(input) {
|
|
|
100160
100160
|
locator
|
|
100161
100161
|
}];
|
|
100162
100162
|
const manifestPath = sourceManifestPath2(entry);
|
|
100163
|
-
const manifestAbsPath =
|
|
100163
|
+
const manifestAbsPath = join99(input.projectRoot, manifestPath);
|
|
100164
100164
|
const materializedAt = entry.materializedAt;
|
|
100165
|
-
const materializedAtAbsPath =
|
|
100165
|
+
const materializedAtAbsPath = join99(input.projectRoot, materializedAt);
|
|
100166
100166
|
const manifest = createDocumentSnapshotManifest({
|
|
100167
100167
|
sourceType: "lark",
|
|
100168
100168
|
sourceName: resolved.sourceName,
|
|
@@ -100196,13 +100196,13 @@ async function runCaptureLarkPhaseUnlocked(input) {
|
|
|
100196
100196
|
}));
|
|
100197
100197
|
try {
|
|
100198
100198
|
const requestedWrites = [{
|
|
100199
|
-
path:
|
|
100199
|
+
path: join99(materializedAtAbsPath, documentPath),
|
|
100200
100200
|
bytes: normalized
|
|
100201
100201
|
}];
|
|
100202
100202
|
for (const asset of assets) {
|
|
100203
100203
|
if (asset.bytes !== undefined) {
|
|
100204
100204
|
requestedWrites.push({
|
|
100205
|
-
path:
|
|
100205
|
+
path: join99(materializedAtAbsPath, asset.entry.path),
|
|
100206
100206
|
bytes: asset.bytes
|
|
100207
100207
|
});
|
|
100208
100208
|
}
|
|
@@ -100219,7 +100219,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
|
|
|
100219
100219
|
currentPaths: new Set(assets.filter((asset) => asset.bytes !== undefined).map((asset) => asset.entry.path))
|
|
100220
100220
|
});
|
|
100221
100221
|
await applyAtomicFileBatch({
|
|
100222
|
-
transactionRoot:
|
|
100222
|
+
transactionRoot: join99(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
|
|
100223
100223
|
writes,
|
|
100224
100224
|
removals
|
|
100225
100225
|
});
|
|
@@ -100369,7 +100369,7 @@ __export(exports_articleRetirement, {
|
|
|
100369
100369
|
retireArticles: () => retireArticles,
|
|
100370
100370
|
articleRetirementSchema: () => articleRetirementSchema
|
|
100371
100371
|
});
|
|
100372
|
-
import { readFile as
|
|
100372
|
+
import { readFile as readFile84, readdir as readdir24 } from "node:fs/promises";
|
|
100373
100373
|
import { posix as posix10 } from "node:path";
|
|
100374
100374
|
function rebuildInput(revision) {
|
|
100375
100375
|
return JSON.stringify({ id: `retirement-${revision.slice(7)}`, operation: "rebuild", timing: "priority", targets: [] });
|
|
@@ -100390,7 +100390,7 @@ function invalid2(reason, message, details = {}) {
|
|
|
100390
100390
|
}
|
|
100391
100391
|
async function text9(root2, path3) {
|
|
100392
100392
|
try {
|
|
100393
|
-
return await
|
|
100393
|
+
return await readFile84(await safeProjectTarget(root2, path3), "utf8");
|
|
100394
100394
|
} catch (error) {
|
|
100395
100395
|
if (error.code === "ENOENT")
|
|
100396
100396
|
return;
|
|
@@ -100652,8 +100652,8 @@ __export(exports_writeLockRecovery, {
|
|
|
100652
100652
|
recoverWriterLock: () => recoverWriterLock,
|
|
100653
100653
|
inspectWriterLock: () => inspectWriterLock
|
|
100654
100654
|
});
|
|
100655
|
-
import { lstat as lstat11, mkdir as mkdir35, readFile as
|
|
100656
|
-
import { join as
|
|
100655
|
+
import { lstat as lstat11, mkdir as mkdir35, readFile as readFile85, readdir as readdir25, rename as rename9, rmdir as rmdir2 } from "node:fs/promises";
|
|
100656
|
+
import { join as join104 } from "node:path";
|
|
100657
100657
|
import { createHash as createHash31, randomUUID as randomUUID7 } from "node:crypto";
|
|
100658
100658
|
async function inspectWriterLock(root2) {
|
|
100659
100659
|
const path3 = await safeProjectTarget(root2, lockRelative);
|
|
@@ -100668,7 +100668,7 @@ async function inspectWriterLock(root2) {
|
|
|
100668
100668
|
if (!stat11.isDirectory() || stat11.isSymbolicLink())
|
|
100669
100669
|
throw new Error("Writer lock must be a real directory.");
|
|
100670
100670
|
const ownerPath = await safeProjectTarget(root2, `${lockRelative}/owner.json`);
|
|
100671
|
-
const bytes = await
|
|
100671
|
+
const bytes = await readFile85(ownerPath, "utf8");
|
|
100672
100672
|
const owner = JSON.parse(bytes);
|
|
100673
100673
|
if (owner.protocol !== "context.project-write-lock.v1" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
|
|
100674
100674
|
throw new Error("Writer lock owner is invalid; preserve the lock for diagnosis.");
|
|
@@ -100700,8 +100700,8 @@ async function recoverWriterLock(input) {
|
|
|
100700
100700
|
};
|
|
100701
100701
|
if (input.plan_digest !== before.digest)
|
|
100702
100702
|
throw new Error("Writer lock changed; preview recovery again.");
|
|
100703
|
-
const path3 =
|
|
100704
|
-
const guard =
|
|
100703
|
+
const path3 = join104(input.projectRoot, lockRelative);
|
|
100704
|
+
const guard = join104(path3, ".recovery");
|
|
100705
100705
|
await mkdir35(guard);
|
|
100706
100706
|
let archived = false;
|
|
100707
100707
|
try {
|
|
@@ -100712,7 +100712,7 @@ async function recoverWriterLock(input) {
|
|
|
100712
100712
|
if (names.some((name3) => name3 !== "owner.json" && name3 !== ".recovery"))
|
|
100713
100713
|
throw new Error("Unexpected lock contents; preserve for diagnosis.");
|
|
100714
100714
|
const archive = `.tmp/context-runtime/locks/recovered-write-${randomUUID7()}.lock`;
|
|
100715
|
-
await rename9(path3,
|
|
100715
|
+
await rename9(path3, join104(input.projectRoot, archive));
|
|
100716
100716
|
archived = true;
|
|
100717
100717
|
return { action: "writer-lock-recovered", archived_lock: archive, next: "context task recover --format json" };
|
|
100718
100718
|
} finally {
|
|
@@ -100736,12 +100736,12 @@ __export(exports_taskRecovery, {
|
|
|
100736
100736
|
RECOVERY_COMMAND: () => RECOVERY_COMMAND
|
|
100737
100737
|
});
|
|
100738
100738
|
import { existsSync as existsSync26 } from "node:fs";
|
|
100739
|
-
import { dirname as dirname42, join as
|
|
100740
|
-
import { lstat as lstat12, readdir as readdir26, readFile as
|
|
100739
|
+
import { dirname as dirname42, join as join105 } from "node:path";
|
|
100740
|
+
import { lstat as lstat12, readdir as readdir26, readFile as readFile86 } from "node:fs/promises";
|
|
100741
100741
|
async function recoveryText(root2, path3) {
|
|
100742
100742
|
const target = await safeProjectTarget(root2, path3);
|
|
100743
100743
|
try {
|
|
100744
|
-
return await
|
|
100744
|
+
return await readFile86(target, "utf8");
|
|
100745
100745
|
} catch (error) {
|
|
100746
100746
|
if (error.code === "ENOENT")
|
|
100747
100747
|
return;
|
|
@@ -100754,7 +100754,7 @@ async function recoveryJournals(root2) {
|
|
|
100754
100754
|
await safeProjectTarget(root2, path3);
|
|
100755
100755
|
let stat11;
|
|
100756
100756
|
try {
|
|
100757
|
-
stat11 = await lstat12(
|
|
100757
|
+
stat11 = await lstat12(join105(root2, path3));
|
|
100758
100758
|
} catch (error) {
|
|
100759
100759
|
if (error.code === "ENOENT")
|
|
100760
100760
|
return;
|
|
@@ -100765,7 +100765,7 @@ async function recoveryJournals(root2) {
|
|
|
100765
100765
|
if (stat11.isDirectory()) {
|
|
100766
100766
|
if (depth > 3)
|
|
100767
100767
|
throw new TypeError("Unexpected transaction directory depth; preserve it for diagnosis.");
|
|
100768
|
-
for (const name3 of (await readdir26(
|
|
100768
|
+
for (const name3 of (await readdir26(join105(root2, path3))).sort())
|
|
100769
100769
|
await visit4(`${path3}/${name3}`, depth + 1);
|
|
100770
100770
|
} else if (stat11.isFile())
|
|
100771
100771
|
entries2.push({ path: path3, digest: indexerProtocolDigest(await recoveryText(root2, path3)) });
|
|
@@ -100777,8 +100777,8 @@ function recoveryResources() {
|
|
|
100777
100777
|
try {
|
|
100778
100778
|
const root2 = dirname42(contextWorkflowProviderPath());
|
|
100779
100779
|
const resources = {
|
|
100780
|
-
skill:
|
|
100781
|
-
issue_template:
|
|
100780
|
+
skill: join105(root2, "skills/recover-workspace/SKILL.md"),
|
|
100781
|
+
issue_template: join105(root2, "resources/templates/recovery-issue.md")
|
|
100782
100782
|
};
|
|
100783
100783
|
if (!Object.values(resources).every((path3) => existsSync26(path3)))
|
|
100784
100784
|
throw new Error("Recovery resources are absent from this Provider.");
|
|
@@ -100863,8 +100863,8 @@ var exports_taskLocalSourceAdjustment = {};
|
|
|
100863
100863
|
__export(exports_taskLocalSourceAdjustment, {
|
|
100864
100864
|
adjustLocalRevisionSources: () => adjustLocalRevisionSources
|
|
100865
100865
|
});
|
|
100866
|
-
import { readFile as
|
|
100867
|
-
import { join as
|
|
100866
|
+
import { readFile as readFile87 } from "node:fs/promises";
|
|
100867
|
+
import { join as join106 } from "node:path";
|
|
100868
100868
|
async function adjustLocalRevisionSources(root2, input) {
|
|
100869
100869
|
const { readMaintenance: readMaintenance2 } = await Promise.resolve().then(() => (init_maintenanceStorage(), exports_maintenanceStorage));
|
|
100870
100870
|
if ((await readMaintenance2(root2)).active && await readProductionStage(root2))
|
|
@@ -100907,7 +100907,7 @@ async function adjustLocalRevisionSources(root2, input) {
|
|
|
100907
100907
|
if (input.refresh && (!current2.refresh_sources || indexerProtocolDigest([...current2.refresh_sources].sort()) !== indexerProtocolDigest([...selected].sort()))) {
|
|
100908
100908
|
throw new TypeError("No matching acquisition adjustment exists. Run task adjust without refresh first.");
|
|
100909
100909
|
}
|
|
100910
|
-
const raw = await
|
|
100910
|
+
const raw = await readFile87(join106(root2, await revisionStoragePath(root2)), "utf8");
|
|
100911
100911
|
let next2;
|
|
100912
100912
|
const discardIds = new Set;
|
|
100913
100913
|
if (!input.refresh) {
|
|
@@ -101010,7 +101010,7 @@ ${input.instruction}` : revision.instruction
|
|
|
101010
101010
|
content: content3
|
|
101011
101011
|
}];
|
|
101012
101012
|
if (discardIds.size > 0) {
|
|
101013
|
-
const ledger = await
|
|
101013
|
+
const ledger = await readFile87(join106(root2, CANDIDATE_LEDGER_FILE), "utf8").catch((error) => {
|
|
101014
101014
|
if (error.code === "ENOENT")
|
|
101015
101015
|
return;
|
|
101016
101016
|
throw error;
|
|
@@ -101135,7 +101135,7 @@ var init_taskSourceAdjustment = __esm(() => {
|
|
|
101135
101135
|
});
|
|
101136
101136
|
|
|
101137
101137
|
// src/project/managedDocumentImport.ts
|
|
101138
|
-
import { readFile as
|
|
101138
|
+
import { readFile as readFile96 } from "node:fs/promises";
|
|
101139
101139
|
async function importManagedDocument(projectRoot, value) {
|
|
101140
101140
|
const input = inputSchema.parse(value);
|
|
101141
101141
|
if (input.type !== "sessions" && input.changes !== undefined)
|
|
@@ -101144,7 +101144,7 @@ async function importManagedDocument(projectRoot, value) {
|
|
|
101144
101144
|
const path3 = await assertManagedDocumentPath(projectRoot, input.type, input.name);
|
|
101145
101145
|
let previous3;
|
|
101146
101146
|
try {
|
|
101147
|
-
previous3 = await
|
|
101147
|
+
previous3 = await readFile96(path3, "utf8");
|
|
101148
101148
|
} catch (error) {
|
|
101149
101149
|
if (!(error && typeof error === "object" && ("code" in error) && error.code === "ENOENT"))
|
|
101150
101150
|
throw error;
|
|
@@ -101191,7 +101191,7 @@ var init_managedDocumentImport = __esm(() => {
|
|
|
101191
101191
|
});
|
|
101192
101192
|
|
|
101193
101193
|
// src/project/larkDocumentImport.ts
|
|
101194
|
-
import { readFile as
|
|
101194
|
+
import { readFile as readFile97 } from "node:fs/promises";
|
|
101195
101195
|
import { resolve as resolve40 } from "node:path";
|
|
101196
101196
|
async function importLarkDocument(projectRoot, value) {
|
|
101197
101197
|
const input = schema4.parse(value);
|
|
@@ -101208,7 +101208,7 @@ async function importLarkDocument(projectRoot, value) {
|
|
|
101208
101208
|
const responsePages = [];
|
|
101209
101209
|
for (const path3 of input.response_files) {
|
|
101210
101210
|
assertActionInputWorkspace(projectRoot, path3);
|
|
101211
|
-
responsePages.push(await
|
|
101211
|
+
responsePages.push(await readFile97(resolve40(projectRoot, path3), "utf8"));
|
|
101212
101212
|
}
|
|
101213
101213
|
const mediaFiles = {};
|
|
101214
101214
|
for (const [token, path3] of Object.entries(input.media_files ?? {})) {
|
|
@@ -101275,11 +101275,11 @@ var exports_managedDocumentRename = {};
|
|
|
101275
101275
|
__export(exports_managedDocumentRename, {
|
|
101276
101276
|
renameManagedDocument: () => renameManagedDocument
|
|
101277
101277
|
});
|
|
101278
|
-
import { readFile as
|
|
101279
|
-
import { join as
|
|
101278
|
+
import { readFile as readFile98 } from "node:fs/promises";
|
|
101279
|
+
import { join as join118, posix as posix11 } from "node:path";
|
|
101280
101280
|
async function optionalText2(path3) {
|
|
101281
101281
|
try {
|
|
101282
|
-
return await
|
|
101282
|
+
return await readFile98(path3, "utf8");
|
|
101283
101283
|
} catch (error) {
|
|
101284
101284
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
101285
101285
|
return;
|
|
@@ -101363,7 +101363,7 @@ async function renameManagedDocument(input) {
|
|
|
101363
101363
|
if (path3.split("/").includes("..") || path3.startsWith("/"))
|
|
101364
101364
|
throw new TypeError("Source references contain an unsafe knowledge path; repair it before renaming.");
|
|
101365
101365
|
await safeProjectTarget(input.projectRoot, path3);
|
|
101366
|
-
const before = await optionalText2(
|
|
101366
|
+
const before = await optionalText2(join118(input.projectRoot, path3));
|
|
101367
101367
|
if (before === undefined)
|
|
101368
101368
|
continue;
|
|
101369
101369
|
let after;
|
|
@@ -101452,7 +101452,7 @@ var init_managedDocumentRename = __esm(() => {
|
|
|
101452
101452
|
|
|
101453
101453
|
// src/cli.ts
|
|
101454
101454
|
import { existsSync as existsSync36, realpathSync as realpathSync3 } from "node:fs";
|
|
101455
|
-
import { dirname as dirname50, join as
|
|
101455
|
+
import { dirname as dirname50, join as join124 } from "node:path";
|
|
101456
101456
|
import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL3 } from "node:url";
|
|
101457
101457
|
|
|
101458
101458
|
// ../../node_modules/.bun/commander@11.1.0/node_modules/commander/esm.mjs
|
|
@@ -101507,7 +101507,7 @@ init_dist();
|
|
|
101507
101507
|
init_cliFeedback();
|
|
101508
101508
|
init_errors3();
|
|
101509
101509
|
init_exitCode();
|
|
101510
|
-
import { join as
|
|
101510
|
+
import { join as join93 } from "node:path";
|
|
101511
101511
|
|
|
101512
101512
|
// src/project/documentCaptureAvailability.ts
|
|
101513
101513
|
init_atomicFileBatch();
|
|
@@ -102588,9 +102588,9 @@ init_workflowTypes();
|
|
|
102588
102588
|
// src/project/reviewHtml.ts
|
|
102589
102589
|
init_candidateLedger();
|
|
102590
102590
|
init_reviewShared();
|
|
102591
|
-
import { mkdir as mkdir31, readFile as
|
|
102591
|
+
import { mkdir as mkdir31, readFile as readFile72, writeFile as writeFile25 } from "node:fs/promises";
|
|
102592
102592
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
102593
|
-
import { dirname as dirname38, isAbsolute as isAbsolute15, join as
|
|
102593
|
+
import { dirname as dirname38, isAbsolute as isAbsolute15, join as join91, resolve as resolve28 } from "node:path";
|
|
102594
102594
|
|
|
102595
102595
|
// src/project/reviewSiteModel.ts
|
|
102596
102596
|
init_siteTheme2();
|
|
@@ -105792,6 +105792,7 @@ async function collectReviewSiteModel(root2, candidates) {
|
|
|
105792
105792
|
html: old === undefined ? renderReviewMarkdown(next.replace(/^# [^\n]+\n*/u, "")) : reviewBodyDiff(body(old).replace(/^# [^\n]+\n*/u, ""), next.replace(/^# [^\n]+\n*/u, "")),
|
|
105793
105793
|
sources: [...r.source_refs, ...r.indexer_candidate.sections.flatMap((s) => s.references.map((ref2) => JSON.stringify(ref2)))]
|
|
105794
105794
|
};
|
|
105795
|
+
page.readVersion = hash3(JSON.stringify([page.id, page.title, page.path, page.html, page.sources]));
|
|
105795
105796
|
if (found)
|
|
105796
105797
|
pages.splice(pages.indexOf(found), 1, page);
|
|
105797
105798
|
else
|
|
@@ -105832,146 +105833,394 @@ async function collectReviewSiteModel(root2, candidates) {
|
|
|
105832
105833
|
const siteTitle = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site?.title ? [p.site.title] : [])[0];
|
|
105833
105834
|
const sitePackages = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site ? [p.site] : []) ?? [];
|
|
105834
105835
|
const themeCss = siteThemeVariables(await resolveSiteTheme(root2, sitePackages.length === 1 ? sitePackages[0]?.theme : undefined));
|
|
105835
|
-
return { themeCss, title: siteTitle ?? pkg.name ?? "Knowledge
|
|
105836
|
+
return { storageScope: hash3(root2), themeCss, title: siteTitle ?? pkg.name ?? "Knowledge reading report", baselineHash: await reviewSiteBaselineHash(root2, candidates.map((c) => c.record.approved_revision?.previous_path ?? c.record.path)), nodes, pages, navigationBaseline };
|
|
105836
105837
|
}
|
|
105837
105838
|
var reviewHtmlJson = (value) => JSON.stringify(value).replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
|
|
105838
105839
|
|
|
105839
|
-
// src/project/
|
|
105840
|
-
|
|
105841
|
-
|
|
105842
|
-
|
|
105843
|
-
|
|
105844
|
-
|
|
105845
|
-
|
|
105846
|
-
|
|
105847
|
-
|
|
105840
|
+
// src/project/reviewReportScope.ts
|
|
105841
|
+
init_atomicWrite();
|
|
105842
|
+
init_errors3();
|
|
105843
|
+
init_cliFeedback();
|
|
105844
|
+
init_exitCode();
|
|
105845
|
+
init_reviewShared();
|
|
105846
|
+
import { readFile as readFile71 } from "node:fs/promises";
|
|
105847
|
+
import { join as join90 } from "node:path";
|
|
105848
|
+
var scopePath = (root2, scope2) => join90(root2, ".tmp/context-runtime/review", `${scope2}.scope.json`);
|
|
105849
|
+
async function saveReviewReportScope(root2, scope2, candidates, baselineHash) {
|
|
105850
|
+
const ids = candidates.map((candidate) => candidate.record.candidate_id).sort();
|
|
105851
|
+
const value = {
|
|
105852
|
+
baseline_hash: baselineHash,
|
|
105853
|
+
scope: {
|
|
105854
|
+
kind: scope2 === "all" ? "all" : "collection",
|
|
105855
|
+
...scope2 === "all" ? {} : { collection: scope2 },
|
|
105856
|
+
count: ids.length,
|
|
105857
|
+
ids_sha256: candidateIdsHash(ids),
|
|
105858
|
+
candidates_sha256: candidateSetHash(candidates.map((candidate) => candidate.record)),
|
|
105859
|
+
visible_candidate_ids: ids
|
|
105848
105860
|
}
|
|
105849
|
-
|
|
105850
|
-
|
|
105851
|
-
|
|
105852
|
-
|
|
105853
|
-
|
|
105854
|
-
|
|
105855
|
-
|
|
105856
|
-
|
|
105857
|
-
|
|
105858
|
-
|
|
105861
|
+
};
|
|
105862
|
+
await atomicWriteFile(scopePath(root2, scope2), JSON.stringify(value, null, 2) + `
|
|
105863
|
+
`);
|
|
105864
|
+
}
|
|
105865
|
+
async function readConfirmedReviewScope(root2, scope2) {
|
|
105866
|
+
try {
|
|
105867
|
+
const value = JSON.parse(await readFile71(scopePath(root2, scope2), "utf8"));
|
|
105868
|
+
if (!value || typeof value !== "object" || !("scope" in value) || !("baseline_hash" in value))
|
|
105869
|
+
throw new Error("Missing report scope");
|
|
105870
|
+
const parsed = value;
|
|
105871
|
+
if (typeof parsed.baseline_hash !== "string" || !/^[a-f0-9]{64}$/u.test(parsed.baseline_hash) || !parsed.scope || !Number.isInteger(parsed.scope.count) || parsed.scope.count < 0 || typeof parsed.scope.ids_sha256 !== "string" || typeof parsed.scope.candidates_sha256 !== "string" || !Array.isArray(parsed.scope.visible_candidate_ids) || parsed.scope.visible_candidate_ids.some((id3) => typeof id3 !== "string") || (scope2 === "all" ? parsed.scope.kind !== "all" : parsed.scope.kind !== "collection" || parsed.scope.collection !== scope2)) {
|
|
105872
|
+
throw new Error("Invalid report scope");
|
|
105859
105873
|
}
|
|
105860
|
-
|
|
105861
|
-
|
|
105862
|
-
|
|
105874
|
+
return parsed;
|
|
105875
|
+
} catch (error) {
|
|
105876
|
+
throw new ContextError(ExitCode.UserError, "A current report scope is required for user confirmation", {
|
|
105877
|
+
category: ErrorCategory.UserInputInvalid,
|
|
105878
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
105879
|
+
next: `context review html ${scope2 === "all" ? "--all" : scope2} --format json`
|
|
105880
|
+
});
|
|
105863
105881
|
}
|
|
105864
|
-
|
|
105865
|
-
|
|
105866
|
-
|
|
105867
|
-
|
|
105868
|
-
|
|
105869
|
-
|
|
105870
|
-
|
|
105871
|
-
|
|
105872
|
-
|
|
105873
|
-
|
|
105874
|
-
|
|
105875
|
-
|
|
105876
|
-
|
|
105877
|
-
|
|
105878
|
-
|
|
105879
|
-
|
|
105880
|
-
|
|
105881
|
-
|
|
105882
|
-
|
|
105883
|
-
|
|
105884
|
-
|
|
105885
|
-
|
|
105886
|
-
|
|
105882
|
+
}
|
|
105883
|
+
|
|
105884
|
+
// src/project/reviewSiteClient.ts
|
|
105885
|
+
var REVIEW_SITE_CLIENT = `
|
|
105886
|
+
const $ = id => document.getElementById(id);
|
|
105887
|
+
const escape = value => String(value).replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]));
|
|
105888
|
+
const words = {
|
|
105889
|
+
read: ['已读', 'Read'], revisions: ['修订', 'Revisions'], total: ['总', 'Total'], unit: ['篇', ''],
|
|
105890
|
+
copy: ['复制修订意见', 'Copy revision notes'], home: ['本次内容', 'This update'],
|
|
105891
|
+
homeHint: ['阅读文章,可在正文下方填写修订意见。完成后返回会话确认或反馈。', 'Read the articles and add revision notes below. Return to the conversation to confirm or share feedback.'],
|
|
105892
|
+
navigate: ['目录与文章', 'Directories and articles'], files: ['预期工作区变化', 'Expected workspace changes'],
|
|
105893
|
+
note: ['输入本篇修订意见……', 'Enter revision notes for this article…'], clear: ['清空本条修订', 'Clear this note'],
|
|
105894
|
+
revisionTitle: ['修订意见', 'Revision notes'], revisionHint: ['点击文章标题返回正文,继续编辑修订意见。', 'Select an article title to return to the article and edit its notes.'],
|
|
105895
|
+
empty: ['暂无修订意见', 'No revision notes yet'], emptyHint: ['可在文章正文下方填写修订意见。', 'Add revision notes below an article.'],
|
|
105896
|
+
emptyCopy: ['暂无修订意见,可返回会话确认。', 'No revision notes. You can confirm in the conversation.'],
|
|
105897
|
+
copied: ['已复制修订意见,可粘贴到会话中。', 'Revision notes copied. Paste them into the conversation.'],
|
|
105898
|
+
manualCopy: ['请手动复制修订意见', 'Copy revision notes manually'], manualHint: ['自动复制不可用,请复制下方文本并回复到会话中。', 'Automatic copy is unavailable. Copy the text below and reply in the conversation.'],
|
|
105899
|
+
close: ['关闭', 'Close'], baseline: ['无 Git 导航基线,目录沿用当前工作区;未推断历史目录变化。', 'No Git navigation baseline. The current workspace navigation is shown.'],
|
|
105900
|
+
approvedContext: ['既有正文,仅供对照。', 'Existing content, shown for reference.'], unchanged: ['本次未变更内容略。', 'Unchanged content omitted.'],
|
|
105901
|
+
previous: ['查看旧文本', 'Previous text'], removed: ['移除或替换的旧内容', 'Removed or replaced content'],
|
|
105902
|
+
removedNav: ['移除的目录条目', 'Removed navigation entries'], unplaced: ['待落位文章', 'Unplaced articles'],
|
|
105903
|
+
sources: ['来源引用', 'Sources'], feedbackTag: ['修订', 'Notes'],
|
|
105904
|
+
};
|
|
105905
|
+
let language = 'zh';
|
|
105906
|
+
const t = key => words[key]?.[language === 'zh' ? 0 : 1] || key;
|
|
105907
|
+
const candidates = DATA.pages.filter(page => page.candidate_id);
|
|
105908
|
+
const pagesById = new Map(DATA.pages.map(page => [page.id, page]));
|
|
105909
|
+
const read = new Set();
|
|
105910
|
+
const notes = new Map();
|
|
105911
|
+
const expanded = new Set();
|
|
105912
|
+
let selected = null;
|
|
105913
|
+
let active = null;
|
|
105914
|
+
let view = 'home';
|
|
105915
|
+
let toastTimer;
|
|
105916
|
+
let copyTimer;
|
|
105917
|
+
const storageKey = 'context-reading-v1:' + DATA.storageScope;
|
|
105918
|
+
|
|
105919
|
+
function restore() {
|
|
105920
|
+
for (const page of candidates) {
|
|
105921
|
+
if (page.revisionInstruction?.trim()) notes.set(page.id, page.revisionInstruction);
|
|
105887
105922
|
}
|
|
105888
|
-
|
|
105889
|
-
const
|
|
105890
|
-
|
|
105891
|
-
|
|
105892
|
-
const
|
|
105893
|
-
|
|
105894
|
-
|
|
105895
|
-
|
|
105896
|
-
|
|
105897
|
-
|
|
105898
|
-
|
|
105899
|
-
throw new Error("Review feedback is damaged or incomplete; copy the full code again");
|
|
105900
|
-
let bits = 0, carry = 0;
|
|
105901
|
-
const bytes = [];
|
|
105902
|
-
for (const char of match[1]) {
|
|
105903
|
-
carry = carry << 6 | alphabet.indexOf(char);
|
|
105904
|
-
bits += 6;
|
|
105905
|
-
if (bits >= 8) {
|
|
105906
|
-
bits -= 8;
|
|
105907
|
-
bytes.push(carry >>> bits & 255);
|
|
105923
|
+
try {
|
|
105924
|
+
const saved = JSON.parse(localStorage.getItem(storageKey) || '{}');
|
|
105925
|
+
language = saved.language === 'en' ? 'en' : 'zh';
|
|
105926
|
+
document.body.classList.toggle('dark', saved.dark === true);
|
|
105927
|
+
for (const page of candidates) {
|
|
105928
|
+
const entry = saved.pages?.[page.id];
|
|
105929
|
+
if (entry?.version !== page.readVersion) continue;
|
|
105930
|
+
if (entry.read === true) read.add(page.id);
|
|
105931
|
+
if (typeof entry.note === 'string') {
|
|
105932
|
+
if (entry.note.trim()) notes.set(page.id, entry.note);
|
|
105933
|
+
else notes.delete(page.id);
|
|
105908
105934
|
}
|
|
105909
105935
|
}
|
|
105910
|
-
|
|
105911
|
-
const value = validate({ ...headerValue, repairs: lines.map((line) => {
|
|
105912
|
-
const row = JSON.parse(line);
|
|
105913
|
-
if (!Array.isArray(row) || row.length !== 2)
|
|
105914
|
-
throw new Error("Invalid revision instruction line");
|
|
105915
|
-
return { index: row[0], instruction: row[1] };
|
|
105916
|
-
}) });
|
|
105917
|
-
if (encode(value) !== code3)
|
|
105918
|
-
throw new Error("Noncanonical review feedback encoding");
|
|
105919
|
-
return value;
|
|
105920
|
-
}
|
|
105921
|
-
return { encode, decode: decode2 };
|
|
105936
|
+
} catch { /* The report remains usable when local storage is unavailable. */ }
|
|
105922
105937
|
}
|
|
105923
105938
|
|
|
105924
|
-
|
|
105925
|
-
|
|
105926
|
-
|
|
105927
|
-
|
|
105928
|
-
|
|
105929
|
-
|
|
105930
|
-
|
|
105931
|
-
|
|
105932
|
-
|
|
105933
|
-
|
|
105934
|
-
|
|
105935
|
-
|
|
105936
|
-
function
|
|
105937
|
-
function
|
|
105938
|
-
|
|
105939
|
-
|
|
105940
|
-
|
|
105941
|
-
|
|
105942
|
-
function
|
|
105943
|
-
|
|
105944
|
-
|
|
105945
|
-
|
|
105946
|
-
|
|
105947
|
-
function
|
|
105948
|
-
|
|
105949
|
-
|
|
105950
|
-
|
|
105951
|
-
|
|
105952
|
-
function
|
|
105953
|
-
|
|
105954
|
-
|
|
105955
|
-
|
|
105956
|
-
|
|
105957
|
-
|
|
105958
|
-
|
|
105959
|
-
|
|
105960
|
-
|
|
105961
|
-
const
|
|
105962
|
-
|
|
105963
|
-
|
|
105964
|
-
|
|
105965
|
-
|
|
105966
|
-
|
|
105967
|
-
|
|
105968
|
-
|
|
105939
|
+
function persist() {
|
|
105940
|
+
try {
|
|
105941
|
+
localStorage.setItem(storageKey, JSON.stringify({
|
|
105942
|
+
language, dark: document.body.classList.contains('dark'),
|
|
105943
|
+
pages: Object.fromEntries(candidates.map(page => [page.id, {
|
|
105944
|
+
version: page.readVersion, read: read.has(page.id), note: notes.get(page.id) || '',
|
|
105945
|
+
}])),
|
|
105946
|
+
}));
|
|
105947
|
+
} catch { /* Reading and copying still work without storage access. */ }
|
|
105948
|
+
}
|
|
105949
|
+
|
|
105950
|
+
function revisedPages() { return candidates.filter(page => (notes.get(page.id) || '').trim()); }
|
|
105951
|
+
function countText(label, count) { return \`\${label} <b>\${count}</b>\${language === 'zh' ? ' 篇' : ''}\`; }
|
|
105952
|
+
function badges(page) {
|
|
105953
|
+
if (!page?.candidate_id) return '';
|
|
105954
|
+
return (read.has(page.id) ? '<span class="badge read">READ</span>' : '')
|
|
105955
|
+
+ ((notes.get(page.id) || '').trim() ? \`<span class="badge has-feedback">\${t('feedbackTag')}</span>\` : '');
|
|
105956
|
+
}
|
|
105957
|
+
function badge(change) {
|
|
105958
|
+
if (!change || change === 'unchanged') return '';
|
|
105959
|
+
const label = { new: 'New', modify: 'Modify', removed: 'Remove' }[change] || change;
|
|
105960
|
+
return \`<span class="badge \${escape(change)}">\${escape(label)}</span>\`;
|
|
105961
|
+
}
|
|
105962
|
+
function descendants(key, seen = new Set()) {
|
|
105963
|
+
if (seen.has(key)) return [];
|
|
105964
|
+
seen.add(key);
|
|
105965
|
+
return DATA.nodes.filter(node => node.parent === key).flatMap(node => [node, ...descendants(node.key, seen)]);
|
|
105966
|
+
}
|
|
105967
|
+
function nodeChange(node) {
|
|
105968
|
+
if (node.removed) return 'removed';
|
|
105969
|
+
const nested = descendants(node.key);
|
|
105970
|
+
const changes = [node.change, ...nested.map(child => child.change),
|
|
105971
|
+
...[node, ...nested].map(child => pagesById.get(child.page)?.change)];
|
|
105972
|
+
return nested.some(child => child.removed) || changes.includes('modify') ? 'modify' : changes.includes('new') ? 'new' : 'unchanged';
|
|
105973
|
+
}
|
|
105974
|
+
function nodeTitle(node) { return node.key === 'review-unplaced' ? t('unplaced') : node.title; }
|
|
105975
|
+
function nodeLabel(node) {
|
|
105976
|
+
const title = escape(nodeTitle(node));
|
|
105977
|
+
return (node.removed ? \`<del>\${title}</del>\` : title)
|
|
105978
|
+
+ (node.oldTitle ? \`<small class="old-title"> ← \${escape(node.oldTitle)}</small>\` : '');
|
|
105979
|
+
}
|
|
105980
|
+
function rootFor(pageId) {
|
|
105981
|
+
let node = DATA.nodes.find(item => item.page === pageId);
|
|
105982
|
+
const seen = new Set();
|
|
105983
|
+
while (node?.parent && !seen.has(node.key)) { seen.add(node.key); node = DATA.nodes.find(item => item.key === node.parent); }
|
|
105984
|
+
return node?.key || null;
|
|
105985
|
+
}
|
|
105986
|
+
|
|
105987
|
+
function updateCounts() {
|
|
105988
|
+
const revised = revisedPages().length;
|
|
105989
|
+
$('counts').innerHTML = \`<span>\${countText(t('read'), read.size)}</span><span class="separator">/</span>\`
|
|
105990
|
+
+ \`<button id="revisions-link" class="\${revised ? 'has-notes' : ''}" aria-pressed="\${view === 'revisions'}">\${countText(t('revisions'), revised)}</button>\`
|
|
105991
|
+
+ \`<span class="separator">/</span><span>\${countText(t('total'), candidates.length)}</span>\`;
|
|
105992
|
+
$('revisions-link').onclick = () => { view = 'revisions'; selected = null; location.hash = 'revisions'; render(); window.scrollTo(0, 0); };
|
|
105993
|
+
$('read-fill').style.width = (candidates.length ? read.size / candidates.length * 100 : 0) + '%';
|
|
105994
|
+
const track = document.querySelector('.read-track');
|
|
105995
|
+
track.setAttribute('aria-valuenow', String(read.size));
|
|
105996
|
+
track.setAttribute('aria-valuemax', String(candidates.length));
|
|
105997
|
+
}
|
|
105998
|
+
|
|
105999
|
+
function renderNav() {
|
|
106000
|
+
const roots = DATA.nodes.filter(node => !node.parent).sort((a, b) => a.order - b.order);
|
|
106001
|
+
$('top').innerHTML = roots.map(node => \`<button data-root="\${escape(node.key)}" class="\${nodeChange(node)}\${active === node.key ? ' active' : ''}">\${nodeLabel(node)}</button>\`).join('');
|
|
106002
|
+
function nodeButton(node, depth) {
|
|
106003
|
+
const page = pagesById.get(node.page);
|
|
106004
|
+
const isDirectory = DATA.nodes.some(child => child.parent === node.key);
|
|
106005
|
+
return \`<button data-node="\${escape(node.key)}" title="\${escape(nodeTitle(node))}" class="node \${isDirectory ? 'directory ' : ''}\${nodeChange(node)}\${selected === node.page ? ' selected' : ''}" style="padding-left:\${26 + 14 * depth}px">\`
|
|
106006
|
+
+ \`<span class="node-content"><span class="node-title">\${nodeLabel(node)}</span>\`
|
|
106007
|
+
+ \`<span class="node-status">\${badges(page)}</span>\${isDirectory ? '<span class="caret">›</span>' : ''}</span></button>\`;
|
|
106008
|
+
}
|
|
106009
|
+
let content = '';
|
|
106010
|
+
function walk(key, depth, seen = new Set()) {
|
|
106011
|
+
if (seen.has(key)) return;
|
|
106012
|
+
seen.add(key);
|
|
106013
|
+
for (const node of DATA.nodes.filter(item => item.parent === key).sort((a, b) => a.order - b.order)) {
|
|
106014
|
+
content += nodeButton(node, depth);
|
|
106015
|
+
if (expanded.has(node.key) || node.page === selected || descendants(node.key).some(child => child.page === selected)) walk(node.key, depth + 1, seen);
|
|
106016
|
+
}
|
|
106017
|
+
}
|
|
106018
|
+
if (active) walk(active, 0);
|
|
106019
|
+
else content = roots.map(node => nodeButton(node, 0)).join('');
|
|
106020
|
+
$('tree').innerHTML = content;
|
|
106021
|
+
}
|
|
106022
|
+
|
|
106023
|
+
function workspaceTree() {
|
|
106024
|
+
const root = {};
|
|
106025
|
+
for (const page of candidates) {
|
|
106026
|
+
let node = root;
|
|
106027
|
+
for (const part of ('knowledge/' + page.path).split('/')) node = node[part] ??= {};
|
|
106028
|
+
node.$page = page;
|
|
106029
|
+
}
|
|
106030
|
+
function lines(node, depth = 0) {
|
|
106031
|
+
return Object.entries(node).filter(([key]) => key !== '$page').sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => {
|
|
106032
|
+
const label = child.$page ? \`<button data-page="\${escape(child.$page.id)}">\${escape(key)}</button>\${badge(child.$page.change)}\${badges(child.$page)}\` : escape(key) + '/';
|
|
106033
|
+
return \`<div style="padding-left:\${depth * 18}px">\${label}</div>\${lines(child, depth + 1)}\`;
|
|
106034
|
+
}).join('');
|
|
106035
|
+
}
|
|
106036
|
+
return \`<div class="workspace-tree">\${lines(root)}</div>\`;
|
|
106037
|
+
}
|
|
106038
|
+
|
|
106039
|
+
function renderHome() {
|
|
106040
|
+
$('article').innerHTML = \`<h1>\${t('home')}</h1><p class="stats">\${t('homeHint')}</p>\`
|
|
106041
|
+
+ (DATA.navigationBaseline === 'current' ? \`<p class="note">\${t('baseline')}</p>\` : '')
|
|
106042
|
+
+ \`<h2>\${t('navigate')}</h2>\`
|
|
106043
|
+
+ DATA.nodes.filter(node => !node.parent).map(node => {
|
|
106044
|
+
const ids = new Set([node, ...descendants(node.key)].map(item => item.page));
|
|
106045
|
+
const pages = candidates.filter(page => ids.has(page.id));
|
|
106046
|
+
if (!pages.length) return '';
|
|
106047
|
+
return \`<section class="home-group"><h3>\${nodeLabel(node)}\${badge(nodeChange(node))}</h3><div class="cards">\`
|
|
106048
|
+
+ pages.map(page => \`<button data-page="\${escape(page.id)}">\${escape(page.title)}\${badge(page.change)}<span data-status-for="\${escape(page.id)}">\${badges(page)}</span></button>\`).join('') + '</div></section>';
|
|
106049
|
+
}).join('')
|
|
106050
|
+
+ (DATA.nodes.some(node => node.removed) ? \`<h2>\${t('removedNav')}</h2><ul>\${DATA.nodes.filter(node => node.removed).map(node => \`<li class="removed">\${nodeLabel(node)}</li>\`).join('')}</ul>\` : '')
|
|
106051
|
+
+ \`<h2>\${t('files')}</h2>\${workspaceTree()}\`;
|
|
106052
|
+
}
|
|
106053
|
+
|
|
106054
|
+
function renderRevisions() {
|
|
106055
|
+
const pages = revisedPages();
|
|
106056
|
+
$('article').innerHTML = \`<h1>\${t('revisionTitle')} <span class="revision-count">· \${pages.length}</span></h1><p class="stats">\${t('revisionHint')}</p>\`
|
|
106057
|
+
+ (pages.length ? pages.map(page => \`<section class="revision-card"><h2><button class="revision-title" data-page="\${escape(page.id)}">\${escape(page.title)}<span class="arrow" aria-hidden="true">↗</span></button></h2>\`
|
|
106058
|
+
+ \`<p class="revision-path">\${escape(page.path)}</p><p class="revision-body">\${escape(notes.get(page.id))}</p></section>\`).join('')
|
|
106059
|
+
: \`<div class="empty-notes">\${t('empty')}<br><span class="note">\${t('emptyHint')}</span></div>\`);
|
|
106060
|
+
}
|
|
106061
|
+
|
|
106062
|
+
function resizeInput() {
|
|
106063
|
+
const input = $('revision-note');
|
|
106064
|
+
input.style.height = '42px';
|
|
106065
|
+
input.style.height = Math.min(112, Math.max(42, input.scrollHeight + 2)) + 'px';
|
|
106066
|
+
}
|
|
106067
|
+
|
|
106068
|
+
function render() {
|
|
106069
|
+
document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en';
|
|
106070
|
+
document.body.classList.toggle('home', view === 'home');
|
|
106071
|
+
document.body.classList.toggle('revision-index', view === 'revisions');
|
|
106072
|
+
const page = pagesById.get(selected);
|
|
106073
|
+
$('article').classList.toggle('review-new-page', view === 'article' && page?.change === 'new');
|
|
106074
|
+
renderNav();
|
|
106075
|
+
updateCounts();
|
|
106076
|
+
if (view === 'home') renderHome();
|
|
106077
|
+
else if (view === 'revisions') renderRevisions();
|
|
106078
|
+
else if (page) {
|
|
106079
|
+
$('article').innerHTML = \`<h1>\${escape(page.title)}\${badge(page.change)}<span id="article-status">\${badges(page)}</span></h1>\`
|
|
106080
|
+
+ (page.previousPath ? \`<p class="note">\${escape(page.previousPath)} → \${escape(page.path)}</p>\` : '')
|
|
106081
|
+
+ (page.candidate_id ? page.html : page.html ? \`<p class="note">\${t('approvedContext')}</p>\${page.html}\` : \`<div class="review-omitted">\${t('unchanged')}</div>\`)
|
|
106082
|
+
+ (page.sources.length ? \`<details><summary>\${t('sources')}</summary><ul>\${page.sources.map(source => \`<li>\${escape(source)}</li>\`).join('')}</ul></details>\` : '');
|
|
106083
|
+
}
|
|
106084
|
+
document.querySelectorAll('[data-label]').forEach(element => { element.textContent = t(element.dataset.label); });
|
|
106085
|
+
$('footer').hidden = !(view === 'article' && page?.candidate_id);
|
|
106086
|
+
$('revision-note').value = page ? notes.get(page.id) || '' : '';
|
|
106087
|
+
$('revision-note').placeholder = t('note');
|
|
106088
|
+
$('clear-note').textContent = t('clear');
|
|
106089
|
+
$('clear-note').disabled = !page || !(notes.get(page.id) || '').trim();
|
|
106090
|
+
$('copy-notes').textContent = t('copy');
|
|
106091
|
+
$('copy-close').textContent = t('close');
|
|
106092
|
+
$('language').textContent = language === 'zh' ? 'EN' : '中文';
|
|
106093
|
+
if (!$('footer').hidden) resizeInput();
|
|
106094
|
+
globalThis.contextDiagramViewer?.render($('article'), { dark: document.body.classList.contains('dark'), language });
|
|
106095
|
+
}
|
|
106096
|
+
|
|
106097
|
+
function showPage(id, updateHash = true) {
|
|
106098
|
+
const page = pagesById.get(id);
|
|
106099
|
+
if (!page) return;
|
|
106100
|
+
selected = id;
|
|
106101
|
+
active = rootFor(id) || active;
|
|
106102
|
+
view = 'article';
|
|
106103
|
+
if (page.candidate_id) { read.add(page.id); persist(); }
|
|
106104
|
+
if (updateHash) history.replaceState(null, '', '#article=' + encodeURIComponent(id));
|
|
106105
|
+
render();
|
|
106106
|
+
window.scrollTo(0, 0);
|
|
106107
|
+
const currentNode = $('tree').querySelector('.selected');
|
|
106108
|
+
if (currentNode) $('tree').scrollTop = Math.max(0, currentNode.offsetTop - $('tree').offsetTop - $('tree').clientHeight / 3);
|
|
106109
|
+
}
|
|
106110
|
+
|
|
106111
|
+
function showToast(message) {
|
|
106112
|
+
clearTimeout(toastTimer);
|
|
106113
|
+
$('toast').textContent = message;
|
|
106114
|
+
$('toast').hidden = false;
|
|
106115
|
+
toastTimer = setTimeout(() => { $('toast').hidden = true; }, 3000);
|
|
106116
|
+
}
|
|
106117
|
+
|
|
106118
|
+
function feedbackText() {
|
|
106119
|
+
return t('revisionTitle') + '\\n\\n' + revisedPages().map(page =>
|
|
106120
|
+
\`《\${page.title}》\\n\${language === 'zh' ? '文章 ID' : 'Article ID'}:\${page.id}\\n\${language === 'zh' ? '修订意见' : 'Revision notes'}:\${notes.get(page.id)}\`
|
|
106121
|
+
).join('\\n\\n');
|
|
106122
|
+
}
|
|
106123
|
+
|
|
106124
|
+
async function copyNotes() {
|
|
106125
|
+
if (!revisedPages().length) { showToast(t('emptyCopy')); return; }
|
|
106126
|
+
const text = feedbackText();
|
|
106127
|
+
try {
|
|
106128
|
+
if (!navigator.clipboard?.writeText) throw new Error('Clipboard unavailable');
|
|
106129
|
+
await navigator.clipboard.writeText(text);
|
|
106130
|
+
showCopyDialog(text, true);
|
|
106131
|
+
} catch {
|
|
106132
|
+
showCopyDialog(text, false);
|
|
106133
|
+
}
|
|
106134
|
+
}
|
|
106135
|
+
|
|
106136
|
+
function showCopyDialog(text, copied) {
|
|
106137
|
+
clearInterval(copyTimer);
|
|
106138
|
+
$('copy-title').textContent = copied ? (language === 'zh' ? '✓ 已复制修订意见' : '✓ Revision notes copied') : t('manualCopy');
|
|
106139
|
+
$('copy-title').classList.toggle('copy-success', copied);
|
|
106140
|
+
$('copy-summary').textContent = copied ? (language === 'zh' ? '可返回会话粘贴以下修订意见。' : 'Paste these revision notes into the conversation.') : t('manualHint');
|
|
106141
|
+
$('copy-preview').hidden = !copied;
|
|
106142
|
+
$('copy-preview').classList.remove('has-overflow');
|
|
106143
|
+
$('copy-preview-text').textContent = text;
|
|
106144
|
+
$('copied-notes').hidden = copied;
|
|
106145
|
+
$('copied-notes').value = text;
|
|
106146
|
+
$('copy-close').parentElement.hidden = copied;
|
|
106147
|
+
$('copy-countdown').hidden = !copied;
|
|
106148
|
+
$('copy-countdown').textContent = language === 'zh' ? '5 秒后关闭' : 'Closes in 5s';
|
|
106149
|
+
if (!$('copy-dialog').open) $('copy-dialog').showModal();
|
|
106150
|
+
if (!copied) {
|
|
106151
|
+
$('copied-notes').focus();
|
|
106152
|
+
$('copied-notes').select();
|
|
106153
|
+
return;
|
|
106154
|
+
}
|
|
106155
|
+
const preview = $('copy-preview');
|
|
106156
|
+
preview.classList.toggle('has-overflow', preview.scrollHeight > preview.clientHeight);
|
|
106157
|
+
const deadline = performance.now() + 5000;
|
|
106158
|
+
copyTimer = setInterval(() => {
|
|
106159
|
+
const remaining = Math.max(0, Math.ceil((deadline - performance.now()) / 1000));
|
|
106160
|
+
$('copy-countdown').textContent = language === 'zh' ? \`\${remaining} 秒后关闭\` : \`Closes in \${remaining}s\`;
|
|
106161
|
+
if (!remaining) { clearInterval(copyTimer); $('copy-dialog').close(); }
|
|
106162
|
+
}, 100);
|
|
106163
|
+
}
|
|
106164
|
+
|
|
106165
|
+
document.addEventListener('click', event => {
|
|
106166
|
+
const button = event.target.closest('button');
|
|
106167
|
+
if (!button) return;
|
|
106168
|
+
if (button.dataset.page) showPage(button.dataset.page);
|
|
106169
|
+
else if (button.dataset.root) {
|
|
106170
|
+
active = button.dataset.root;
|
|
106171
|
+
const node = DATA.nodes.find(item => item.key === active);
|
|
106172
|
+
const children = descendants(active);
|
|
106173
|
+
const preferred = candidates.find(page => [node, ...children].some(item => item?.page === page.id));
|
|
106174
|
+
const pageId = preferred?.id || node?.page || children.find(child => child.page)?.page;
|
|
106175
|
+
if (pageId) showPage(pageId);
|
|
106176
|
+
else render();
|
|
106177
|
+
} else if (button.dataset.node) {
|
|
106178
|
+
const node = DATA.nodes.find(item => item.key === button.dataset.node);
|
|
106179
|
+
if (node.page) showPage(node.page);
|
|
106180
|
+
else { expanded.has(node.key) ? expanded.delete(node.key) : expanded.add(node.key); renderNav(); }
|
|
106181
|
+
}
|
|
106182
|
+
});
|
|
106183
|
+
|
|
106184
|
+
$('revision-note').addEventListener('input', event => {
|
|
106185
|
+
const page = pagesById.get(selected);
|
|
106186
|
+
if (!page?.candidate_id) return;
|
|
106187
|
+
const value = event.target.value;
|
|
106188
|
+
if (value.trim()) notes.set(page.id, value);
|
|
106189
|
+
else notes.delete(page.id);
|
|
106190
|
+
$('clear-note').disabled = !value.trim();
|
|
106191
|
+
persist();
|
|
106192
|
+
updateCounts();
|
|
106193
|
+
renderNav();
|
|
106194
|
+
$('article-status').innerHTML = badges(page);
|
|
106195
|
+
resizeInput();
|
|
106196
|
+
});
|
|
106197
|
+
$('clear-note').onclick = () => {
|
|
106198
|
+
notes.delete(selected);
|
|
106199
|
+
persist();
|
|
106200
|
+
$('revision-note').value = '';
|
|
106201
|
+
$('revision-note').dispatchEvent(new Event('input', { bubbles: true }));
|
|
106202
|
+
$('revision-note').focus();
|
|
106203
|
+
};
|
|
106204
|
+
$('home').onclick = () => { selected = null; active = null; view = 'home'; history.replaceState(null, '', location.pathname + location.search); render(); window.scrollTo(0, 0); };
|
|
106205
|
+
$('theme').onclick = () => { document.body.classList.toggle('dark'); persist(); render(); };
|
|
106206
|
+
$('language').onclick = () => { language = language === 'zh' ? 'en' : 'zh'; persist(); render(); };
|
|
106207
|
+
$('copy-notes').onclick = copyNotes;
|
|
106208
|
+
$('copy-close').onclick = () => $('copy-dialog').close();
|
|
106209
|
+
$('copy-dialog').onclose = () => clearInterval(copyTimer);
|
|
106210
|
+
window.addEventListener('hashchange', () => {
|
|
106211
|
+
if (location.hash.startsWith('#article=')) showPage(decodeURIComponent(location.hash.slice(9)), false);
|
|
106212
|
+
else { selected = null; view = location.hash === '#revisions' ? 'revisions' : 'home'; render(); }
|
|
106213
|
+
});
|
|
106214
|
+
new ResizeObserver(() => { document.documentElement.style.setProperty('--header-height', document.querySelector('header').offsetHeight + 'px'); }).observe(document.querySelector('header'));
|
|
106215
|
+
restore();
|
|
106216
|
+
if (location.hash.startsWith('#article=')) showPage(decodeURIComponent(location.hash.slice(9)), false);
|
|
106217
|
+
else { if (location.hash === '#revisions') view = 'revisions'; render(); }
|
|
105969
106218
|
`;
|
|
105970
106219
|
|
|
105971
106220
|
// src/project/reviewSiteStyles.ts
|
|
105972
106221
|
init_diagramStyles();
|
|
105973
106222
|
var REVIEW_SITE_STYLES = String.raw`
|
|
105974
|
-
:root{--blue:#2563eb;--text:#161e2e;--muted:#646b7c;--line:#e7e9ef;--bg:#fff;--side:#f8f9fc;--green:#28734f;--red:#b7444c;--amber:#93651d}*{box-sizing:border-box}body{margin:0;color:var(--text);background:var(--bg);font:14px/1.75 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}button,input,textarea{font:inherit}button{cursor:pointer}button:disabled{opacity:.35;cursor:not-allowed}[hidden]{display:none!important}header{height:64px;padding:0 24px;display:flex;align-items:center;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:5}#home{border:0;background:none;color:var(--text);width:280px;flex-shrink:0;text-align:left;font-size:15px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nav{display:flex;flex:1;min-width:0;overflow:auto;align-self:stretch}nav button{border:0;background:none;color:var(--text);padding:0 16px;font-size:13px;font-weight:550;white-space:nowrap}nav .active{color:var(--blue);box-shadow:inset 0 -2px var(--blue)}.new,nav .new{color:var(--green)}.modify,nav .modify{color:var(--amber)}.badge{display:inline-block;margin-left:7px;padding:1px 6px;font-size:10px;line-height:18px;vertical-align:middle;border-radius:4px;font-weight:600;letter-spacing:0}.badge.new
|
|
106223
|
+
:root{--blue:#2563eb;--text:#161e2e;--muted:#646b7c;--line:#e7e9ef;--bg:#fff;--side:#f8f9fc;--green:#28734f;--red:#b7444c;--amber:#93651d}*{box-sizing:border-box}body{margin:0;color:var(--text);background:var(--bg);font:14px/1.75 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}button,input,textarea{font:inherit}button{cursor:pointer}button:disabled{opacity:.35;cursor:not-allowed}[hidden]{display:none!important}header{height:64px;padding:0 24px;display:flex;align-items:center;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:5}#home{border:0;background:none;color:var(--text);width:280px;flex-shrink:0;text-align:left;font-size:15px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nav{display:flex;flex:1;min-width:0;overflow:auto;align-self:stretch}nav button{border:0;background:none;color:var(--text);padding:0 16px;font-size:13px;font-weight:550;white-space:nowrap}nav .active{color:var(--blue);box-shadow:inset 0 -2px var(--blue)}.new,nav .new{color:var(--green)}.modify,nav .modify{color:var(--amber)}.badge{display:inline-block;margin-left:7px;padding:1px 6px;font-size:10px;line-height:18px;vertical-align:middle;border-radius:4px;font-weight:600;letter-spacing:0}.badge.new{color:var(--green);background:color-mix(in srgb,var(--green) 10%,var(--bg))}.badge.modify{color:var(--amber);background:color-mix(in srgb,var(--amber) 10%,var(--bg))}.badge.removed{color:var(--red);background:color-mix(in srgb,var(--red) 10%,var(--bg))}.removed,nav .removed,.node.removed{color:var(--red)}.badge{border:1px solid color-mix(in srgb,currentColor 18%,transparent)}.tools{display:flex;align-items:center;gap:8px;position:relative;margin-left:12px}.btn{border:1px solid var(--line);border-radius:6px;background:var(--bg);color:var(--text);padding:5px 10px;font-size:12px;line-height:22px;white-space:nowrap}.primary{color:var(--bg);background:var(--blue);border-color:var(--blue)}#theme,#language{margin-left:8px;flex-shrink:0}.layout{display:grid;grid-template-columns:280px minmax(0,1fr)}aside{height:calc(100vh - 64px);position:sticky;top:64px;overflow:auto;background:var(--side);border-right:1px solid var(--line);padding:12px 0 80px}.node{display:block;width:100%;text-align:left;border:0;background:none;height:38px;min-height:38px;padding:9px 16px 9px 26px;font-size:13px;font-weight:450;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--muted)}.node.directory{color:var(--text)}.node.selected.unchanged{color:var(--blue);font-weight:550}.node.new{color:var(--green)}.node.modify{color:var(--amber)}.node.selected{background:color-mix(in srgb,var(--blue) 8%,transparent)}.caret{float:right}.node:hover{background:#8e96aa1a}main{min-width:0;padding:44px 60px 100px 36px}h1{font-size:36px;line-height:1.3;letter-spacing:-.025em;margin:0 0 28px}h2{font-size:25px;line-height:1.4;font-weight:650;margin:46px 0 22px;border-bottom:1px solid var(--line);padding-bottom:14px}h3{font-size:19px;font-weight:600;line-height:1.5;margin:30px 0 14px}article{font-size:16px;line-height:1.8}article>h1>.badge{margin-left:12px}article a{color:inherit}article a:hover{color:var(--blue)}article table{border-collapse:collapse;font-size:14px;display:block;overflow:auto}article th,article td{border:1px solid var(--line);padding:11px 14px}article th{background:var(--side)}pre{overflow:auto;background:var(--side);padding:16px}blockquote{padding:16px 20px;margin:24px 0;border-left:3px solid var(--context-accent);background:color-mix(in srgb,var(--blue) 8%,transparent);color:var(--muted)}.home .layout{display:block}.home aside{display:none}.home main{width:1120px;max-width:calc(100% - 280px);margin-left:280px;padding-top:32px}.home article{font-size:14px}.home h1{font-size:28px;margin-bottom:12px}.home h2{font-size:19px;margin:24px 0 12px;padding-bottom:10px}.home h3{font-size:15px;margin:16px 0 10px}.stats{font-size:12px;color:var(--muted)}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,1fr));gap:8px}.cards button{border:1px solid var(--line);border-radius:7px;background:var(--bg);color:var(--text);text-align:left;font-size:13px;line-height:20px;padding:10px 14px}.cards button:hover{border-color:var(--blue)}.review-unchanged-heading{color:var(--muted)}.review-omitted{margin:20px 0;padding:18px 20px;border:1px dashed var(--line);border-radius:8px;color:var(--muted);font-size:13px}.changed{position:relative;display:flow-root;color:var(--amber);padding:0 76px 0 0;margin:20px 0}.changed>.badge{position:absolute;right:0;top:4px}.changed>:nth-child(2){margin-top:0}.changed>:last-child{margin-bottom:0}.changed details{color:inherit}.changed.removed{color:var(--red)}.workspace-tree{font:12px/1.9 ui-monospace,monospace;border:1px solid var(--line);border-radius:8px;padding:16px;background:var(--side);overflow:auto}.workspace-tree button{border:0;background:none;color:var(--text);padding:0;font:inherit;white-space:nowrap}.note{font-size:12px;color:var(--muted)}footer{position:fixed;bottom:0;left:280px;right:0;background:var(--bg);border-top:1px solid var(--line);padding:12px 35px;display:flex;gap:10px;z-index:4}footer .btn{min-width:76px}dialog{width:560px;max-width:90vw;background:var(--bg);color:var(--text);border:1px solid var(--line);border-radius:10px;padding:20px;font-size:13px;line-height:1.65}dialog::backdrop{background:#131b3255}dialog h2{font-size:17px;margin:0 0 12px;border:0;padding:0}dialog textarea{width:100%;height:130px;border:1px solid var(--line);border-radius:6px;padding:10px;font:11px/1.6 monospace;background:var(--side);color:var(--text)}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}.dark{--bg:#171b24;--side:#1b1d24;--text:#e2e4ed;--muted:#a1a6b7;--line:#303440;--blue:#8eb7ff;--green:#83c9a0;--amber:#dab779;--red:#e49a9e}@media(min-width:1600px){main,.home main{padding-left:52px}}@media(max-width:1279px){#home{width:220px}nav button{padding:0 9px}.tools{gap:4px}}@media(max-width:1050px){header{height:auto;flex-wrap:wrap;min-height:64px}nav{order:3;flex-basis:100%;height:44px}.tools{margin-left:auto}}@media(max-width:700px){.layout{display:block}aside{position:relative;top:0;height:200px}main,.home main{width:100%;max-width:100%;margin:0;padding:24px 20px 90px}footer{left:0;padding:10px;flex-wrap:wrap}footer input{flex-basis:100%}.tools{flex-wrap:wrap}h1{font-size:28px}}
|
|
105975
106224
|
${readerContentStyles("article")}
|
|
105976
106225
|
article code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
105977
106226
|
article :not(pre)>code{padding:3px 6px;overflow-wrap:break-word}
|
|
@@ -105994,10 +106243,85 @@ article.review-new-page .language-mermaid{--context-text:var(--green);--context-
|
|
|
105994
106243
|
article .changed .language-mermaid{--context-text:var(--amber);--context-muted-text:var(--amber)}
|
|
105995
106244
|
article .changed.removed .language-mermaid{--context-text:var(--red);--context-muted-text:var(--red)}
|
|
105996
106245
|
article.review-new-page .context-diagram-shell,article .changed .context-diagram-shell{--diagram-text:var(--context-text)}
|
|
106246
|
+
header { gap: 0; }
|
|
106247
|
+
header #home { width: 242px; }
|
|
106248
|
+
header nav { scrollbar-width: none; }
|
|
106249
|
+
header nav::-webkit-scrollbar { display: none; }
|
|
106250
|
+
header nav button { padding: 0 11px; }
|
|
106251
|
+
.tools { flex-shrink: 0; gap: 16px; }
|
|
106252
|
+
.reading-progress { min-width: 225px; }
|
|
106253
|
+
#counts { display: flex; align-items: center; justify-content: space-between; gap: 8px; white-space: nowrap; font-size: 12px; color: var(--muted); }
|
|
106254
|
+
#counts b { font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
106255
|
+
#counts .separator { color: var(--line); }
|
|
106256
|
+
#revisions-link { border: 0; padding: 0; background: transparent; color: var(--muted); font-size: inherit; }
|
|
106257
|
+
#revisions-link.has-notes, #revisions-link.has-notes b { color: var(--amber); }
|
|
106258
|
+
#revisions-link:hover, #revisions-link[aria-pressed="true"] { color: var(--blue); text-decoration: none; }
|
|
106259
|
+
.read-track { height: 2px; margin-top: 5px; background: var(--line); border-radius: 2px; overflow: hidden; }
|
|
106260
|
+
#read-fill { display: block; height: 100%; width: 0; background: var(--blue); transition: width .2s ease; }
|
|
106261
|
+
.badge.read { color: var(--blue); background: color-mix(in srgb,var(--blue) 8%,var(--bg)); font-size: 9px; letter-spacing: .03em; }
|
|
106262
|
+
.badge.has-feedback { color: var(--amber); background: color-mix(in srgb,var(--amber) 8%,var(--bg)); }
|
|
106263
|
+
aside { top: var(--header-height,64px); height: calc(100vh - var(--header-height,64px)); }
|
|
106264
|
+
.node { height: auto; min-height: 38px; }
|
|
106265
|
+
.node .node-content { display: flex; align-items: center; min-width: 0; gap: 5px; }
|
|
106266
|
+
.node .node-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
106267
|
+
.node .node-status { margin-left: auto; display: inline-flex; gap: 3px; flex-shrink: 0; }
|
|
106268
|
+
.node .badge { margin: 0; padding: 0 4px; line-height: 17px; }
|
|
106269
|
+
.node .caret { margin-left: auto; }
|
|
106270
|
+
main { padding-bottom: 160px; }
|
|
106271
|
+
.home main { max-width: 1100px; width: calc(100% - 280px); }
|
|
106272
|
+
.home h1 { font-size: 28px; }
|
|
106273
|
+
.revision-index main { max-width: 1060px; }
|
|
106274
|
+
.revision-index h1 { font-size: 28px; margin-bottom: 12px; }
|
|
106275
|
+
.revision-index .revision-card { margin: 18px 0; padding: 22px 24px; border: 1px solid var(--line); background: var(--bg); border-radius: 9px; }
|
|
106276
|
+
.revision-card h2 { border: 0; padding: 0; margin: 0 0 8px; font-size: 17px; line-height: 1.5; }
|
|
106277
|
+
.revision-title { border: 0; padding: 0; background: transparent; color: var(--text); text-align: left; font-size: inherit; font-weight: 600; }
|
|
106278
|
+
.revision-title:hover { color: var(--blue); }
|
|
106279
|
+
.revision-title .arrow { color: var(--muted); font-size: 14px; margin-left: 5px; }
|
|
106280
|
+
.revision-path { color: var(--muted); font-size: 11px; margin: 0 0 15px; overflow-wrap: anywhere; }
|
|
106281
|
+
.revision-body { font-size: 14px; line-height: 1.85; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; }
|
|
106282
|
+
.empty-notes { padding: 38px 24px; margin: 24px 0; border: 1px dashed var(--line); border-radius: 9px; color: var(--muted); text-align: center; }
|
|
106283
|
+
footer { align-items: center; flex-wrap: nowrap; padding: 12px 35px; gap: 12px; }
|
|
106284
|
+
#revision-note { display: block; flex: 1 1 0%; min-width: 0; height: 42px; max-height: 112px; resize: none; overflow-y: auto; border: 1px solid var(--line); border-radius: 6px; padding: 9px 12px; background: var(--side); color: var(--text); font: inherit; font-size: 14px; line-height: 22px; }
|
|
106285
|
+
#revision-note::placeholder { color: var(--muted); opacity: .85; }
|
|
106286
|
+
#revision-note:focus { outline: 2px solid color-mix(in srgb,var(--blue) 25%,transparent); outline-offset: 0; border-color: var(--blue); }
|
|
106287
|
+
#clear-note { flex-shrink: 0; min-width: 108px; height: 36px; }
|
|
106288
|
+
#clear-note:disabled { opacity: .4; }
|
|
106289
|
+
#toast { position: fixed; bottom: 100px; left: 50%; transform: translateX(-50%); z-index: 20; max-width: calc(100vw - 32px); border: 1px solid var(--line); border-radius: 8px; padding: 10px 18px; color: var(--text); background: var(--bg); box-shadow: 0 5px 24px #17244222; font-size: 13px; }
|
|
106290
|
+
#copied-notes { height: 240px; font: 13px/1.8 inherit; resize: vertical; }
|
|
106291
|
+
.copy-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
106292
|
+
.copy-heading h2 { margin: 0; }
|
|
106293
|
+
.copy-heading h2.copy-success { color: var(--green); }
|
|
106294
|
+
#copy-countdown { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
106295
|
+
#copy-preview { position: relative; max-height: min(400px, calc(100dvh - 180px)); overflow: hidden; border: 1px solid var(--line); border-radius: 7px; background: var(--side); }
|
|
106296
|
+
#copy-preview-text { padding: 14px 16px; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13px; line-height: 1.85; }
|
|
106297
|
+
.copy-ellipsis { display: none; position: absolute; bottom: 0; left: 0; right: 0; height: 40px; padding: 13px 16px 2px; background: linear-gradient(transparent, var(--side) 35%); color: var(--muted); font-size: 16px; }
|
|
106298
|
+
#copy-preview.has-overflow .copy-ellipsis { display: block; }
|
|
106299
|
+
button:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
|
|
106300
|
+
@media (min-width: 1600px) { .home main { max-width: 1100px; } }
|
|
106301
|
+
@media (max-width: 1200px) { header #home { width: 205px; } .tools { gap: 10px; } header nav button { padding: 0 8px; } }
|
|
106302
|
+
@media (max-width: 1050px) { header { padding: 10px 20px 0; } header nav { order: 3; flex-basis: 100%; height: 42px; } header #home { flex: 1; width: auto; } .tools { margin-left: 8px; } }
|
|
106303
|
+
@media (max-width: 700px) {
|
|
106304
|
+
header { padding: 10px 14px 0; gap: 6px 0; }
|
|
106305
|
+
header #home { flex: 1 1 auto; font-size: 14px; }
|
|
106306
|
+
header .tools { order: 2; width: 100%; margin-left: 0; justify-content: space-between; flex-wrap: nowrap; padding: 4px 0; }
|
|
106307
|
+
header .reading-progress { flex: 1; min-width: 0; max-width: 260px; }
|
|
106308
|
+
header #theme, header #language { margin-left: 6px; }
|
|
106309
|
+
header nav { order: 3; margin-top: 2px; }
|
|
106310
|
+
aside { top: 0; position: relative; height: 180px; }
|
|
106311
|
+
main, .home main { width: 100%; max-width: 100%; margin: 0; padding: 24px 20px 150px; }
|
|
106312
|
+
.home h1 { font-size: 26px; }
|
|
106313
|
+
footer { left: 0; flex-wrap: nowrap; gap: 8px; padding: 10px 12px; }
|
|
106314
|
+
#revision-note { flex: 1 1 0%; font-size: 14px; }
|
|
106315
|
+
#clear-note { min-width: 96px; padding: 5px 8px; }
|
|
106316
|
+
.revision-index .revision-card { padding: 18px; }
|
|
106317
|
+
.changed { padding-right: 0; }
|
|
106318
|
+
.changed > .badge { position: static; margin: 0 0 10px; }
|
|
106319
|
+
}
|
|
106320
|
+
|
|
105997
106321
|
`;
|
|
105998
106322
|
|
|
105999
106323
|
// src/project/reviewHtml.ts
|
|
106000
|
-
var REVIEW_HTML_ROOT =
|
|
106324
|
+
var REVIEW_HTML_ROOT = join91(".tmp", "context-runtime", "review");
|
|
106001
106325
|
async function collectReviewCandidates(projectRoot, collection) {
|
|
106002
106326
|
const rows = await readCandidateRecords(projectRoot);
|
|
106003
106327
|
const draftRows = rows.filter((row) => row.collection === collection && row.status === "draft");
|
|
@@ -106014,23 +106338,39 @@ async function collectAllReviewCandidates(projectRoot) {
|
|
|
106014
106338
|
snapshot: await readReviewCandidateSnapshot(projectRoot, record4)
|
|
106015
106339
|
})));
|
|
106016
106340
|
}
|
|
106017
|
-
function renderReviewHtml(
|
|
106018
|
-
|
|
106019
|
-
|
|
106020
|
-
|
|
106021
|
-
|
|
106022
|
-
|
|
106023
|
-
|
|
106024
|
-
<
|
|
106025
|
-
<div class="
|
|
106026
|
-
|
|
106027
|
-
<
|
|
106028
|
-
|
|
106029
|
-
|
|
106341
|
+
function renderReviewHtml(model, diagramScript) {
|
|
106342
|
+
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeReviewHtml(model.title)} · Reading report</title><style>${REVIEW_SITE_STYLES}${model.themeCss ?? ""}</style></head><body>
|
|
106343
|
+
<header>
|
|
106344
|
+
<button id="home" aria-label="返回本次内容">${escapeReviewHtml(model.title)}</button>
|
|
106345
|
+
<nav id="top" aria-label="知识分类"></nav>
|
|
106346
|
+
<div class="tools">
|
|
106347
|
+
<div class="reading-progress">
|
|
106348
|
+
<div id="counts" aria-label="阅读进度"></div>
|
|
106349
|
+
<div class="read-track" role="progressbar" aria-label="本轮阅读进度" aria-valuemin="0" aria-valuenow="0"><span id="read-fill"></span></div>
|
|
106350
|
+
</div>
|
|
106351
|
+
<button class="btn primary" id="copy-notes">复制修订意见</button>
|
|
106352
|
+
</div>
|
|
106353
|
+
<button class="btn" id="theme" aria-label="切换深色主题" title="切换深色主题">◐</button>
|
|
106354
|
+
<button class="btn" id="language" aria-label="Switch language">EN</button>
|
|
106355
|
+
</header>
|
|
106356
|
+
<div class="layout"><aside id="tree" aria-label="文章目录"></aside><main><article id="article"></article></main></div>
|
|
106357
|
+
<footer id="footer" hidden>
|
|
106358
|
+
<textarea id="revision-note" rows="1" placeholder="输入本篇修订意见……" aria-label="本篇修订意见"></textarea>
|
|
106359
|
+
<button class="btn" id="clear-note" disabled>清空本条修订</button>
|
|
106360
|
+
</footer>
|
|
106361
|
+
<dialog id="copy-dialog" aria-labelledby="copy-title">
|
|
106362
|
+
<div class="copy-heading"><h2 id="copy-title"></h2><span id="copy-countdown" hidden></span></div>
|
|
106363
|
+
<p id="copy-summary"></p>
|
|
106364
|
+
<div id="copy-preview" hidden><div id="copy-preview-text"></div><span class="copy-ellipsis" aria-hidden="true">...</span></div>
|
|
106365
|
+
<textarea id="copied-notes" readonly aria-label="修订意见文本"></textarea>
|
|
106366
|
+
<div class="dialog-actions"><button class="btn" id="copy-close">关闭</button></div>
|
|
106367
|
+
</dialog>
|
|
106368
|
+
<div id="toast" role="status" aria-live="polite" hidden></div>
|
|
106369
|
+
${diagramScript ? `<script>${diagramScript}</script>` : ""}<script>const DATA=${reviewHtmlJson(model)};${REVIEW_SITE_CLIENT}</script></body></html>`;
|
|
106030
106370
|
}
|
|
106031
106371
|
function resolveOutputPath(projectRoot, outPath, reviewScope) {
|
|
106032
106372
|
if (outPath === undefined)
|
|
106033
|
-
return
|
|
106373
|
+
return join91(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
|
|
106034
106374
|
return isAbsolute15(outPath) ? outPath : resolve28(projectRoot, outPath);
|
|
106035
106375
|
}
|
|
106036
106376
|
async function writeReviewHtml(input) {
|
|
@@ -106046,14 +106386,15 @@ async function writeReviewHtml(input) {
|
|
|
106046
106386
|
if (model.pages.some((page) => page.html.includes('class="language-mermaid"'))) {
|
|
106047
106387
|
const here = dirname38(fileURLToPath6(import.meta.url));
|
|
106048
106388
|
try {
|
|
106049
|
-
diagramScript = await
|
|
106389
|
+
diagramScript = await readFile72(join91(here, "browser/diagrams.js"), "utf8");
|
|
106050
106390
|
} catch (error) {
|
|
106051
106391
|
if (error.code !== "ENOENT")
|
|
106052
106392
|
throw error;
|
|
106053
|
-
diagramScript = await
|
|
106393
|
+
diagramScript = await readFile72(resolve28(here, "../../dist/browser/diagrams.js"), "utf8");
|
|
106054
106394
|
}
|
|
106055
106395
|
}
|
|
106056
|
-
await writeFile25(outPath, renderReviewHtml(
|
|
106396
|
+
await writeFile25(outPath, renderReviewHtml(model, diagramScript), "utf8");
|
|
106397
|
+
await saveReviewReportScope(input.projectRoot, reviewScope, candidates, model.baselineHash);
|
|
106057
106398
|
const unplaced = model.pages.filter((page) => page.candidate_id && !model.nodes.some((node3) => node3.page === page.id && node3.parent !== "review-unplaced" && !node3.removed)).map((page) => ({ article_id: page.id, candidate_id: page.candidate_id, path: page.path, title: page.title }));
|
|
106058
106399
|
return {
|
|
106059
106400
|
navigation: { ready: unplaced.length === 0, unplaced_count: unplaced.length, unplaced },
|
|
@@ -106073,8 +106414,8 @@ init_productionRequirements();
|
|
|
106073
106414
|
init_dist();
|
|
106074
106415
|
init_atomicWrite();
|
|
106075
106416
|
init_reviewShared();
|
|
106076
|
-
import { mkdir as mkdir32, readFile as
|
|
106077
|
-
import { join as
|
|
106417
|
+
import { mkdir as mkdir32, readFile as readFile73 } from "node:fs/promises";
|
|
106418
|
+
import { join as join92 } from "node:path";
|
|
106078
106419
|
var REVIEW_BATCH_MAX_CANDIDATES = 6;
|
|
106079
106420
|
var REVIEW_BATCH_MAX_BYTES = 512 * 1024;
|
|
106080
106421
|
async function readerPurposes(projectRoot, sources) {
|
|
@@ -106154,12 +106495,12 @@ async function materializeCurrentReviewBatchSet(input) {
|
|
|
106154
106495
|
const batches = buildCurrentReviewBatchDocuments(input.candidates);
|
|
106155
106496
|
const setDigest = digestText(batches.map((batch) => `${batch.task_key}:${batch.digest}`).join(`
|
|
106156
106497
|
`));
|
|
106157
|
-
const root2 =
|
|
106498
|
+
const root2 = join92(input.projectRoot, ".tmp", "context-runtime", "review", `current-${setDigest.slice("sha256:".length)}`);
|
|
106158
106499
|
await mkdir32(root2, { recursive: true });
|
|
106159
106500
|
const entries2 = [];
|
|
106160
106501
|
for (const batch of batches) {
|
|
106161
|
-
const path4 =
|
|
106162
|
-
const existing = await
|
|
106502
|
+
const path4 = join92(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
|
|
106503
|
+
const existing = await readFile73(path4, "utf8").catch((error) => {
|
|
106163
106504
|
if (error.code === "ENOENT")
|
|
106164
106505
|
return;
|
|
106165
106506
|
throw error;
|
|
@@ -106187,7 +106528,7 @@ async function materializeCurrentReviewBatchSet(input) {
|
|
|
106187
106528
|
"Apply only decisions for pages actually reviewed. Keep undecided or repair pages out of decisions; do not set default unless every page has that decision. Omit remains a durable exclusion, never a request to repair.",
|
|
106188
106529
|
"Save the following current scope in a temporary JSON input file and add decisions as {candidate_id, status: approved|rejected}; use that file in the Route command. Applied pages survive later repairs of other pages.",
|
|
106189
106530
|
"```json",
|
|
106190
|
-
JSON.stringify({ schema: REVIEW_PAYLOAD_SCHEMA, scope: {
|
|
106531
|
+
JSON.stringify({ schema: REVIEW_PAYLOAD_SCHEMA, baseline_hash: await reviewSiteBaselineHash(input.projectRoot, input.candidates.map((c) => c.record.approved_revision?.previous_path ?? c.record.path)), scope: {
|
|
106191
106532
|
kind: "all",
|
|
106192
106533
|
count: input.candidates.length,
|
|
106193
106534
|
ids_sha256: candidateIdsHash(input.candidates.map((item) => item.record.candidate_id).sort()),
|
|
@@ -106212,7 +106553,7 @@ async function materializeCurrentReviewBatchSet(input) {
|
|
|
106212
106553
|
].join(`
|
|
106213
106554
|
`);
|
|
106214
106555
|
const digest6 = digestText(content3);
|
|
106215
|
-
const path3 =
|
|
106556
|
+
const path3 = join92(root2, "index.md");
|
|
106216
106557
|
await atomicWriteFile(path3, `${content3}
|
|
106217
106558
|
`);
|
|
106218
106559
|
return {
|
|
@@ -106245,11 +106586,11 @@ function shellQuote6(value) {
|
|
|
106245
106586
|
}
|
|
106246
106587
|
function receiptSetPath(receipts) {
|
|
106247
106588
|
const token = digestText(JSON.stringify(receipts)).slice("sha256:".length);
|
|
106248
|
-
return
|
|
106589
|
+
return join93(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
|
|
106249
106590
|
}
|
|
106250
106591
|
async function writeReceiptContinuation(input) {
|
|
106251
106592
|
const path3 = receiptSetPath(input.receipts);
|
|
106252
|
-
const absolutePath =
|
|
106593
|
+
const absolutePath = join93(input.projectRoot, path3);
|
|
106253
106594
|
await writeJsonAtomic(absolutePath, input.receipts);
|
|
106254
106595
|
const contextCommand = input.managed ? [
|
|
106255
106596
|
"context",
|
|
@@ -106344,7 +106685,7 @@ async function materializeContextWorkflowResource(input) {
|
|
|
106344
106685
|
const resourceId = workflowResourceId(input.resourceId);
|
|
106345
106686
|
const content3 = renderContextWorkflowResource(resourceId, status);
|
|
106346
106687
|
const location = await materializeResource(await loadContextWorkflowProvider(), resourceId, {
|
|
106347
|
-
cache:
|
|
106688
|
+
cache: join93(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
|
|
106348
106689
|
workspace: found.projectRoot,
|
|
106349
106690
|
revision: input.revision,
|
|
106350
106691
|
input: {
|
|
@@ -106376,7 +106717,7 @@ async function materializeContextWorkflowResource(input) {
|
|
|
106376
106717
|
receipts: afterReadReceipts
|
|
106377
106718
|
});
|
|
106378
106719
|
const directResources = (status.workflow.current?.resources.required ?? []).filter((resource) => resource.read_state === "read-required" && resource.path !== undefined && resource.digest !== undefined);
|
|
106379
|
-
const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${
|
|
106720
|
+
const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${join93(found.projectRoot, continuation.path)}`)} --format json`;
|
|
106380
106721
|
return {
|
|
106381
106722
|
protocol: "context.workflow.resource.v1",
|
|
106382
106723
|
id: resourceId,
|
|
@@ -106437,14 +106778,14 @@ async function acknowledgeCurrentWorkflowResources(input) {
|
|
|
106437
106778
|
const reevaluated = await reevaluateProjectStatusWorkflow({
|
|
106438
106779
|
snapshot,
|
|
106439
106780
|
resourceReceipts: normalizedReceipts,
|
|
106440
|
-
resourceReceiptsReference: `@${
|
|
106781
|
+
resourceReceiptsReference: `@${join93(found.projectRoot, continuation.path)}`
|
|
106441
106782
|
});
|
|
106442
106783
|
return {
|
|
106443
106784
|
...reevaluated,
|
|
106444
106785
|
resourceAcknowledgement: {
|
|
106445
106786
|
protocol: "context.workflow.resource-receipts.v1",
|
|
106446
106787
|
acknowledged: directResources.length,
|
|
106447
|
-
receiptReference: `@${
|
|
106788
|
+
receiptReference: `@${join93(found.projectRoot, continuation.path)}`,
|
|
106448
106789
|
message: "Read the returned workflow and follow its selected command unchanged, including revision and resource-receipt arguments. Do not pre-chain an earlier write command or replace this result with status without receipts. Acknowledgement does not necessarily change the revision; reread only changed or unavailable required content."
|
|
106449
106790
|
}
|
|
106450
106791
|
};
|
|
@@ -106491,9 +106832,9 @@ init_cliFeedback();
|
|
|
106491
106832
|
init_errors3();
|
|
106492
106833
|
init_exitCode();
|
|
106493
106834
|
init_workspace();
|
|
106494
|
-
import { readFile as
|
|
106495
|
-
import { isAbsolute as isAbsolute16, join as
|
|
106496
|
-
var RECEIPT_DIRECTORY =
|
|
106835
|
+
import { readFile as readFile74 } from "node:fs/promises";
|
|
106836
|
+
import { isAbsolute as isAbsolute16, join as join94, sep as sep6, resolve as resolve29 } from "node:path";
|
|
106837
|
+
var RECEIPT_DIRECTORY = join94(".tmp", "context-runtime", "workflow", "read-receipts");
|
|
106497
106838
|
function workflowResourceReceiptCwd(value, cwd) {
|
|
106498
106839
|
if (value === undefined || !value.startsWith("@"))
|
|
106499
106840
|
return cwd;
|
|
@@ -106511,7 +106852,7 @@ async function receiptDocument(value, cwd) {
|
|
|
106511
106852
|
let source2 = value;
|
|
106512
106853
|
if (value.startsWith("@")) {
|
|
106513
106854
|
try {
|
|
106514
|
-
source2 = await
|
|
106855
|
+
source2 = await readFile74(resolve29(cwd, value.slice(1)), "utf8");
|
|
106515
106856
|
} catch (error) {
|
|
106516
106857
|
const ioCode = error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : undefined;
|
|
106517
106858
|
throw new ContextError(ExitCode.UserError, "resource read receipt file is unavailable", {
|
|
@@ -106770,14 +107111,14 @@ function compactJsonResult(result, verbose) {
|
|
|
106770
107111
|
// src/project/runLog.ts
|
|
106771
107112
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
106772
107113
|
import { mkdir as mkdir34, writeFile as writeFile27 } from "node:fs/promises";
|
|
106773
|
-
import { dirname as dirname40, join as
|
|
107114
|
+
import { dirname as dirname40, join as join100 } from "node:path";
|
|
106774
107115
|
var createPhaseRunId = () => {
|
|
106775
107116
|
const timestamp = new Date().toISOString().replace(/[-:.TZ]/gu, "");
|
|
106776
107117
|
return `run_${timestamp}_${randomUUID5().slice(0, 8)}`;
|
|
106777
107118
|
};
|
|
106778
107119
|
async function writePhaseRunLog(input) {
|
|
106779
|
-
const relPath =
|
|
106780
|
-
const absPath =
|
|
107120
|
+
const relPath = join100(".tmp", "context-runtime", "runs", `${input.runId}.json`);
|
|
107121
|
+
const absPath = join100(input.projectRoot, relPath);
|
|
106781
107122
|
await mkdir34(dirname40(absPath), { recursive: true });
|
|
106782
107123
|
await writeFile27(absPath, `${JSON.stringify({
|
|
106783
107124
|
run_id: input.runId,
|
|
@@ -107720,6 +108061,9 @@ execution scope cleanup failed`, true)
|
|
|
107720
108061
|
// src/project/workflow/workflowInProcessActions.ts
|
|
107721
108062
|
init_workflowFacts();
|
|
107722
108063
|
|
|
108064
|
+
// src/project/reviewLegacy.ts
|
|
108065
|
+
init_candidateLedger();
|
|
108066
|
+
|
|
107723
108067
|
// src/project/reviewCode.ts
|
|
107724
108068
|
function createReviewCodeCodec() {
|
|
107725
108069
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
@@ -107859,11 +108203,140 @@ function createReviewCodeCodec() {
|
|
|
107859
108203
|
return { encode, decode: decode2 };
|
|
107860
108204
|
}
|
|
107861
108205
|
|
|
108206
|
+
// src/project/reviewFeedbackCode.ts
|
|
108207
|
+
function createReviewFeedbackCodec() {
|
|
108208
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
108209
|
+
function checksum(text9) {
|
|
108210
|
+
let crc = 4294967295;
|
|
108211
|
+
for (let i2 = 0;i2 < text9.length; i2++) {
|
|
108212
|
+
crc ^= text9.charCodeAt(i2);
|
|
108213
|
+
for (let j = 0;j < 8; j++)
|
|
108214
|
+
crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
|
|
108215
|
+
}
|
|
108216
|
+
return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
|
|
108217
|
+
}
|
|
108218
|
+
function validate2(value) {
|
|
108219
|
+
if (!value || !/^[a-z][a-z0-9-]*$/.test(value.scope) || ![value.idsHash, value.contentHash, value.baselineHash].every((h) => typeof h === "string" && /^[a-f0-9]{64}$/.test(h)) || !Array.isArray(value.statuses) || !value.statuses.length || value.statuses.length > 1e5 || value.statuses.some((s) => !["approved", "rejected", "pending", "revised"].includes(s)) || value.statuses.every((s) => s === "pending") || !Array.isArray(value.repairs))
|
|
108220
|
+
throw new Error("Invalid review feedback scope or decisions");
|
|
108221
|
+
const seen = new Set;
|
|
108222
|
+
for (const repair of value.repairs) {
|
|
108223
|
+
if (!repair || !Number.isInteger(repair.index) || seen.has(repair.index) || value.statuses[repair.index] !== "revised" || typeof repair.instruction !== "string" || !repair.instruction.trim() || repair.instruction.length > 20000)
|
|
108224
|
+
throw new Error("Invalid or conflicting revision instruction");
|
|
108225
|
+
seen.add(repair.index);
|
|
108226
|
+
}
|
|
108227
|
+
if (value.statuses.filter((s) => s === "revised").length !== seen.size)
|
|
108228
|
+
throw new Error("Missing revision instruction");
|
|
108229
|
+
return value;
|
|
108230
|
+
}
|
|
108231
|
+
function encode(value) {
|
|
108232
|
+
validate2(value);
|
|
108233
|
+
const bytes = new TextEncoder().encode(JSON.stringify({ scope: value.scope, idsHash: value.idsHash, contentHash: value.contentHash, baselineHash: value.baselineHash, statuses: value.statuses }));
|
|
108234
|
+
let bits = 0, carry = 0, data2 = "";
|
|
108235
|
+
for (const byte of bytes) {
|
|
108236
|
+
carry = carry << 8 | byte;
|
|
108237
|
+
bits += 8;
|
|
108238
|
+
while (bits >= 6) {
|
|
108239
|
+
bits -= 6;
|
|
108240
|
+
data2 += alphabet[carry >>> bits & 63];
|
|
108241
|
+
}
|
|
108242
|
+
}
|
|
108243
|
+
if (bits)
|
|
108244
|
+
data2 += alphabet[carry << 6 - bits & 63];
|
|
108245
|
+
const body2 = `CR2.${data2}`;
|
|
108246
|
+
const repairs = value.repairs.map((r) => JSON.stringify([r.index, r.instruction])).join(`
|
|
108247
|
+
`);
|
|
108248
|
+
const trailer = repairs ? `
|
|
108249
|
+
${repairs}` : "";
|
|
108250
|
+
const code3 = `${body2}.${checksum(body2 + trailer)}${trailer}`;
|
|
108251
|
+
if (code3.length > 250000)
|
|
108252
|
+
throw new Error("Review feedback is too large; shorten instructions or review a smaller scope");
|
|
108253
|
+
return code3;
|
|
108254
|
+
}
|
|
108255
|
+
function decode2(raw) {
|
|
108256
|
+
const code3 = raw.trim();
|
|
108257
|
+
if (code3.length > 250000)
|
|
108258
|
+
throw new Error("Review feedback exceeds supported size");
|
|
108259
|
+
const [header, ...lines] = code3.split(`
|
|
108260
|
+
`);
|
|
108261
|
+
const trailer = lines.length ? `
|
|
108262
|
+
${lines.join(`
|
|
108263
|
+
`)}` : "";
|
|
108264
|
+
const match = /^CR2\.([A-Za-z0-9_-]+)\.([a-f0-9]{8})$/.exec(header);
|
|
108265
|
+
if (!match || checksum(`CR2.${match[1]}` + trailer) !== match[2])
|
|
108266
|
+
throw new Error("Review feedback is damaged or incomplete; copy the full code again");
|
|
108267
|
+
let bits = 0, carry = 0;
|
|
108268
|
+
const bytes = [];
|
|
108269
|
+
for (const char of match[1]) {
|
|
108270
|
+
carry = carry << 6 | alphabet.indexOf(char);
|
|
108271
|
+
bits += 6;
|
|
108272
|
+
if (bits >= 8) {
|
|
108273
|
+
bits -= 8;
|
|
108274
|
+
bytes.push(carry >>> bits & 255);
|
|
108275
|
+
}
|
|
108276
|
+
}
|
|
108277
|
+
const headerValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes)));
|
|
108278
|
+
const value = validate2({ ...headerValue, repairs: lines.map((line) => {
|
|
108279
|
+
const row = JSON.parse(line);
|
|
108280
|
+
if (!Array.isArray(row) || row.length !== 2)
|
|
108281
|
+
throw new Error("Invalid revision instruction line");
|
|
108282
|
+
return { index: row[0], instruction: row[1] };
|
|
108283
|
+
}) });
|
|
108284
|
+
if (encode(value) !== code3)
|
|
108285
|
+
throw new Error("Noncanonical review feedback encoding");
|
|
108286
|
+
return value;
|
|
108287
|
+
}
|
|
108288
|
+
return { encode, decode: decode2 };
|
|
108289
|
+
}
|
|
108290
|
+
|
|
108291
|
+
// src/project/reviewLegacy.ts
|
|
108292
|
+
init_reviewShared();
|
|
108293
|
+
init_errors3();
|
|
108294
|
+
init_cliFeedback();
|
|
108295
|
+
init_exitCode();
|
|
108296
|
+
function warnLegacyReview(entry) {
|
|
108297
|
+
process.stderr.write(`[context] ${entry} is accepted for compatibility. 新版审核页无需审核码;直接回复确认或修订意见。Agent 在用户确认后使用 context review approve-all [collection] --confirmed(跨集合加 --all);修订意见通过当前范围 JSON 提交。
|
|
108298
|
+
`);
|
|
108299
|
+
}
|
|
108300
|
+
async function readLegacyReviewPayload(raw, projectRoot) {
|
|
108301
|
+
warnLegacyReview("Legacy review input");
|
|
108302
|
+
try {
|
|
108303
|
+
const feedback = raw.trim().startsWith("CR2.") ? createReviewFeedbackCodec().decode(raw) : undefined;
|
|
108304
|
+
const decoded = feedback ?? createReviewCodeCodec().decode(raw);
|
|
108305
|
+
const collection = decoded.scope === "all" ? undefined : assertCollection(decoded.scope);
|
|
108306
|
+
const rows = (await readCandidateRecords(projectRoot)).filter((row) => row.status === "draft" && (collection === undefined || row.collection === collection)).sort((a, b) => a.candidate_id < b.candidate_id ? -1 : a.candidate_id > b.candidate_id ? 1 : 0);
|
|
108307
|
+
return {
|
|
108308
|
+
...collection === undefined ? {} : { collection },
|
|
108309
|
+
...feedback === undefined ? {} : { baseline_hash: feedback.baselineHash },
|
|
108310
|
+
scope: {
|
|
108311
|
+
kind: collection === undefined ? "all" : "collection",
|
|
108312
|
+
...collection === undefined ? {} : { collection },
|
|
108313
|
+
count: decoded.statuses.length,
|
|
108314
|
+
ids_sha256: decoded.idsHash,
|
|
108315
|
+
candidates_sha256: decoded.contentHash,
|
|
108316
|
+
visible_candidate_ids: rows.map((row) => row.candidate_id)
|
|
108317
|
+
},
|
|
108318
|
+
decisions: rows.flatMap((row, index2) => {
|
|
108319
|
+
const status = decoded.statuses[index2];
|
|
108320
|
+
return status === "approved" || status === "rejected" ? [{ candidate_id: row.candidate_id, status }] : [];
|
|
108321
|
+
}),
|
|
108322
|
+
repairs: (feedback?.repairs ?? []).map((repair) => ({
|
|
108323
|
+
candidate_id: rows[repair.index]?.candidate_id ?? "legacy-stale-candidate",
|
|
108324
|
+
instruction: repair.instruction
|
|
108325
|
+
}))
|
|
108326
|
+
};
|
|
108327
|
+
} catch (error) {
|
|
108328
|
+
throw new ContextError(ExitCode.UserError, error instanceof Error ? error.message : String(error), {
|
|
108329
|
+
category: ErrorCategory.UserInputInvalid,
|
|
108330
|
+
next: "No new code is needed. Return confirmation or revision notes in the conversation; use the current report scope."
|
|
108331
|
+
});
|
|
108332
|
+
}
|
|
108333
|
+
}
|
|
108334
|
+
|
|
107862
108335
|
// src/project/review.ts
|
|
107863
108336
|
init_cliFeedback();
|
|
107864
108337
|
init_errors3();
|
|
107865
108338
|
init_exitCode();
|
|
107866
|
-
import { readFile as
|
|
108339
|
+
import { readFile as readFile81 } from "node:fs/promises";
|
|
107867
108340
|
import { isAbsolute as isAbsolute19, resolve as resolve31 } from "node:path";
|
|
107868
108341
|
|
|
107869
108342
|
// src/project/reviewApply.ts
|
|
@@ -107880,8 +108353,8 @@ init_durableMultiFileTransaction();
|
|
|
107880
108353
|
init_durableSingleFileTransaction();
|
|
107881
108354
|
init_reviewShared();
|
|
107882
108355
|
import { existsSync as existsSync25 } from "node:fs";
|
|
107883
|
-
import { readFile as
|
|
107884
|
-
import { join as
|
|
108356
|
+
import { readFile as readFile79 } from "node:fs/promises";
|
|
108357
|
+
import { join as join101 } from "node:path";
|
|
107885
108358
|
function existingTimestamp(markdown) {
|
|
107886
108359
|
if (markdown === undefined)
|
|
107887
108360
|
return;
|
|
@@ -107908,7 +108381,7 @@ async function prepareApprovedPage(input) {
|
|
|
107908
108381
|
next: "Refresh the current production or article revision, then reopen Review before approval."
|
|
107909
108382
|
});
|
|
107910
108383
|
}
|
|
107911
|
-
const relPath =
|
|
108384
|
+
const relPath = join101("knowledge", input.record.path);
|
|
107912
108385
|
const existingView = findApprovedPageForArticleId(input.record.indexer_candidate.artifact_ref, input.approvedPageIndex);
|
|
107913
108386
|
const previousPath = input.record.approved_revision?.previous_path;
|
|
107914
108387
|
if (previousPath !== undefined && (!isSafeKnowledgeTargetPath(previousPath.split("/")[0], previousPath) || previousPath.includes("\\")))
|
|
@@ -107923,13 +108396,13 @@ async function prepareApprovedPage(input) {
|
|
|
107923
108396
|
next: "Resolve the approved page path migration explicitly before approving this candidate."
|
|
107924
108397
|
});
|
|
107925
108398
|
}
|
|
107926
|
-
const absPath =
|
|
107927
|
-
const existing = existsSync25(absPath) ? await
|
|
108399
|
+
const absPath = join101(input.projectRoot, relPath);
|
|
108400
|
+
const existing = existsSync25(absPath) ? await readFile79(absPath, "utf8") : undefined;
|
|
107928
108401
|
let previous3;
|
|
107929
108402
|
if (previousPath !== undefined) {
|
|
107930
108403
|
if (existing !== undefined || existingView?.relPath !== `knowledge/${previousPath}`)
|
|
107931
108404
|
throw new TypeError("Page move destination or original identity changed; refresh its revision.");
|
|
107932
|
-
previous3 = { path: `knowledge/${previousPath}`, content: await
|
|
108405
|
+
previous3 = { path: `knowledge/${previousPath}`, content: await readFile79(join101(input.projectRoot, "knowledge", previousPath), "utf8") };
|
|
107933
108406
|
}
|
|
107934
108407
|
if (input.record.approved_revision !== undefined) {
|
|
107935
108408
|
const base = previous3?.content ?? existing;
|
|
@@ -107976,7 +108449,7 @@ async function prepareApprovedPage(input) {
|
|
|
107976
108449
|
}
|
|
107977
108450
|
async function readProjectFileMaybe(projectRoot, relPath) {
|
|
107978
108451
|
try {
|
|
107979
|
-
return await
|
|
108452
|
+
return await readFile79(join101(projectRoot, relPath), "utf8");
|
|
107980
108453
|
} catch (error) {
|
|
107981
108454
|
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
107982
108455
|
return;
|
|
@@ -108005,31 +108478,19 @@ function reviewFileTarget(input) {
|
|
|
108005
108478
|
content: input.targetContent
|
|
108006
108479
|
};
|
|
108007
108480
|
}
|
|
108008
|
-
function expandEncodedReviewDecisions(payload, scopedIds) {
|
|
108009
|
-
if (payload.scope?.candidates_sha256 === undefined || payload.encoded_statuses.length !== scopedIds.length || payload.decisions.length !== 0 || payload.default !== undefined || payload.encoded_statuses.some((status) => status !== "approved" && status !== "rejected" && status !== "pending")) {
|
|
108010
|
-
throw new ContextError(ExitCode.UserError, "Invalid scoped review code decisions", {
|
|
108011
|
-
category: ErrorCategory.UserInputInvalid,
|
|
108012
|
-
next: "Copy a fresh complete review code from the current HTML report."
|
|
108013
|
-
});
|
|
108014
|
-
}
|
|
108015
|
-
return scopedIds.flatMap((candidate_id, index2) => {
|
|
108016
|
-
const status = payload.encoded_statuses[index2];
|
|
108017
|
-
return status === "pending" ? [] : [{ candidate_id, status }];
|
|
108018
|
-
});
|
|
108019
|
-
}
|
|
108020
108481
|
function expandReviewPayload(payload, rows) {
|
|
108021
108482
|
const scopedRows = payload.scope?.kind === "all" ? rows.filter((row) => row.status === "draft") : payload.collection !== undefined ? rows.filter((row) => row.collection === payload.collection && row.status === "draft") : [];
|
|
108022
108483
|
const scopedIds = scopedRows.map((row) => row.candidate_id).sort();
|
|
108023
108484
|
if (payload.scope === undefined) {
|
|
108024
108485
|
throw new ContextError(ExitCode.UserError, "review payload requires an explicit scope from the current review gate", {
|
|
108025
108486
|
category: ErrorCategory.UserInputInvalid,
|
|
108026
|
-
next: payload.collection === undefined ? "Rerun context review html --all --format json and
|
|
108487
|
+
next: payload.collection === undefined ? "Rerun context review html --all --format json and use its current scope template." : `Rerun context review html ${payload.collection} --format json and use its current scope template.`
|
|
108027
108488
|
});
|
|
108028
108489
|
}
|
|
108029
108490
|
const actualHash = candidateIdsHash(scopedIds);
|
|
108030
108491
|
const actualCandidatesHash = candidateSetHash(scopedRows);
|
|
108031
108492
|
const visibleIds = payload.scope.visible_candidate_ids;
|
|
108032
|
-
if (payload.scope.kind === "all" && visibleIds === undefined
|
|
108493
|
+
if (payload.scope.kind === "all" && visibleIds === undefined) {
|
|
108033
108494
|
throw new ContextError(ExitCode.UserError, "all-scope review payload requires scope.visible_candidate_ids", {
|
|
108034
108495
|
category: ErrorCategory.UserInputInvalid,
|
|
108035
108496
|
expected: {
|
|
@@ -108037,7 +108498,7 @@ function expandReviewPayload(payload, rows) {
|
|
|
108037
108498
|
ids_sha256: actualHash,
|
|
108038
108499
|
visible_candidate_ids: scopedIds
|
|
108039
108500
|
},
|
|
108040
|
-
next: "Rerun context review html --all --format json and
|
|
108501
|
+
next: "Rerun context review html --all --format json and use its current scope template."
|
|
108041
108502
|
});
|
|
108042
108503
|
}
|
|
108043
108504
|
const visibleMismatch = visibleIds !== undefined && (visibleIds.length !== scopedIds.length || visibleIds.some((id3, index2) => id3 !== scopedIds[index2]));
|
|
@@ -108053,11 +108514,9 @@ function expandReviewPayload(payload, rows) {
|
|
|
108053
108514
|
candidates_sha256: actualCandidatesHash,
|
|
108054
108515
|
visible_candidate_ids: scopedIds
|
|
108055
108516
|
},
|
|
108056
|
-
next: `Rerun context review html ${scopeLabel} and
|
|
108517
|
+
next: `Rerun context review html ${scopeLabel} and use its current scope template.`
|
|
108057
108518
|
});
|
|
108058
108519
|
}
|
|
108059
|
-
if (payload.encoded_statuses !== undefined)
|
|
108060
|
-
return expandEncodedReviewDecisions(payload, scopedIds);
|
|
108061
108520
|
if (payload.default === undefined) {
|
|
108062
108521
|
const scopedSet = new Set(scopedIds);
|
|
108063
108522
|
for (const decision of payload.decisions) {
|
|
@@ -108107,6 +108566,14 @@ async function applyReviewDecisions(input) {
|
|
|
108107
108566
|
const nextRows = [...rows];
|
|
108108
108567
|
const decisions = expandReviewPayload(input.payload, rows);
|
|
108109
108568
|
const scoped = rows.filter((r) => r.status === "draft" && (input.payload.scope?.kind === "all" || r.collection === input.payload.collection)).sort((a, b) => a.candidate_id < b.candidate_id ? -1 : 1);
|
|
108569
|
+
const pendingFeedback = await readPendingReviewFeedback(input.projectRoot, scoped.map((record4) => ({ record: record4, snapshot: undefined })));
|
|
108570
|
+
const unresolved = pendingFeedback.filter((repair) => decisions.some((decision) => decision.candidate_id === repair.candidate_id && decision.status === "approved"));
|
|
108571
|
+
if (unresolved.length) {
|
|
108572
|
+
throw new ContextError(ExitCode.UserError, "Pending revision instructions must be resolved before approval", {
|
|
108573
|
+
category: ErrorCategory.UserInputInvalid,
|
|
108574
|
+
repairs: unresolved
|
|
108575
|
+
});
|
|
108576
|
+
}
|
|
108110
108577
|
if (input.payload.baseline_hash && input.payload.baseline_hash !== await reviewSiteBaselineHash(input.projectRoot, scoped.map((r) => r.approved_revision?.previous_path ?? r.path))) {
|
|
108111
108578
|
throw new ContextError(ExitCode.WorkspaceStateError, "Review navigation or approved baseline changed; generate a fresh report", {
|
|
108112
108579
|
category: ErrorCategory.WorkspaceStateInvalid,
|
|
@@ -108115,11 +108582,15 @@ async function applyReviewDecisions(input) {
|
|
|
108115
108582
|
});
|
|
108116
108583
|
}
|
|
108117
108584
|
const repairIds = new Set;
|
|
108118
|
-
const repairs = (input.payload.
|
|
108119
|
-
const row = scoped
|
|
108120
|
-
if (!
|
|
108121
|
-
throw new
|
|
108122
|
-
|
|
108585
|
+
const repairs = (input.payload.repairs ?? []).map((repair) => {
|
|
108586
|
+
const row = scoped.find((candidate) => candidate.candidate_id === repair.candidate_id);
|
|
108587
|
+
if (!row || repairIds.has(repair.candidate_id) || !repair.instruction.trim() || decisions.some((decision) => decision.candidate_id === repair.candidate_id)) {
|
|
108588
|
+
throw new ContextError(ExitCode.UserError, "Revision requests must be unique, scoped and separate from decisions", {
|
|
108589
|
+
category: ErrorCategory.UserInputInvalid,
|
|
108590
|
+
candidate_id: repair.candidate_id
|
|
108591
|
+
});
|
|
108592
|
+
}
|
|
108593
|
+
repairIds.add(repair.candidate_id);
|
|
108123
108594
|
const quote = (value) => "'" + value.replace(/'/gu, "'\\''") + "'";
|
|
108124
108595
|
return {
|
|
108125
108596
|
candidate_id: row.candidate_id,
|
|
@@ -108199,7 +108670,7 @@ async function applyReviewDecisions(input) {
|
|
|
108199
108670
|
});
|
|
108200
108671
|
}
|
|
108201
108672
|
seenApprovedIds.set(approvedRef, row.candidate_id);
|
|
108202
|
-
const approvedPath =
|
|
108673
|
+
const approvedPath = join101("knowledge", row.path);
|
|
108203
108674
|
const previousPathCandidate = seenApprovedPaths.get(knowledgeTargetPathKey(approvedPath));
|
|
108204
108675
|
if (previousPathCandidate !== undefined) {
|
|
108205
108676
|
throw new ContextError(ExitCode.UserError, `multiple approved review decisions target the same knowledge path: ${approvedPath}`, {
|
|
@@ -108249,7 +108720,7 @@ async function applyReviewDecisions(input) {
|
|
|
108249
108720
|
for (const path3 of approvedPageIndex.byRelPath.keys()) {
|
|
108250
108721
|
if (pagesToWrite.some((page) => page.relPath === path3 || page.previous?.path === path3))
|
|
108251
108722
|
continue;
|
|
108252
|
-
const before = await
|
|
108723
|
+
const before = await readFile79(join101(input.projectRoot, path3), "utf8");
|
|
108253
108724
|
const local = path3.replace(/^knowledge\//u, "");
|
|
108254
108725
|
const after = moveKnowledgeLinkTargets2(before, local, local, moved);
|
|
108255
108726
|
navigationTargets.push(reviewFileTarget({ path: path3, baseContent: before, targetContent: after }));
|
|
@@ -108308,11 +108779,11 @@ async function applyReviewDecisions(input) {
|
|
|
108308
108779
|
init_reviewShared();
|
|
108309
108780
|
init_writeLock();
|
|
108310
108781
|
init_verifyFrontmatter();
|
|
108311
|
-
import { readFile as
|
|
108782
|
+
import { readFile as readFile80, writeFile as writeFile28 } from "node:fs/promises";
|
|
108312
108783
|
function deprecateApprovedPage(input) {
|
|
108313
108784
|
return withProjectWriteLock(input.projectRoot, "deprecate-article", async () => {
|
|
108314
108785
|
const page = await approvedPageForArticleId(input.projectRoot, input.viewRef);
|
|
108315
|
-
const original = await
|
|
108786
|
+
const original = await readFile80(page.path, "utf8");
|
|
108316
108787
|
const content3 = parseFrontmatterLoose(original).deprecated === true ? original : updateFrontmatter(original, (metadata) => ({ ...metadata, deprecated: true, timestamp: new Date().toISOString() }));
|
|
108317
108788
|
const changed = content3 !== original;
|
|
108318
108789
|
if (changed)
|
|
@@ -108333,12 +108804,12 @@ init_candidateLedger();
|
|
|
108333
108804
|
|
|
108334
108805
|
// src/project/localHtmlReport.ts
|
|
108335
108806
|
import { execFile as execFile11 } from "node:child_process";
|
|
108336
|
-
import { isAbsolute as isAbsolute18, join as
|
|
108807
|
+
import { isAbsolute as isAbsolute18, join as join102 } from "node:path";
|
|
108337
108808
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
108338
108809
|
import { promisify as promisify11 } from "node:util";
|
|
108339
108810
|
var execFileAsync5 = promisify11(execFile11);
|
|
108340
108811
|
function htmlReportReference(input) {
|
|
108341
|
-
const absolutePath = isAbsolute18(input.path) ? input.path :
|
|
108812
|
+
const absolutePath = isAbsolute18(input.path) ? input.path : join102(input.projectRoot, input.path);
|
|
108342
108813
|
return {
|
|
108343
108814
|
format: "html",
|
|
108344
108815
|
path: input.path,
|
|
@@ -108442,7 +108913,7 @@ function parsePayloadValues(parsed) {
|
|
|
108442
108913
|
if (first.schema !== REVIEW_PAYLOAD_SCHEMA) {
|
|
108443
108914
|
throw new ContextError(ExitCode.UserError, `review payload header schema must be ${REVIEW_PAYLOAD_SCHEMA}`, {
|
|
108444
108915
|
category: ErrorCategory.UserInputInvalid,
|
|
108445
|
-
next: "
|
|
108916
|
+
next: "Use the current scope template from the review resource."
|
|
108446
108917
|
});
|
|
108447
108918
|
}
|
|
108448
108919
|
const scope2 = parsePayloadScope(first.scope);
|
|
@@ -108470,7 +108941,18 @@ function parsePayloadValues(parsed) {
|
|
|
108470
108941
|
}
|
|
108471
108942
|
const decisions = first.decisions ?? parsed.slice(1);
|
|
108472
108943
|
const validatedDecisions = decisions.map((item, index2) => parsePayloadLineDecision(item, index2 + 2));
|
|
108944
|
+
const repairs = first.repairs;
|
|
108945
|
+
if (repairs !== undefined && (!Array.isArray(repairs) || repairs.some((item) => !isRecord14(item) || typeof item.candidate_id !== "string" || !item.candidate_id.trim() || typeof item.instruction !== "string" || !item.instruction.trim()))) {
|
|
108946
|
+
throw new ContextError(ExitCode.UserError, "review repairs require candidate_id and nonempty instruction", {
|
|
108947
|
+
category: ErrorCategory.UserInputInvalid
|
|
108948
|
+
});
|
|
108949
|
+
}
|
|
108950
|
+
if (first.baseline_hash !== undefined && (typeof first.baseline_hash !== "string" || !/^[a-f0-9]{64}$/iu.test(first.baseline_hash))) {
|
|
108951
|
+
throw new ContextError(ExitCode.UserError, "review baseline_hash must be a SHA-256 digest", { category: ErrorCategory.UserInputInvalid });
|
|
108952
|
+
}
|
|
108473
108953
|
return {
|
|
108954
|
+
...Array.isArray(repairs) ? { repairs } : {},
|
|
108955
|
+
...typeof first.baseline_hash === "string" ? { baseline_hash: first.baseline_hash.toLowerCase() } : {},
|
|
108474
108956
|
decisions: validatedDecisions,
|
|
108475
108957
|
...typeof first.note === "string" ? { note: first.note } : {},
|
|
108476
108958
|
...collection !== undefined ? { collection } : {},
|
|
@@ -108507,59 +108989,21 @@ function parseReviewPayloadText(raw) {
|
|
|
108507
108989
|
throw error;
|
|
108508
108990
|
}
|
|
108509
108991
|
}
|
|
108510
|
-
async function readReviewPayloadFile(filePath2) {
|
|
108992
|
+
async function readReviewPayloadFile(filePath2, projectRoot) {
|
|
108511
108993
|
let raw;
|
|
108512
108994
|
try {
|
|
108513
|
-
raw = await
|
|
108995
|
+
raw = await readFile81(filePath2, "utf8");
|
|
108514
108996
|
} catch (error) {
|
|
108515
108997
|
const message = error instanceof Error ? error.message : String(error);
|
|
108516
108998
|
throw new ContextError(ExitCode.UserError, `review payload file cannot be read: ${filePath2}`, {
|
|
108517
108999
|
category: ErrorCategory.UserInputInvalid,
|
|
108518
109000
|
path: filePath2,
|
|
108519
109001
|
reason: message,
|
|
108520
|
-
next: "Pass the JSON or JSONL
|
|
109002
|
+
next: "Pass the scoped JSON or JSONL decisions prepared from the current review resource."
|
|
108521
109003
|
});
|
|
108522
109004
|
}
|
|
108523
|
-
if (raw.trim()
|
|
108524
|
-
|
|
108525
|
-
if (raw.trim().startsWith("CR2.")) {
|
|
108526
|
-
const feedback = createReviewFeedbackCodec().decode(raw);
|
|
108527
|
-
const collection2 = feedback.scope === "all" ? undefined : assertCollection(feedback.scope);
|
|
108528
|
-
return {
|
|
108529
|
-
decisions: [],
|
|
108530
|
-
encoded_statuses: feedback.statuses.map((s) => s === "revised" ? "pending" : s),
|
|
108531
|
-
feedback_repairs: feedback.repairs,
|
|
108532
|
-
baseline_hash: feedback.baselineHash,
|
|
108533
|
-
...collection2 === undefined ? {} : { collection: collection2 },
|
|
108534
|
-
scope: {
|
|
108535
|
-
kind: collection2 === undefined ? "all" : "collection",
|
|
108536
|
-
...collection2 === undefined ? {} : { collection: collection2 },
|
|
108537
|
-
count: feedback.statuses.length,
|
|
108538
|
-
ids_sha256: feedback.idsHash,
|
|
108539
|
-
candidates_sha256: feedback.contentHash
|
|
108540
|
-
}
|
|
108541
|
-
};
|
|
108542
|
-
}
|
|
108543
|
-
const decoded = createReviewCodeCodec().decode(raw);
|
|
108544
|
-
const collection = decoded.scope === "all" ? undefined : assertCollection(decoded.scope);
|
|
108545
|
-
return {
|
|
108546
|
-
decisions: [],
|
|
108547
|
-
encoded_statuses: decoded.statuses,
|
|
108548
|
-
...collection === undefined ? {} : { collection },
|
|
108549
|
-
scope: {
|
|
108550
|
-
kind: collection === undefined ? "all" : "collection",
|
|
108551
|
-
...collection === undefined ? {} : { collection },
|
|
108552
|
-
count: decoded.count,
|
|
108553
|
-
ids_sha256: decoded.idsHash,
|
|
108554
|
-
candidates_sha256: decoded.contentHash
|
|
108555
|
-
}
|
|
108556
|
-
};
|
|
108557
|
-
} catch (error) {
|
|
108558
|
-
throw new ContextError(ExitCode.UserError, error instanceof Error ? error.message : String(error), {
|
|
108559
|
-
category: ErrorCategory.UserInputInvalid,
|
|
108560
|
-
next: "Copy the complete review code and all following revision instruction lines unchanged from the current report into one input file. Older segmented codes require every segment."
|
|
108561
|
-
});
|
|
108562
|
-
}
|
|
109005
|
+
if (/^CR(?:[12]|P1)\./u.test(raw.trim())) {
|
|
109006
|
+
return readLegacyReviewPayload(raw, projectRoot ?? projectRootFromCwd(process.cwd()));
|
|
108563
109007
|
}
|
|
108564
109008
|
return parseReviewPayloadText(raw);
|
|
108565
109009
|
}
|
|
@@ -108697,7 +109141,7 @@ async function runReviewHtmlCommand(input) {
|
|
|
108697
109141
|
...openResult.error !== undefined ? [`open_error: ${openResult.error}`] : []
|
|
108698
109142
|
] : []
|
|
108699
109143
|
],
|
|
108700
|
-
next: result.next_action ? `${result.next_action.message} ${result.next_action.command}` : "
|
|
109144
|
+
next: result.next_action ? `${result.next_action.message} ${result.next_action.command}` : "Return to the conversation to confirm or provide revision notes."
|
|
108701
109145
|
}));
|
|
108702
109146
|
}
|
|
108703
109147
|
async function runReviewListCommand(input) {
|
|
@@ -108750,41 +109194,45 @@ async function runReviewListCommand(input) {
|
|
|
108750
109194
|
async function runReviewApplyCommand(input) {
|
|
108751
109195
|
const projectRoot = projectRootFromCwd(input.cwd);
|
|
108752
109196
|
const payloadPath = isAbsolute19(input.payloadInput) ? input.payloadInput : resolve31(input.cwd, input.payloadInput);
|
|
108753
|
-
const payload = await readReviewPayloadFile(payloadPath);
|
|
109197
|
+
const payload = await readReviewPayloadFile(payloadPath, projectRoot);
|
|
108754
109198
|
const result = await applyReviewDecisions({ projectRoot, payload });
|
|
108755
109199
|
const continuation = await input.afterApply?.(projectRoot);
|
|
108756
109200
|
process.stdout.write(formatApplyResult({ ...result, ...continuation === undefined ? {} : { continuation } }, input.format ?? "text"));
|
|
108757
109201
|
}
|
|
108758
109202
|
async function runReviewApproveAllCommand(input) {
|
|
108759
|
-
if (input.managed === true && input.force === true) {
|
|
108760
|
-
throw new ContextError(ExitCode.UserError, "review approve-all accepts either --managed or --
|
|
109203
|
+
if (input.managed === true && (input.confirmed === true || input.force === true)) {
|
|
109204
|
+
throw new ContextError(ExitCode.UserError, "review approve-all accepts either --managed or --confirmed, not both", {
|
|
108761
109205
|
category: ErrorCategory.UserInputInvalid,
|
|
108762
109206
|
code: "review-approval-authority-conflict",
|
|
108763
109207
|
next: "context status --format json"
|
|
108764
109208
|
});
|
|
108765
109209
|
}
|
|
108766
|
-
if (input.managed !== true && input.force !== true) {
|
|
108767
|
-
throw new ContextError(ExitCode.UserError, "review approve-all requires explicit --managed authority or --
|
|
109210
|
+
if (input.managed !== true && input.confirmed !== true && input.force !== true) {
|
|
109211
|
+
throw new ContextError(ExitCode.UserError, "review approve-all requires explicit --managed authority or --confirmed user confirmation", {
|
|
108768
109212
|
category: ErrorCategory.UserInputInvalid,
|
|
108769
109213
|
code: "review-approval-authority-required",
|
|
108770
109214
|
next: "context status --format json"
|
|
108771
109215
|
});
|
|
108772
109216
|
}
|
|
108773
|
-
|
|
109217
|
+
if (input.force === true)
|
|
109218
|
+
warnLegacyReview("--force");
|
|
109219
|
+
const decisionSource = input.managed === true ? "managed-session" : "explicit-user-confirmation";
|
|
108774
109220
|
const projectRoot = projectRootFromCwd(input.cwd);
|
|
108775
109221
|
const scoped = await reviewCommandScope({
|
|
108776
109222
|
projectRoot,
|
|
108777
109223
|
...input.collection !== undefined ? { collection: input.collection } : {},
|
|
108778
109224
|
...input.all === true ? { all: true } : {}
|
|
108779
109225
|
});
|
|
109226
|
+
const confirmedScope = input.confirmed === true ? await readConfirmedReviewScope(projectRoot, scoped.collection ?? "all") : undefined;
|
|
108780
109227
|
const result = await applyReviewDecisions({
|
|
108781
109228
|
projectRoot,
|
|
108782
109229
|
payload: {
|
|
109230
|
+
...confirmedScope === undefined ? {} : { baseline_hash: confirmedScope.baseline_hash },
|
|
108783
109231
|
decisions: [],
|
|
108784
109232
|
default: "approved",
|
|
108785
|
-
note: input.managed === true ? "managed-session auto approval" : "explicit user
|
|
109233
|
+
note: input.managed === true ? "managed-session auto approval" : "explicit user confirmation",
|
|
108786
109234
|
...scoped.collection !== undefined ? { collection: scoped.collection } : {},
|
|
108787
|
-
scope: scoped.scope
|
|
109235
|
+
scope: confirmedScope?.scope ?? scoped.scope
|
|
108788
109236
|
}
|
|
108789
109237
|
});
|
|
108790
109238
|
const continuation = await input.afterApply?.(projectRoot);
|
|
@@ -108818,7 +109266,7 @@ async function runReviewApproveAllCommand(input) {
|
|
|
108818
109266
|
process.stdout.write(formatFeedback({
|
|
108819
109267
|
symbol: "✓",
|
|
108820
109268
|
action: "approved",
|
|
108821
|
-
subject: input.managed === true ? "managed review batch" : "
|
|
109269
|
+
subject: input.managed === true ? "managed review batch" : "confirmed review batch",
|
|
108822
109270
|
headline: `${result.approved} candidate(s) approved`,
|
|
108823
109271
|
body: [
|
|
108824
109272
|
`decision source: ${decisionSource}`,
|
|
@@ -108987,7 +109435,7 @@ function supportsCommand(command2) {
|
|
|
108987
109435
|
return options !== undefined && options.positionals.length === 0 && outputFormat(options) !== undefined;
|
|
108988
109436
|
}
|
|
108989
109437
|
if (topLevel === "review" && subcommand === "approve-all") {
|
|
108990
|
-
const options = parseOptions(command2.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--
|
|
109438
|
+
const options = parseOptions(command2.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--confirmed", "--verbose"]));
|
|
108991
109439
|
return options !== undefined && options.positionals.length <= 1 && !(options.flags.has("--all") && options.positionals.length === 1) && outputFormat(options) !== undefined;
|
|
108992
109440
|
}
|
|
108993
109441
|
if (topLevel === "package" && subcommand === "template" && command2[2] === "accept") {
|
|
@@ -109026,7 +109474,7 @@ async function executeBuild(cwd, command2) {
|
|
|
109026
109474
|
});
|
|
109027
109475
|
}
|
|
109028
109476
|
async function executeReviewApproveAll(cwd, invocation) {
|
|
109029
|
-
const options = parseOptions(invocation.command.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--
|
|
109477
|
+
const options = parseOptions(invocation.command.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--confirmed", "--verbose"]));
|
|
109030
109478
|
if (options === undefined || options.positionals.length > 1)
|
|
109031
109479
|
throw new Error("unsupported review approve-all command");
|
|
109032
109480
|
await runReviewApproveAllCommand({
|
|
@@ -109034,7 +109482,7 @@ async function executeReviewApproveAll(cwd, invocation) {
|
|
|
109034
109482
|
...options.positionals[0] === undefined ? {} : { collection: options.positionals[0] },
|
|
109035
109483
|
...options.flags.has("--all") ? { all: true } : {},
|
|
109036
109484
|
managed: invocation.managed || options.flags.has("--managed"),
|
|
109037
|
-
|
|
109485
|
+
confirmed: options.flags.has("--confirmed"),
|
|
109038
109486
|
verbose: options.flags.has("--verbose"),
|
|
109039
109487
|
format: outputFormat(options) ?? "text"
|
|
109040
109488
|
});
|
|
@@ -109362,8 +109810,8 @@ init_exitCode();
|
|
|
109362
109810
|
init_maintenanceStorage();
|
|
109363
109811
|
init_productionFeedback();
|
|
109364
109812
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
109365
|
-
import { readFile as
|
|
109366
|
-
import { join as
|
|
109813
|
+
import { readFile as readFile82 } from "node:fs/promises";
|
|
109814
|
+
import { join as join103 } from "node:path";
|
|
109367
109815
|
async function beginProductionRevision(input) {
|
|
109368
109816
|
return withProductionFeedback({ operation: "revision" }, () => withProjectWriteLock(input.projectRoot, "production-revision", async () => {
|
|
109369
109817
|
await recoverDurableMultiFileTransactions(input.projectRoot);
|
|
@@ -109402,7 +109850,7 @@ async function beginProductionRevision(input) {
|
|
|
109402
109850
|
const formal = approved.byPath.get(path3);
|
|
109403
109851
|
if (!prior && !formal)
|
|
109404
109852
|
throw invalid2("Write the current task first; there is no article draft to revise yet.");
|
|
109405
|
-
const markdown = prior?.body ?? await
|
|
109853
|
+
const markdown = prior?.body ?? await readFile82(await safeProjectTarget(input.projectRoot, join103("knowledge", path3)), "utf8");
|
|
109406
109854
|
const sections = prior?.indexer_candidate.sections.map((section) => ({ id: section.section_key, references: section.references })) ?? formal?.sections;
|
|
109407
109855
|
const sources = [];
|
|
109408
109856
|
for (const source2 of owner.sources) {
|
|
@@ -109579,7 +110027,7 @@ init_cliFeedback();
|
|
|
109579
110027
|
init_errors3();
|
|
109580
110028
|
init_exitCode();
|
|
109581
110029
|
var import_yaml41 = __toESM(require_dist(), 1);
|
|
109582
|
-
import { readFile as
|
|
110030
|
+
import { readFile as readFile83 } from "node:fs/promises";
|
|
109583
110031
|
function userInputError2(message, detail = {}) {
|
|
109584
110032
|
return new ContextError(ExitCode.UserError, message, {
|
|
109585
110033
|
category: ErrorCategory.UserInputInvalid,
|
|
@@ -109648,7 +110096,7 @@ async function readPayloadTextFromStdin(stdin) {
|
|
|
109648
110096
|
}
|
|
109649
110097
|
async function readPayloadText(path3) {
|
|
109650
110098
|
if (path3 !== "-")
|
|
109651
|
-
return
|
|
110099
|
+
return readFile83(path3, "utf8");
|
|
109652
110100
|
return readPayloadTextFromStdin(process.stdin);
|
|
109653
110101
|
}
|
|
109654
110102
|
async function readYamlOrJsonInput(input) {
|
|
@@ -110042,7 +110490,7 @@ init_atomicWrite();
|
|
|
110042
110490
|
var import_yaml44 = __toESM(require_dist(), 1);
|
|
110043
110491
|
import { Buffer as Buffer5 } from "node:buffer";
|
|
110044
110492
|
import { createHash as createHash32 } from "node:crypto";
|
|
110045
|
-
import { join as
|
|
110493
|
+
import { join as join107 } from "node:path";
|
|
110046
110494
|
var INLINE_LIMIT = 16 * 1024;
|
|
110047
110495
|
function record4(value) {
|
|
110048
110496
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
@@ -110068,8 +110516,8 @@ async function prepareActionCompletionOutput(input) {
|
|
|
110068
110516
|
if (production && Buffer5.byteLength(full) <= INLINE_LIMIT)
|
|
110069
110517
|
return input.result;
|
|
110070
110518
|
const digest7 = createHash32("sha256").update(full).digest("hex");
|
|
110071
|
-
const root2 =
|
|
110072
|
-
const resultFile =
|
|
110519
|
+
const root2 = join107(input.projectRoot, ".tmp/context-runtime/action-results");
|
|
110520
|
+
const resultFile = join107(root2, `${digest7}.json`);
|
|
110073
110521
|
await atomicWriteFile(resultFile, full);
|
|
110074
110522
|
if (production)
|
|
110075
110523
|
return {
|
|
@@ -110488,7 +110936,7 @@ init_cliFeedback();
|
|
|
110488
110936
|
import { existsSync as existsSync27 } from "node:fs";
|
|
110489
110937
|
import { readdir as readdir27, rm as rm23 } from "node:fs/promises";
|
|
110490
110938
|
import { homedir } from "node:os";
|
|
110491
|
-
import { join as
|
|
110939
|
+
import { join as join108 } from "node:path";
|
|
110492
110940
|
var ORPHAN_MARKER = ".orphaned_at";
|
|
110493
110941
|
var CLAUDE_PLUGIN_CACHE_ROOT_ENV = "C4A_CLAUDE_PLUGIN_CACHE_ROOT";
|
|
110494
110942
|
var CLAUDE_PLUGIN_CACHE_HOME_ENV = "C4A_CLAUDE_PLUGIN_CACHE_HOME";
|
|
@@ -110505,19 +110953,19 @@ async function cleanClaudePluginCache(opts = {}) {
|
|
|
110505
110953
|
for (const mp of marketplaces) {
|
|
110506
110954
|
if (!mp.isDirectory())
|
|
110507
110955
|
continue;
|
|
110508
|
-
const mpDir =
|
|
110956
|
+
const mpDir = join108(cacheRoot, mp.name);
|
|
110509
110957
|
const plugins = await readdir27(mpDir, { withFileTypes: true });
|
|
110510
110958
|
for (const pl of plugins) {
|
|
110511
110959
|
if (!pl.isDirectory())
|
|
110512
110960
|
continue;
|
|
110513
|
-
const plDir =
|
|
110961
|
+
const plDir = join108(mpDir, pl.name);
|
|
110514
110962
|
const versions = await readdir27(plDir, { withFileTypes: true });
|
|
110515
110963
|
for (const ver of versions) {
|
|
110516
110964
|
if (!ver.isDirectory())
|
|
110517
110965
|
continue;
|
|
110518
110966
|
scanned += 1;
|
|
110519
|
-
const verDir =
|
|
110520
|
-
const markerPath =
|
|
110967
|
+
const verDir = join108(plDir, ver.name);
|
|
110968
|
+
const markerPath = join108(verDir, ORPHAN_MARKER);
|
|
110521
110969
|
if (!existsSync27(markerPath))
|
|
110522
110970
|
continue;
|
|
110523
110971
|
const label2 = `${mp.name}/${pl.name}/${ver.name}`;
|
|
@@ -110549,7 +110997,7 @@ function resolveClaudePluginCacheRoot(opts) {
|
|
|
110549
110997
|
if (explicitRoot)
|
|
110550
110998
|
return explicitRoot;
|
|
110551
110999
|
const home = opts.home ?? process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV] ?? homedir();
|
|
110552
|
-
return
|
|
111000
|
+
return join108(home, ".claude", "plugins", "cache");
|
|
110553
111001
|
}
|
|
110554
111002
|
async function isEmptyDir(dir) {
|
|
110555
111003
|
try {
|
|
@@ -110581,13 +111029,13 @@ init_exitCode();
|
|
|
110581
111029
|
|
|
110582
111030
|
// src/lib/packageVersion.ts
|
|
110583
111031
|
import { existsSync as existsSync28, readFileSync as readFileSync9 } from "node:fs";
|
|
110584
|
-
import { dirname as dirname43, join as
|
|
111032
|
+
import { dirname as dirname43, join as join109 } from "node:path";
|
|
110585
111033
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
110586
111034
|
function readPackageVersion() {
|
|
110587
111035
|
try {
|
|
110588
111036
|
let dir = dirname43(fileURLToPath8(import.meta.url));
|
|
110589
111037
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
110590
|
-
const packagePath =
|
|
111038
|
+
const packagePath = join109(dir, "package.json");
|
|
110591
111039
|
if (existsSync28(packagePath)) {
|
|
110592
111040
|
const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
|
|
110593
111041
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
@@ -110610,7 +111058,7 @@ function readPackageVersion() {
|
|
|
110610
111058
|
|
|
110611
111059
|
// src/project/sourceCommands.ts
|
|
110612
111060
|
init_src2();
|
|
110613
|
-
import { readFile as
|
|
111061
|
+
import { readFile as readFile99 } from "node:fs/promises";
|
|
110614
111062
|
import { isAbsolute as isAbsolute23, resolve as resolve41 } from "node:path";
|
|
110615
111063
|
init_cliFeedback();
|
|
110616
111064
|
init_errors3();
|
|
@@ -110973,15 +111421,15 @@ async function restoreRepositorySources(input) {
|
|
|
110973
111421
|
init_src2();
|
|
110974
111422
|
init_documentCapture();
|
|
110975
111423
|
import { existsSync as existsSync30 } from "node:fs";
|
|
110976
|
-
import { readFile as
|
|
110977
|
-
import { join as
|
|
111424
|
+
import { readFile as readFile89 } from "node:fs/promises";
|
|
111425
|
+
import { join as join111 } from "node:path";
|
|
110978
111426
|
|
|
110979
111427
|
// src/project/sourceCommandViews.ts
|
|
110980
111428
|
init_documentSiteDetection();
|
|
110981
111429
|
init_workspace();
|
|
110982
111430
|
init_documentBatchManifest();
|
|
110983
|
-
import { readFile as
|
|
110984
|
-
import { join as
|
|
111431
|
+
import { readFile as readFile88 } from "node:fs/promises";
|
|
111432
|
+
import { join as join110 } from "node:path";
|
|
110985
111433
|
function repoSourceAgentView(source2) {
|
|
110986
111434
|
return {
|
|
110987
111435
|
id: source2.id ?? source2.name,
|
|
@@ -111042,7 +111490,7 @@ async function fileSourceAgentViewWithNextAction(input) {
|
|
|
111042
111490
|
};
|
|
111043
111491
|
}
|
|
111044
111492
|
function documentSourceManifestPath(source2) {
|
|
111045
|
-
return source2.snapshot?.manifest ??
|
|
111493
|
+
return source2.snapshot?.manifest ?? join110(source2.materializedAt, "manifest.json");
|
|
111046
111494
|
}
|
|
111047
111495
|
async function fileSourceDocumentSiteHint(input) {
|
|
111048
111496
|
const detection = await detectDocumentSiteFiles({
|
|
@@ -111052,7 +111500,7 @@ async function fileSourceDocumentSiteHint(input) {
|
|
|
111052
111500
|
let snapshotConfigured = false;
|
|
111053
111501
|
const manifest = documentSourceManifestPath(input.source);
|
|
111054
111502
|
try {
|
|
111055
|
-
const parsed = parseDocumentSnapshotForSource(JSON.parse(await
|
|
111503
|
+
const parsed = parseDocumentSnapshotForSource(JSON.parse(await readFile88(join110(input.projectRoot, manifest), "utf8")), input.source.name);
|
|
111056
111504
|
snapshotConfigured = manifestUsesMdxJsonDocs(parsed);
|
|
111057
111505
|
} catch {
|
|
111058
111506
|
snapshotConfigured = false;
|
|
@@ -111085,11 +111533,11 @@ async function larkSourceAgentViewWithNextAction(input) {
|
|
|
111085
111533
|
init_documentBatchManifest();
|
|
111086
111534
|
init_documentSnapshotFidelity();
|
|
111087
111535
|
function documentSourceManifestPath2(source2) {
|
|
111088
|
-
return source2.snapshot?.manifest ??
|
|
111536
|
+
return source2.snapshot?.manifest ?? join111(source2.materializedAt, "manifest.json");
|
|
111089
111537
|
}
|
|
111090
111538
|
async function documentSnapshotState(input) {
|
|
111091
111539
|
const manifest = documentSourceManifestPath2(input.source);
|
|
111092
|
-
const manifestPath =
|
|
111540
|
+
const manifestPath = join111(input.projectRoot, manifest);
|
|
111093
111541
|
if (!existsSync30(manifestPath)) {
|
|
111094
111542
|
return {
|
|
111095
111543
|
snapshotReady: false,
|
|
@@ -111100,7 +111548,7 @@ async function documentSnapshotState(input) {
|
|
|
111100
111548
|
};
|
|
111101
111549
|
}
|
|
111102
111550
|
try {
|
|
111103
|
-
const parsed = findDocumentSnapshotForSource(JSON.parse(await
|
|
111551
|
+
const parsed = findDocumentSnapshotForSource(JSON.parse(await readFile89(manifestPath, "utf8")), input.source.name);
|
|
111104
111552
|
if (parsed === null) {
|
|
111105
111553
|
return {
|
|
111106
111554
|
snapshotReady: false,
|
|
@@ -111168,7 +111616,7 @@ async function documentSnapshotState(input) {
|
|
|
111168
111616
|
const missing = [
|
|
111169
111617
|
...parsed.files.map((file) => file.path),
|
|
111170
111618
|
...(parsed.assets ?? []).filter((asset) => asset.content_hash !== undefined).map((asset) => asset.path)
|
|
111171
|
-
].find((path3) => !existsSync30(
|
|
111619
|
+
].find((path3) => !existsSync30(join111(input.projectRoot, input.source.materializedAt, path3)));
|
|
111172
111620
|
if (missing !== undefined) {
|
|
111173
111621
|
return {
|
|
111174
111622
|
snapshotReady: false,
|
|
@@ -111269,8 +111717,8 @@ init_errors3();
|
|
|
111269
111717
|
init_exitCode();
|
|
111270
111718
|
var import_yaml47 = __toESM(require_dist(), 1);
|
|
111271
111719
|
import { createHash as createHash33 } from "node:crypto";
|
|
111272
|
-
import { readFile as
|
|
111273
|
-
import { basename as basename13, extname as extname15, isAbsolute as isAbsolute21, join as
|
|
111720
|
+
import { readFile as readFile90, realpath as realpath12 } from "node:fs/promises";
|
|
111721
|
+
import { basename as basename13, extname as extname15, isAbsolute as isAbsolute21, join as join112, relative as relative28, resolve as resolve36 } from "node:path";
|
|
111274
111722
|
init_writeLock();
|
|
111275
111723
|
var SOURCE_NAME_PATTERN3 = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
111276
111724
|
function isDateSourceNamespace(value) {
|
|
@@ -111366,7 +111814,7 @@ function assertSafeFileInclude(value) {
|
|
|
111366
111814
|
}
|
|
111367
111815
|
async function readRegistryDocument(projectRoot, registryPath2) {
|
|
111368
111816
|
try {
|
|
111369
|
-
const content3 = await
|
|
111817
|
+
const content3 = await readFile90(join112(projectRoot, registryPath2), "utf8");
|
|
111370
111818
|
return content3.trim().length === 0 ? { sources: [] } : import_yaml47.default.parse(content3);
|
|
111371
111819
|
} catch (error) {
|
|
111372
111820
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
@@ -111484,7 +111932,7 @@ async function addFileSourceUnlocked(input) {
|
|
|
111484
111932
|
const record6 = entry2;
|
|
111485
111933
|
return record6.name !== input.name && record6.id !== input.name;
|
|
111486
111934
|
}), nextEntry];
|
|
111487
|
-
await atomicWriteFile(
|
|
111935
|
+
await atomicWriteFile(join112(input.projectRoot, DEFAULT_FILE_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
|
|
111488
111936
|
const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
|
|
111489
111937
|
const entry = updated.files.find((source2) => source2.name === input.name || source2.id === input.name);
|
|
111490
111938
|
if (entry === undefined) {
|
|
@@ -111543,7 +111991,7 @@ async function addLarkSourceUnlocked(input) {
|
|
|
111543
111991
|
const record6 = entry2;
|
|
111544
111992
|
return record6.name !== input.name && record6.id !== input.name;
|
|
111545
111993
|
}), nextEntry];
|
|
111546
|
-
await atomicWriteFile(
|
|
111994
|
+
await atomicWriteFile(join112(input.projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
|
|
111547
111995
|
const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
|
|
111548
111996
|
const entry = updated.larks.find((source2) => source2.name === input.name || source2.id === input.name);
|
|
111549
111997
|
if (entry === undefined) {
|
|
@@ -111575,18 +112023,18 @@ init_cliFeedback();
|
|
|
111575
112023
|
init_errors3();
|
|
111576
112024
|
init_exitCode();
|
|
111577
112025
|
var import_yaml48 = __toESM(require_dist(), 1);
|
|
111578
|
-
import { readFile as
|
|
111579
|
-
import { join as
|
|
112026
|
+
import { readFile as readFile91 } from "node:fs/promises";
|
|
112027
|
+
import { join as join113 } from "node:path";
|
|
111580
112028
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
|
|
111581
112029
|
function identityKeys(entry) {
|
|
111582
112030
|
return new Set([entry.name, entry.id]);
|
|
111583
112031
|
}
|
|
111584
112032
|
async function createLarkBatchRegistration(projectRoot, namespace) {
|
|
111585
112033
|
const registry2 = await loadSourcesRegistry({ rootDir: projectRoot });
|
|
111586
|
-
const registryPath2 =
|
|
112034
|
+
const registryPath2 = join113(projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH);
|
|
111587
112035
|
let committedSources;
|
|
111588
112036
|
try {
|
|
111589
|
-
const text10 = await
|
|
112037
|
+
const text10 = await readFile91(registryPath2, "utf8");
|
|
111590
112038
|
committedSources = text10.trim().length === 0 ? [] : import_yaml48.default.parse(text10).sources ?? [];
|
|
111591
112039
|
} catch (error) {
|
|
111592
112040
|
if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
|
|
@@ -111704,8 +112152,8 @@ init_cliFeedback();
|
|
|
111704
112152
|
init_errors3();
|
|
111705
112153
|
init_exitCode();
|
|
111706
112154
|
import { createHash as createHash34 } from "node:crypto";
|
|
111707
|
-
import { lstat as lstat14, readFile as
|
|
111708
|
-
import { join as
|
|
112155
|
+
import { lstat as lstat14, readFile as readFile92 } from "node:fs/promises";
|
|
112156
|
+
import { join as join114, resolve as resolve37 } from "node:path";
|
|
111709
112157
|
var INPUT_SCHEMA = "context.source-batch.input.v1";
|
|
111710
112158
|
var STATE_SCHEMA = "context.source-batch.state.v1";
|
|
111711
112159
|
var RUNTIME_PARTS = [".tmp", "context-runtime", "source-batches"];
|
|
@@ -111756,14 +112204,14 @@ function paths(projectRoot, jobId) {
|
|
|
111756
112204
|
throw journalError("source-batch-id-invalid", "Source batch checkpoint ID must contain exactly 32 lowercase hexadecimal characters.");
|
|
111757
112205
|
}
|
|
111758
112206
|
const directory = resolve37(projectRoot, ...RUNTIME_PARTS, jobId);
|
|
111759
|
-
return { inputPath:
|
|
112207
|
+
return { inputPath: join114(directory, "input.json"), statePath: join114(directory, "state.json") };
|
|
111760
112208
|
}
|
|
111761
112209
|
async function assertJournalPath(projectRoot, jobId, filename) {
|
|
111762
112210
|
paths(projectRoot, jobId);
|
|
111763
112211
|
let current2 = resolve37(projectRoot);
|
|
111764
112212
|
const parts = [...RUNTIME_PARTS, jobId, filename];
|
|
111765
112213
|
for (const [index2, part] of parts.entries()) {
|
|
111766
|
-
current2 =
|
|
112214
|
+
current2 = join114(current2, part);
|
|
111767
112215
|
let stat11;
|
|
111768
112216
|
try {
|
|
111769
112217
|
stat11 = await lstat14(current2);
|
|
@@ -111794,7 +112242,7 @@ async function loadSourceBatchJournal(input) {
|
|
|
111794
112242
|
const { inputPath } = paths(input.projectRoot, input.jobId);
|
|
111795
112243
|
try {
|
|
111796
112244
|
await assertJournalPath(input.projectRoot, input.jobId, "input.json");
|
|
111797
|
-
const stored = JSON.parse(await
|
|
112245
|
+
const stored = JSON.parse(await readFile92(inputPath, "utf8"));
|
|
111798
112246
|
if (stored === null || typeof stored !== "object" || Array.isArray(stored)) {
|
|
111799
112247
|
throw journalError("source-batch-input-invalid", "Source batch checkpoint input must be an object.");
|
|
111800
112248
|
}
|
|
@@ -111897,7 +112345,7 @@ async function readSourceBatchJournalStatus(input) {
|
|
|
111897
112345
|
let warning;
|
|
111898
112346
|
try {
|
|
111899
112347
|
await assertJournalPath(journal.projectRoot, journal.jobId, "state.json");
|
|
111900
|
-
const parsed = JSON.parse(await
|
|
112348
|
+
const parsed = JSON.parse(await readFile92(journal.statePath, "utf8"));
|
|
111901
112349
|
if (parsed.schema !== STATE_SCHEMA || parsed.job_id !== journal.jobId || parsed.input_digest !== journal.inputDigest || parsed.total !== journal.items.length || !Number.isSafeInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.updated_at !== "string" || !Number.isFinite(Date.parse(parsed.updated_at))) {
|
|
111902
112350
|
throw journalError("source-batch-state-invalid", "Source batch checkpoint progress is invalid.");
|
|
111903
112351
|
}
|
|
@@ -112488,13 +112936,13 @@ init_exitCode();
|
|
|
112488
112936
|
|
|
112489
112937
|
// src/project/wikiDiscovery.ts
|
|
112490
112938
|
import { mkdir as mkdir38 } from "node:fs/promises";
|
|
112491
|
-
import { join as
|
|
112939
|
+
import { join as join116, relative as relative30 } from "node:path";
|
|
112492
112940
|
|
|
112493
112941
|
// src/project/wikiDiscoveryStore.ts
|
|
112494
112942
|
init_atomicWrite();
|
|
112495
112943
|
import { createHash as createHash35, randomUUID as randomUUID8 } from "node:crypto";
|
|
112496
|
-
import { link as link2, lstat as lstat15, mkdir as mkdir37, readFile as
|
|
112497
|
-
import { join as
|
|
112944
|
+
import { link as link2, lstat as lstat15, mkdir as mkdir37, readFile as readFile93, readdir as readdir28, rm as rm25, writeFile as writeFile29 } from "node:fs/promises";
|
|
112945
|
+
import { join as join115, relative as relative29, resolve as resolve38, sep as sep9 } from "node:path";
|
|
112498
112946
|
function wikiDiscoveryDigest(value) {
|
|
112499
112947
|
return createHash35("sha256").update(value).digest("hex");
|
|
112500
112948
|
}
|
|
@@ -112508,7 +112956,7 @@ async function assertWikiDiscoveryPath(root2, path3) {
|
|
|
112508
112956
|
throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint must stay inside its workspace");
|
|
112509
112957
|
let current2 = base;
|
|
112510
112958
|
for (const part of parts) {
|
|
112511
|
-
current2 =
|
|
112959
|
+
current2 = join115(current2, part);
|
|
112512
112960
|
try {
|
|
112513
112961
|
if ((await lstat15(current2)).isSymbolicLink()) {
|
|
112514
112962
|
throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint paths must not contain symbolic links");
|
|
@@ -112524,7 +112972,7 @@ async function readWikiDiscoveryJson(path3) {
|
|
|
112524
112972
|
try {
|
|
112525
112973
|
if ((await lstat15(path3)).isSymbolicLink())
|
|
112526
112974
|
throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint must not be a symbolic link");
|
|
112527
|
-
return JSON.parse(await
|
|
112975
|
+
return JSON.parse(await readFile93(path3, "utf8"));
|
|
112528
112976
|
} catch (error) {
|
|
112529
112977
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
112530
112978
|
return;
|
|
@@ -112537,12 +112985,12 @@ async function saveWikiDiscoveryJson(path3, value) {
|
|
|
112537
112985
|
}
|
|
112538
112986
|
async function loadWikiDiscoveryReceipts(directory) {
|
|
112539
112987
|
const result = new Map;
|
|
112540
|
-
await assertWikiDiscoveryPath(directory,
|
|
112541
|
-
await mkdir37(
|
|
112542
|
-
for (const file of await readdir28(
|
|
112988
|
+
await assertWikiDiscoveryPath(directory, join115(directory, "pages"));
|
|
112989
|
+
await mkdir37(join115(directory, "pages"), { recursive: true });
|
|
112990
|
+
for (const file of await readdir28(join115(directory, "pages"))) {
|
|
112543
112991
|
if (!/^[a-f0-9]{64}\.json$/u.test(file))
|
|
112544
112992
|
continue;
|
|
112545
|
-
const value = await readWikiDiscoveryJson(
|
|
112993
|
+
const value = await readWikiDiscoveryJson(join115(directory, "pages", file));
|
|
112546
112994
|
if (!value || typeof value.parent_node_token !== "string" || value.page_token !== undefined && typeof value.page_token !== "string" || !["completed", "failed"].includes(value.status) || value.status === "completed" && (!value.page || !Array.isArray(value.page.nodes) || value.page.next_page_token !== undefined && typeof value.page.next_page_token !== "string") || value.status === "failed" && (!value.failure || typeof value.failure.retryable !== "boolean" || typeof value.failure.reason !== "string" || typeof value.failure.message !== "string") || `${wikiDiscoveryPageKey(value)}.json` !== file) {
|
|
112547
112995
|
throw new WikiDiscoveryError("checkpoint-invalid", false, "Discovery page checkpoint is invalid; preserve it for inspection");
|
|
112548
112996
|
}
|
|
@@ -112561,14 +113009,14 @@ function ownerRunning(pid) {
|
|
|
112561
113009
|
}
|
|
112562
113010
|
}
|
|
112563
113011
|
async function acquireWikiDiscoveryLease(directory) {
|
|
112564
|
-
const leases =
|
|
113012
|
+
const leases = join115(directory, "leases");
|
|
112565
113013
|
await assertWikiDiscoveryPath(directory, leases);
|
|
112566
113014
|
await mkdir37(leases, { recursive: true });
|
|
112567
113015
|
for (let attempt = 0;attempt < 8; attempt++) {
|
|
112568
113016
|
const files = (await readdir28(leases)).filter((file) => /^\d{12}\.json$/u.test(file)).sort();
|
|
112569
113017
|
const latest = files.at(-1);
|
|
112570
113018
|
if (latest) {
|
|
112571
|
-
const current2 = await readWikiDiscoveryJson(
|
|
113019
|
+
const current2 = await readWikiDiscoveryJson(join115(leases, latest));
|
|
112572
113020
|
if (!current2 || !Number.isSafeInteger(current2.pid) || current2.pid <= 0 || typeof current2.token !== "string" || typeof current2.released !== "boolean") {
|
|
112573
113021
|
throw new WikiDiscoveryError("checkpoint-invalid", false, "Discovery lease is invalid; preserve it for inspection");
|
|
112574
113022
|
}
|
|
@@ -112577,9 +113025,9 @@ async function acquireWikiDiscoveryLease(directory) {
|
|
|
112577
113025
|
}
|
|
112578
113026
|
}
|
|
112579
113027
|
const sequence = latest ? Number(latest.slice(0, 12)) + 1 : 1;
|
|
112580
|
-
const path3 =
|
|
113028
|
+
const path3 = join115(leases, `${String(sequence).padStart(12, "0")}.json`);
|
|
112581
113029
|
const owner = { pid: process.pid, token: randomUUID8(), released: false };
|
|
112582
|
-
const temporary =
|
|
113030
|
+
const temporary = join115(leases, `.lease-${owner.token}`);
|
|
112583
113031
|
await writeFile29(temporary, JSON.stringify(owner), { flag: "wx", mode: 384 });
|
|
112584
113032
|
try {
|
|
112585
113033
|
await link2(temporary, path3);
|
|
@@ -112704,7 +113152,7 @@ async function discoverLarkWiki(input) {
|
|
|
112704
113152
|
throw new WikiDiscoveryError("invalid-concurrency", false, "Wiki concurrency must be an integer from 1 to 4");
|
|
112705
113153
|
}
|
|
112706
113154
|
const jobId = wikiDiscoveryDigest(url);
|
|
112707
|
-
const directory =
|
|
113155
|
+
const directory = join116(input.projectRoot, ".tmp", "context-runtime", "wiki-discovery", jobId);
|
|
112708
113156
|
await assertWikiDiscoveryPath(input.projectRoot, directory);
|
|
112709
113157
|
await mkdir38(directory, { recursive: true });
|
|
112710
113158
|
const release = await acquireWikiDiscoveryLease(directory);
|
|
@@ -112716,7 +113164,7 @@ async function discoverLarkWiki(input) {
|
|
|
112716
113164
|
}
|
|
112717
113165
|
async function runDiscovery(input, job) {
|
|
112718
113166
|
const { directory, url, identity: identity2 } = job;
|
|
112719
|
-
const metadataPath =
|
|
113167
|
+
const metadataPath = join116(directory, "job.json");
|
|
112720
113168
|
let metadata = await readWikiDiscoveryJson(metadataPath);
|
|
112721
113169
|
if (metadata && (metadata.protocol !== "context.wiki-discovery.v1" || metadata.url !== url || metadata.identity !== identity2)) {
|
|
112722
113170
|
throw new WikiDiscoveryError("job-identity-mismatch", false, "Resume must preserve the discovery's original URL and identity");
|
|
@@ -112765,7 +113213,7 @@ async function runDiscovery(input, job) {
|
|
|
112765
113213
|
}
|
|
112766
113214
|
const key = wikiDiscoveryPageKey(cursor);
|
|
112767
113215
|
try {
|
|
112768
|
-
await saveWikiDiscoveryJson(
|
|
113216
|
+
await saveWikiDiscoveryJson(join116(directory, "pages", `${key}.json`), receipt2);
|
|
112769
113217
|
receipts.set(key, receipt2);
|
|
112770
113218
|
} catch {
|
|
112771
113219
|
checkpointError = "A discovery page could not be checkpointed; saved pages remain reusable and unsaved pages must be requested again";
|
|
@@ -112783,7 +113231,7 @@ async function finishDiscovery(input, state) {
|
|
|
112783
113231
|
const { directory, url, identity: identity2, jobId, metadata, view } = state;
|
|
112784
113232
|
let checkpointError = state.checkpointError;
|
|
112785
113233
|
let status = input.signal?.aborted ? "paused" : checkpointError || view.pending.length || view.failed.length || !metadata.root ? "partial" : "completed";
|
|
112786
|
-
const manifestPath =
|
|
113234
|
+
const manifestPath = join116(directory, "manifest.json");
|
|
112787
113235
|
try {
|
|
112788
113236
|
await saveWikiDiscoveryJson(manifestPath, {
|
|
112789
113237
|
protocol: "context.wiki-discovery.manifest.v1",
|
|
@@ -112963,8 +113411,8 @@ init_writeLock();
|
|
|
112963
113411
|
var import_yaml51 = __toESM(require_dist(), 1);
|
|
112964
113412
|
import { existsSync as existsSync31 } from "node:fs";
|
|
112965
113413
|
import { createHash as createHash36 } from "node:crypto";
|
|
112966
|
-
import { readFile as
|
|
112967
|
-
import { isAbsolute as isAbsolute22, join as
|
|
113414
|
+
import { readFile as readFile94, readdir as readdir29, rm as rm26 } from "node:fs/promises";
|
|
113415
|
+
import { isAbsolute as isAbsolute22, join as join117, relative as relative31, resolve as resolve39, sep as sep10 } from "node:path";
|
|
112968
113416
|
function sourceIdentity(source2) {
|
|
112969
113417
|
if (source2.kind === "source.collection")
|
|
112970
113418
|
return;
|
|
@@ -112996,10 +113444,10 @@ function collectStrings(value, output) {
|
|
|
112996
113444
|
}
|
|
112997
113445
|
}
|
|
112998
113446
|
async function yamlReferences(input) {
|
|
112999
|
-
const absolutePath =
|
|
113447
|
+
const absolutePath = join117(input.projectRoot, input.path);
|
|
113000
113448
|
if (!existsSync31(absolutePath))
|
|
113001
113449
|
return false;
|
|
113002
|
-
const parsed = import_yaml51.default.parse(await
|
|
113450
|
+
const parsed = import_yaml51.default.parse(await readFile94(absolutePath, "utf8"));
|
|
113003
113451
|
const strings = [];
|
|
113004
113452
|
collectStrings(parsed, strings);
|
|
113005
113453
|
return strings.some((value) => stringReferencesSource(value, input.source));
|
|
@@ -113125,8 +113573,8 @@ async function registryRemovalWrite(projectRoot, source2) {
|
|
|
113125
113573
|
const path3 = registryPath2(source2.type);
|
|
113126
113574
|
if (path3 === null)
|
|
113127
113575
|
return;
|
|
113128
|
-
const absolutePath =
|
|
113129
|
-
const document4 = existsSync31(absolutePath) ? import_yaml51.default.parse(await
|
|
113576
|
+
const absolutePath = join117(projectRoot, path3);
|
|
113577
|
+
const document4 = existsSync31(absolutePath) ? import_yaml51.default.parse(await readFile94(absolutePath, "utf8")) : { sources: [] };
|
|
113130
113578
|
return {
|
|
113131
113579
|
path: absolutePath,
|
|
113132
113580
|
bytes: import_yaml51.default.stringify(removeDocumentEntry(document4, source2))
|
|
@@ -113144,7 +113592,7 @@ function safeManagedMaterializedPath(projectRoot, source2) {
|
|
|
113144
113592
|
return absolute;
|
|
113145
113593
|
}
|
|
113146
113594
|
function safeManagedManifestPath(projectRoot, source2) {
|
|
113147
|
-
const manifest = source2.manifest ??
|
|
113595
|
+
const manifest = source2.manifest ?? join117(source2.materializedAt, "manifest.json");
|
|
113148
113596
|
if (isAbsolute22(manifest))
|
|
113149
113597
|
throw unsafeOwnership(source2, manifest);
|
|
113150
113598
|
const absolute = resolve39(projectRoot, manifest);
|
|
@@ -113273,7 +113721,7 @@ async function createRemovalPlan(projectRoot, selector) {
|
|
|
113273
113721
|
source: source2,
|
|
113274
113722
|
registry: registryPath2(source2.type),
|
|
113275
113723
|
registryBytes: registryWrite?.bytes ?? null,
|
|
113276
|
-
managedBytes: source2.type === "note" || source2.type === "sessions" ? await
|
|
113724
|
+
managedBytes: source2.type === "note" || source2.type === "sessions" ? await readFile94(absoluteRemovals[0], "utf8") : null,
|
|
113277
113725
|
references,
|
|
113278
113726
|
cleanup,
|
|
113279
113727
|
manifestBytes: manifestWrite?.bytes ?? null
|
|
@@ -113306,10 +113754,10 @@ function publicRemovalResult(plan, action) {
|
|
|
113306
113754
|
};
|
|
113307
113755
|
}
|
|
113308
113756
|
async function pruneExtractRuntime(projectRoot, source2) {
|
|
113309
|
-
const fingerprintPath =
|
|
113757
|
+
const fingerprintPath = join117(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
|
|
113310
113758
|
const removedPhaseIds = new Set;
|
|
113311
113759
|
if (existsSync31(fingerprintPath)) {
|
|
113312
|
-
const parsed = JSON.parse(await
|
|
113760
|
+
const parsed = JSON.parse(await readFile94(fingerprintPath, "utf8"));
|
|
113313
113761
|
const phases = parsed.phases ?? {};
|
|
113314
113762
|
const next2 = Object.fromEntries(Object.entries(phases).filter(([phaseId, raw]) => {
|
|
113315
113763
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
@@ -113323,27 +113771,27 @@ async function pruneExtractRuntime(projectRoot, source2) {
|
|
|
113323
113771
|
await atomicWriteFile(fingerprintPath, `${JSON.stringify({ ...parsed, phases: next2 }, null, 2)}
|
|
113324
113772
|
`);
|
|
113325
113773
|
}
|
|
113326
|
-
const phaseOwnershipPath =
|
|
113774
|
+
const phaseOwnershipPath = join117(projectRoot, ".tmp/context-runtime/extract/custom-phase-candidates.json");
|
|
113327
113775
|
if (existsSync31(phaseOwnershipPath) && removedPhaseIds.size > 0) {
|
|
113328
|
-
const parsed = JSON.parse(await
|
|
113776
|
+
const parsed = JSON.parse(await readFile94(phaseOwnershipPath, "utf8"));
|
|
113329
113777
|
const phases = Object.fromEntries(Object.entries(parsed.phases ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
|
|
113330
113778
|
await atomicWriteFile(phaseOwnershipPath, `${JSON.stringify({ ...parsed, phases }, null, 2)}
|
|
113331
113779
|
`);
|
|
113332
113780
|
}
|
|
113333
|
-
const symbolPath =
|
|
113781
|
+
const symbolPath = join117(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
|
|
113334
113782
|
if (existsSync31(symbolPath)) {
|
|
113335
|
-
const parsed = JSON.parse(await
|
|
113783
|
+
const parsed = JSON.parse(await readFile94(symbolPath, "utf8"));
|
|
113336
113784
|
const symbols = Array.isArray(parsed.symbols) ? parsed.symbols.filter((entry) => entry === null || typeof entry !== "object" || Array.isArray(entry) || entry.source !== source2.name) : [];
|
|
113337
113785
|
const phaseFingerprints = Object.fromEntries(Object.entries(parsed.phaseFingerprints ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
|
|
113338
113786
|
await atomicWriteFile(symbolPath, `${JSON.stringify({ ...parsed, phaseFingerprints, symbols }, null, 2)}
|
|
113339
113787
|
`);
|
|
113340
113788
|
}
|
|
113341
|
-
const snapshotRoot =
|
|
113789
|
+
const snapshotRoot = join117(projectRoot, ".tmp/context-runtime/extract/candidates");
|
|
113342
113790
|
const visit4 = async (directory) => {
|
|
113343
113791
|
if (!existsSync31(directory))
|
|
113344
113792
|
return;
|
|
113345
113793
|
for (const entry of await readdir29(directory, { withFileTypes: true })) {
|
|
113346
|
-
const path3 =
|
|
113794
|
+
const path3 = join117(directory, entry.name);
|
|
113347
113795
|
if (entry.isDirectory()) {
|
|
113348
113796
|
await visit4(path3);
|
|
113349
113797
|
continue;
|
|
@@ -113351,7 +113799,7 @@ async function pruneExtractRuntime(projectRoot, source2) {
|
|
|
113351
113799
|
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
113352
113800
|
continue;
|
|
113353
113801
|
try {
|
|
113354
|
-
const parsed = JSON.parse(await
|
|
113802
|
+
const parsed = JSON.parse(await readFile94(path3, "utf8"));
|
|
113355
113803
|
const refs = Array.isArray(parsed.source_refs) ? parsed.source_refs : [];
|
|
113356
113804
|
if (parsed.source === source2.name || refs.some((ref2) => typeof ref2 === "string" && stringReferencesSource(ref2, source2))) {
|
|
113357
113805
|
await rm26(path3, { force: true });
|
|
@@ -113393,7 +113841,7 @@ async function removeProjectSource(input) {
|
|
|
113393
113841
|
});
|
|
113394
113842
|
}
|
|
113395
113843
|
await applyAtomicFileBatch({
|
|
113396
|
-
transactionRoot:
|
|
113844
|
+
transactionRoot: join117(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
|
|
113397
113845
|
writes: [...plan.registryWrite === undefined ? [] : [plan.registryWrite], ...plan.manifestWrite !== undefined ? [plan.manifestWrite] : []],
|
|
113398
113846
|
removals: plan.absoluteRemovals
|
|
113399
113847
|
});
|
|
@@ -113409,7 +113857,7 @@ init_workspace();
|
|
|
113409
113857
|
init_writeLock();
|
|
113410
113858
|
init_durableMultiFileTransaction();
|
|
113411
113859
|
init_durableSingleFileTransaction();
|
|
113412
|
-
import { readFile as
|
|
113860
|
+
import { readFile as readFile95 } from "node:fs/promises";
|
|
113413
113861
|
import ts from "typescript";
|
|
113414
113862
|
function generateSourceConfiguration(text10, selected) {
|
|
113415
113863
|
const file = ts.createSourceFile("index.ts", text10, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
@@ -113549,7 +113997,7 @@ function generateSourceConfiguration(text10, selected) {
|
|
|
113549
113997
|
async function configureRegisteredSources(projectRoot, selected) {
|
|
113550
113998
|
return withProjectWriteLock(projectRoot, "source-project-configuration", async () => {
|
|
113551
113999
|
const path3 = "src/index.ts";
|
|
113552
|
-
const config = JSON.parse(await
|
|
114000
|
+
const config = JSON.parse(await readFile95(await safeProjectTarget(projectRoot, "package.json"), "utf8"));
|
|
113553
114001
|
if (config.context?.entry !== path3)
|
|
113554
114002
|
return {
|
|
113555
114003
|
status: "manual",
|
|
@@ -113558,7 +114006,7 @@ async function configureRegisteredSources(projectRoot, selected) {
|
|
|
113558
114006
|
sources: selected
|
|
113559
114007
|
};
|
|
113560
114008
|
const target = await safeProjectTarget(projectRoot, path3);
|
|
113561
|
-
const text10 = await
|
|
114009
|
+
const text10 = await readFile95(target, "utf8");
|
|
113562
114010
|
const updated = generateSourceConfiguration(text10, selected);
|
|
113563
114011
|
if (updated === undefined)
|
|
113564
114012
|
return {
|
|
@@ -113638,7 +114086,7 @@ async function readIncludeList(projectRoot, path3) {
|
|
|
113638
114086
|
}
|
|
113639
114087
|
let content3;
|
|
113640
114088
|
try {
|
|
113641
|
-
content3 = await
|
|
114089
|
+
content3 = await readFile99(isAbsolute23(trimmed) ? resolve41(trimmed) : resolve41(projectRoot, trimmed), "utf8");
|
|
113642
114090
|
} catch (error) {
|
|
113643
114091
|
throw new ContextError(ExitCode.UserError, `cannot read include list: ${trimmed}`, {
|
|
113644
114092
|
category: ErrorCategory.UserInputInvalid,
|
|
@@ -114060,7 +114508,7 @@ init_exitCode();
|
|
|
114060
114508
|
var import_yaml54 = __toESM(require_dist(), 1);
|
|
114061
114509
|
import { constants as constants6, existsSync as existsSync32 } from "node:fs";
|
|
114062
114510
|
import { lstat as lstat16, open as open5, readdir as readdir30 } from "node:fs/promises";
|
|
114063
|
-
import { dirname as dirname45, join as
|
|
114511
|
+
import { dirname as dirname45, join as join119, resolve as resolve42 } from "node:path";
|
|
114064
114512
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
114065
114513
|
function bundledSkillRoot() {
|
|
114066
114514
|
const directory = dirname45(fileURLToPath9(import.meta.url));
|
|
@@ -114090,7 +114538,7 @@ async function readProductionSkills(root2) {
|
|
|
114090
114538
|
const entries2 = (await readdir30(root2, { withFileTypes: true })).filter((entry) => entry.isDirectory()).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
114091
114539
|
const skills = [];
|
|
114092
114540
|
for (const directory of entries2) {
|
|
114093
|
-
const entry =
|
|
114541
|
+
const entry = join119(root2, directory.name, "SKILL.md");
|
|
114094
114542
|
if (!(await lstat16(entry)).isFile())
|
|
114095
114543
|
throw new TypeError(`Skill entry must be a regular file: ${entry}`);
|
|
114096
114544
|
const handle3 = await open5(entry, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
|
|
@@ -114498,8 +114946,8 @@ init_cliFeedback();
|
|
|
114498
114946
|
init_errors3();
|
|
114499
114947
|
init_exitCode();
|
|
114500
114948
|
import { createHash as createHash37 } from "node:crypto";
|
|
114501
|
-
import { cp as cp2, lstat as lstat17, mkdir as mkdir39, mkdtemp as mkdtemp6, readFile as
|
|
114502
|
-
import { dirname as dirname47, join as
|
|
114949
|
+
import { cp as cp2, lstat as lstat17, mkdir as mkdir39, mkdtemp as mkdtemp6, readFile as readFile100, readdir as readdir31, rename as rename10, rm as rm27, writeFile as writeFile30 } from "node:fs/promises";
|
|
114950
|
+
import { dirname as dirname47, join as join120, resolve as resolve44 } from "node:path";
|
|
114503
114951
|
var MARKER = ".context-skill-install.json";
|
|
114504
114952
|
function conflict(path3) {
|
|
114505
114953
|
throw new ContextError(ExitCode.UserError, `Local skill install conflict: ${path3}`, {
|
|
@@ -114530,7 +114978,7 @@ async function digest8(root2) {
|
|
|
114530
114978
|
for (const name3 of (await readdir31(dir)).sort()) {
|
|
114531
114979
|
if (!prefix && name3 === MARKER)
|
|
114532
114980
|
continue;
|
|
114533
|
-
const path3 =
|
|
114981
|
+
const path3 = join120(dir, name3);
|
|
114534
114982
|
const entry = await lstat17(path3);
|
|
114535
114983
|
if (entry.isSymbolicLink())
|
|
114536
114984
|
conflict(path3);
|
|
@@ -114538,7 +114986,7 @@ async function digest8(root2) {
|
|
|
114538
114986
|
if (entry.isDirectory())
|
|
114539
114987
|
await visit4(path3, `${prefix}${name3}/`);
|
|
114540
114988
|
else if (entry.isFile())
|
|
114541
|
-
hash4.update(await
|
|
114989
|
+
hash4.update(await readFile100(path3));
|
|
114542
114990
|
else
|
|
114543
114991
|
conflict(path3);
|
|
114544
114992
|
}
|
|
@@ -114550,31 +114998,31 @@ async function installLocalSkills(root2, path3, dryRun, agent) {
|
|
|
114550
114998
|
if (!path3.trim())
|
|
114551
114999
|
conflict("--local requires a non-empty path");
|
|
114552
115000
|
const targetRoot = resolve44(path3);
|
|
114553
|
-
const skillsRoot =
|
|
115001
|
+
const skillsRoot = join120(targetRoot, "skills");
|
|
114554
115002
|
await checkParents(skillsRoot);
|
|
114555
|
-
const sourceRoot2 =
|
|
115003
|
+
const sourceRoot2 = join120(root2, "skills");
|
|
114556
115004
|
const commandPlans = [];
|
|
114557
115005
|
const commandEntries = new Set;
|
|
114558
115006
|
if (agent === "claude" || agent === "cursor") {
|
|
114559
|
-
for (const name3 of await readdir31(
|
|
115007
|
+
for (const name3 of await readdir31(join120(root2, "claude", "commands"))) {
|
|
114560
115008
|
if (name3.endsWith(".md"))
|
|
114561
115009
|
commandEntries.add(name3.slice(0, -3));
|
|
114562
115010
|
}
|
|
114563
|
-
const commandsRoot =
|
|
115011
|
+
const commandsRoot = join120(targetRoot, "commands");
|
|
114564
115012
|
await checkParents(commandsRoot);
|
|
114565
|
-
for (const entry of await readdir31(
|
|
115013
|
+
for (const entry of await readdir31(join120(root2, agent, "commands"), { withFileTypes: true })) {
|
|
114566
115014
|
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
114567
115015
|
continue;
|
|
114568
|
-
let content3 = await
|
|
115016
|
+
let content3 = await readFile100(join120(root2, agent, "commands", entry.name), "utf8");
|
|
114569
115017
|
if (agent === "claude" && entry.name !== "context-plan.md") {
|
|
114570
115018
|
const frontmatter2 = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content3);
|
|
114571
115019
|
if (!frontmatter2)
|
|
114572
|
-
conflict(
|
|
115020
|
+
conflict(join120(root2, agent, "commands", entry.name));
|
|
114573
115021
|
content3 = `${frontmatter2[1]}disable-model-invocation: true
|
|
114574
115022
|
${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmatter2[3]}${content3.slice(frontmatter2[0].length)}`;
|
|
114575
115023
|
}
|
|
114576
|
-
const target =
|
|
114577
|
-
const marker =
|
|
115024
|
+
const target = join120(commandsRoot, entry.name);
|
|
115025
|
+
const marker = join120(commandsRoot, `.context-${entry.name}.json`);
|
|
114578
115026
|
for (const file of [target, marker]) {
|
|
114579
115027
|
const existing = await stat11(file);
|
|
114580
115028
|
if (existing && (!existing.isFile() || existing.isSymbolicLink()))
|
|
@@ -114583,11 +115031,11 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114583
115031
|
if (await stat11(target)) {
|
|
114584
115032
|
let previous3;
|
|
114585
115033
|
try {
|
|
114586
|
-
previous3 = JSON.parse(await
|
|
115034
|
+
previous3 = JSON.parse(await readFile100(marker, "utf8"));
|
|
114587
115035
|
} catch {
|
|
114588
115036
|
conflict(target);
|
|
114589
115037
|
}
|
|
114590
|
-
const current2 = createHash37("sha256").update(await
|
|
115038
|
+
const current2 = createHash37("sha256").update(await readFile100(target)).digest("hex");
|
|
114591
115039
|
if (previous3?.owner !== "context-plugin-local" || previous3.digest !== current2)
|
|
114592
115040
|
conflict(target);
|
|
114593
115041
|
} else if (await stat11(marker))
|
|
@@ -114595,14 +115043,14 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114595
115043
|
commandPlans.push({ name: entry.name, target, marker, content: content3, hash: createHash37("sha256").update(content3).digest("hex") });
|
|
114596
115044
|
}
|
|
114597
115045
|
if (!commandPlans.length)
|
|
114598
|
-
conflict(
|
|
115046
|
+
conflict(join120(root2, agent, "commands"));
|
|
114599
115047
|
}
|
|
114600
115048
|
const plans = [];
|
|
114601
115049
|
for (const entry of (await readdir31(sourceRoot2, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
114602
|
-
if (!entry.isDirectory() || !await stat11(
|
|
115050
|
+
if (!entry.isDirectory() || !await stat11(join120(sourceRoot2, entry.name, "SKILL.md")))
|
|
114603
115051
|
continue;
|
|
114604
|
-
const source2 =
|
|
114605
|
-
const target =
|
|
115052
|
+
const source2 = join120(sourceRoot2, entry.name);
|
|
115053
|
+
const target = join120(skillsRoot, entry.name);
|
|
114606
115054
|
if (commandEntries.has(entry.name) && entry.name !== "context-plan") {
|
|
114607
115055
|
if (await stat11(target))
|
|
114608
115056
|
conflict(`${target} (use a fresh host directory to avoid duplicate command/skill entries)`);
|
|
@@ -114612,12 +115060,12 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114612
115060
|
if (existing) {
|
|
114613
115061
|
if (!existing.isDirectory() || existing.isSymbolicLink())
|
|
114614
115062
|
conflict(target);
|
|
114615
|
-
const markerPath =
|
|
115063
|
+
const markerPath = join120(target, MARKER);
|
|
114616
115064
|
if (!(await stat11(markerPath))?.isFile())
|
|
114617
115065
|
conflict(target);
|
|
114618
115066
|
let marker;
|
|
114619
115067
|
try {
|
|
114620
|
-
marker = JSON.parse(await
|
|
115068
|
+
marker = JSON.parse(await readFile100(markerPath, "utf8"));
|
|
114621
115069
|
} catch {
|
|
114622
115070
|
conflict(target);
|
|
114623
115071
|
}
|
|
@@ -114631,15 +115079,15 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114631
115079
|
if (!dryRun) {
|
|
114632
115080
|
await mkdir39(skillsRoot, { recursive: true });
|
|
114633
115081
|
for (const plan of plans) {
|
|
114634
|
-
const staging = await mkdtemp6(
|
|
114635
|
-
const candidate =
|
|
114636
|
-
const previous3 =
|
|
115082
|
+
const staging = await mkdtemp6(join120(skillsRoot, ".context-install-"));
|
|
115083
|
+
const candidate = join120(staging, "candidate");
|
|
115084
|
+
const previous3 = join120(staging, "previous");
|
|
114637
115085
|
let moved = false;
|
|
114638
115086
|
try {
|
|
114639
115087
|
await cp2(plan.source, candidate, { recursive: true });
|
|
114640
115088
|
if (agent === "claude" || agent === "cursor" && plan.name === "context-plan") {
|
|
114641
|
-
const skillFile =
|
|
114642
|
-
const content3 = await
|
|
115089
|
+
const skillFile = join120(candidate, "SKILL.md");
|
|
115090
|
+
const content3 = await readFile100(skillFile, "utf8");
|
|
114643
115091
|
const parts = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content3);
|
|
114644
115092
|
if (parts && (plan.name === "context-plan" || /^\s*context-public-entry:\s*["']?false["']?\s*$/mu.test(parts[2]))) {
|
|
114645
115093
|
const frontmatter2 = parts[2].replace(/^user-invocable:.*\r?\n?/mu, "");
|
|
@@ -114647,7 +115095,7 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114647
115095
|
${frontmatter2}${parts[3]}${content3.slice(parts[0].length)}`);
|
|
114648
115096
|
}
|
|
114649
115097
|
}
|
|
114650
|
-
await writeFile30(
|
|
115098
|
+
await writeFile30(join120(candidate, MARKER), JSON.stringify({ owner: "context-plugin-local", digest: await digest8(candidate) }) + `
|
|
114651
115099
|
`);
|
|
114652
115100
|
if (plan.exists) {
|
|
114653
115101
|
await rename10(plan.target, previous3);
|
|
@@ -114671,12 +115119,12 @@ ${frontmatter2}${parts[3]}${content3.slice(parts[0].length)}`);
|
|
|
114671
115119
|
}
|
|
114672
115120
|
for (const command3 of commandPlans) {
|
|
114673
115121
|
await mkdir39(dirname47(command3.target), { recursive: true });
|
|
114674
|
-
const staging = await mkdtemp6(
|
|
114675
|
-
await writeFile30(
|
|
114676
|
-
await writeFile30(
|
|
115122
|
+
const staging = await mkdtemp6(join120(dirname47(command3.target), ".context-install-"));
|
|
115123
|
+
await writeFile30(join120(staging, "command"), command3.content);
|
|
115124
|
+
await writeFile30(join120(staging, "marker"), JSON.stringify({ owner: "context-plugin-local", digest: command3.hash }) + `
|
|
114677
115125
|
`);
|
|
114678
|
-
await rename10(
|
|
114679
|
-
await rename10(
|
|
115126
|
+
await rename10(join120(staging, "command"), command3.target);
|
|
115127
|
+
await rename10(join120(staging, "marker"), command3.marker);
|
|
114680
115128
|
await rm27(staging, { recursive: true, force: true });
|
|
114681
115129
|
}
|
|
114682
115130
|
}
|
|
@@ -114700,10 +115148,10 @@ init_cliFeedback();
|
|
|
114700
115148
|
init_errors3();
|
|
114701
115149
|
init_exitCode();
|
|
114702
115150
|
import { lstat as lstat18 } from "node:fs/promises";
|
|
114703
|
-
import { join as
|
|
115151
|
+
import { join as join121, resolve as resolve45 } from "node:path";
|
|
114704
115152
|
var HOST_DIRS = { claude: ".claude", cursor: ".cursor", codex: ".agents" };
|
|
114705
115153
|
async function detectLocalAgent(agent, repo) {
|
|
114706
|
-
const path3 =
|
|
115154
|
+
const path3 = join121(repo, HOST_DIRS[agent]);
|
|
114707
115155
|
try {
|
|
114708
115156
|
const entry = await lstat18(path3);
|
|
114709
115157
|
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
@@ -114728,7 +115176,7 @@ async function installAutoLocalSkills(root2, path3, dryRun, detect = detectLocal
|
|
|
114728
115176
|
if (selectedAgent ? selectedAgent === "all" || agent === selectedAgent : await detect(agent, repo))
|
|
114729
115177
|
agents.push(agent);
|
|
114730
115178
|
}
|
|
114731
|
-
const targets = agents.length ? agents.map((agent) => ({ agent, path:
|
|
115179
|
+
const targets = agents.length ? agents.map((agent) => ({ agent, path: join121(repo, HOST_DIRS[agent]) })) : [{ path: join121(repo, ".agents") }];
|
|
114732
115180
|
const previews = [];
|
|
114733
115181
|
for (const target of targets)
|
|
114734
115182
|
previews.push(await installLocalSkills(root2, target.path, true, target.agent));
|
|
@@ -114761,7 +115209,7 @@ init_cliFeedback();
|
|
|
114761
115209
|
init_errors3();
|
|
114762
115210
|
init_exitCode();
|
|
114763
115211
|
import { existsSync as existsSync35 } from "node:fs";
|
|
114764
|
-
import { dirname as dirname49, join as
|
|
115212
|
+
import { dirname as dirname49, join as join123, resolve as resolve46 } from "node:path";
|
|
114765
115213
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
114766
115214
|
|
|
114767
115215
|
// src/project/pluginInstallTargets.ts
|
|
@@ -114770,9 +115218,9 @@ init_errors3();
|
|
|
114770
115218
|
init_exitCode();
|
|
114771
115219
|
import { execFile as execFile13 } from "node:child_process";
|
|
114772
115220
|
import { existsSync as existsSync34 } from "node:fs";
|
|
114773
|
-
import { cp as cp3, mkdir as mkdir40, readdir as readdir32, readFile as
|
|
115221
|
+
import { cp as cp3, mkdir as mkdir40, readdir as readdir32, readFile as readFile101, rename as rename11, rm as rm28, writeFile as writeFile31 } from "node:fs/promises";
|
|
114774
115222
|
import { homedir as homedir2 } from "node:os";
|
|
114775
|
-
import { dirname as dirname48, join as
|
|
115223
|
+
import { dirname as dirname48, join as join122 } from "node:path";
|
|
114776
115224
|
import { promisify as promisify13 } from "node:util";
|
|
114777
115225
|
var execFileAsync7 = promisify13(execFile13);
|
|
114778
115226
|
var MARKETPLACE_NAME = "c4a";
|
|
@@ -114826,23 +115274,23 @@ async function claudePluginInstalled(pluginId) {
|
|
|
114826
115274
|
}
|
|
114827
115275
|
}
|
|
114828
115276
|
function codexHome() {
|
|
114829
|
-
return process.env.CODEX_HOME?.trim() ||
|
|
115277
|
+
return process.env.CODEX_HOME?.trim() || join122(homedir2(), ".codex");
|
|
114830
115278
|
}
|
|
114831
115279
|
function claudePluginCacheRoot() {
|
|
114832
115280
|
const explicitRoot = process.env[CLAUDE_PLUGIN_CACHE_ROOT_ENV2]?.trim();
|
|
114833
115281
|
if (explicitRoot)
|
|
114834
115282
|
return explicitRoot;
|
|
114835
115283
|
const home = process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV2]?.trim() || homedir2();
|
|
114836
|
-
return
|
|
115284
|
+
return join122(home, ".claude", "plugins", "cache");
|
|
114837
115285
|
}
|
|
114838
115286
|
function sharedSkillsRoot() {
|
|
114839
|
-
return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() ||
|
|
115287
|
+
return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join122(homedir2(), ".agents", "skills");
|
|
114840
115288
|
}
|
|
114841
115289
|
function claudeSkillsRoot() {
|
|
114842
|
-
return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() ||
|
|
115290
|
+
return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join122(homedir2(), ".claude", "skills");
|
|
114843
115291
|
}
|
|
114844
115292
|
function cursorPluginRoot() {
|
|
114845
|
-
return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() ||
|
|
115293
|
+
return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join122(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
|
|
114846
115294
|
}
|
|
114847
115295
|
function blockHeader(line) {
|
|
114848
115296
|
const match = line.match(/^\s*\[([^\]]+)\]\s*$/u);
|
|
@@ -114893,8 +115341,8 @@ function pruneLegacyCodexConfigContent(content3) {
|
|
|
114893
115341
|
`), removed };
|
|
114894
115342
|
}
|
|
114895
115343
|
async function pruneLegacyCodexConfig(dryRun, steps) {
|
|
114896
|
-
const configPath =
|
|
114897
|
-
const current2 = await
|
|
115344
|
+
const configPath = join122(codexHome(), "config.toml");
|
|
115345
|
+
const current2 = await readFile101(configPath, "utf8").catch(() => "");
|
|
114898
115346
|
if (!current2)
|
|
114899
115347
|
return;
|
|
114900
115348
|
const next2 = pruneLegacyCodexConfigContent(current2);
|
|
@@ -114910,7 +115358,7 @@ async function pruneLegacyCodexConfig(dryRun, steps) {
|
|
|
114910
115358
|
}
|
|
114911
115359
|
}
|
|
114912
115360
|
async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, steps) {
|
|
114913
|
-
const cacheRoot =
|
|
115361
|
+
const cacheRoot = join122(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
|
|
114914
115362
|
if (!existsSync34(cacheRoot))
|
|
114915
115363
|
return;
|
|
114916
115364
|
const versions = (await readdir32(cacheRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== keepVersion).map((entry) => entry.name).sort();
|
|
@@ -114922,7 +115370,7 @@ async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, ste
|
|
|
114922
115370
|
status: dryRun ? "planned" : "ran"
|
|
114923
115371
|
});
|
|
114924
115372
|
if (!dryRun)
|
|
114925
|
-
await Promise.all(versions.map((version3) => rm28(
|
|
115373
|
+
await Promise.all(versions.map((version3) => rm28(join122(cacheRoot, version3), { recursive: true, force: true })));
|
|
114926
115374
|
}
|
|
114927
115375
|
async function pruneCodexPluginCache(currentVersion, dryRun, steps) {
|
|
114928
115376
|
await pruneCodexPluginCacheForName(PLUGIN_NAME, currentVersion, dryRun, steps);
|
|
@@ -114946,15 +115394,15 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
|
|
|
114946
115394
|
for (const marketplace of marketplaces) {
|
|
114947
115395
|
if (!marketplace.isDirectory())
|
|
114948
115396
|
continue;
|
|
114949
|
-
const pluginDir =
|
|
115397
|
+
const pluginDir = join122(cacheRoot, marketplace.name, PLUGIN_NAME);
|
|
114950
115398
|
if (!existsSync34(pluginDir))
|
|
114951
115399
|
continue;
|
|
114952
115400
|
const versions = await readdir32(pluginDir, { withFileTypes: true }).catch(() => []);
|
|
114953
115401
|
for (const version3 of versions) {
|
|
114954
115402
|
if (!version3.isDirectory())
|
|
114955
115403
|
continue;
|
|
114956
|
-
const versionDir =
|
|
114957
|
-
if (!existsSync34(
|
|
115404
|
+
const versionDir = join122(pluginDir, version3.name);
|
|
115405
|
+
if (!existsSync34(join122(versionDir, ORPHAN_MARKER2)))
|
|
114958
115406
|
continue;
|
|
114959
115407
|
removed.push(`${marketplace.name}/${PLUGIN_NAME}/${version3.name}`);
|
|
114960
115408
|
if (!dryRun) {
|
|
@@ -114964,7 +115412,7 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
|
|
|
114964
115412
|
if (!dryRun && await isEmptyDir2(pluginDir)) {
|
|
114965
115413
|
await rm28(pluginDir, { recursive: true, force: true });
|
|
114966
115414
|
}
|
|
114967
|
-
const marketplaceDir =
|
|
115415
|
+
const marketplaceDir = join122(cacheRoot, marketplace.name);
|
|
114968
115416
|
if (!dryRun && await isEmptyDir2(marketplaceDir)) {
|
|
114969
115417
|
await rm28(marketplaceDir, { recursive: true, force: true });
|
|
114970
115418
|
}
|
|
@@ -114985,7 +115433,7 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
|
|
|
114985
115433
|
return;
|
|
114986
115434
|
const removed = [];
|
|
114987
115435
|
for (const pluginName of LEGACY_PLUGIN_NAMES) {
|
|
114988
|
-
const pluginDir =
|
|
115436
|
+
const pluginDir = join122(cacheRoot, MARKETPLACE_NAME, pluginName);
|
|
114989
115437
|
if (!existsSync34(pluginDir))
|
|
114990
115438
|
continue;
|
|
114991
115439
|
removed.push(`${MARKETPLACE_NAME}/${pluginName}`);
|
|
@@ -115002,11 +115450,11 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
|
|
|
115002
115450
|
}
|
|
115003
115451
|
}
|
|
115004
115452
|
async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
|
|
115005
|
-
const manifest = await
|
|
115453
|
+
const manifest = await readFile101(join122(root2, "claude", ".claude-plugin", "plugin.json"), "utf8").then((content3) => JSON.parse(content3)).catch(() => null);
|
|
115006
115454
|
const currentVersion = typeof manifest?.version === "string" ? manifest.version : null;
|
|
115007
115455
|
if (!currentVersion)
|
|
115008
115456
|
return;
|
|
115009
|
-
const pluginDir =
|
|
115457
|
+
const pluginDir = join122(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
|
|
115010
115458
|
const staleVersions = (await readdir32(pluginDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== currentVersion).map((entry) => entry.name);
|
|
115011
115459
|
if (staleVersions.length === 0)
|
|
115012
115460
|
return;
|
|
@@ -115016,7 +115464,7 @@ async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
|
|
|
115016
115464
|
status: dryRun ? "planned" : "ran"
|
|
115017
115465
|
});
|
|
115018
115466
|
if (!dryRun) {
|
|
115019
|
-
await Promise.all(staleVersions.map((version3) => rm28(
|
|
115467
|
+
await Promise.all(staleVersions.map((version3) => rm28(join122(pluginDir, version3), { recursive: true, force: true })));
|
|
115020
115468
|
}
|
|
115021
115469
|
}
|
|
115022
115470
|
function enableCodexPluginConfig(content3) {
|
|
@@ -115080,33 +115528,33 @@ source = ${JSON.stringify(root2)}
|
|
|
115080
115528
|
`;
|
|
115081
115529
|
}
|
|
115082
115530
|
async function ensureCodexPluginEnabled() {
|
|
115083
|
-
const configPath =
|
|
115531
|
+
const configPath = join122(codexHome(), "config.toml");
|
|
115084
115532
|
await mkdir40(dirname48(configPath), { recursive: true });
|
|
115085
|
-
const current2 = await
|
|
115533
|
+
const current2 = await readFile101(configPath, "utf8").catch(() => "");
|
|
115086
115534
|
const next2 = enableCodexPluginConfig(current2);
|
|
115087
115535
|
if (next2 !== current2) {
|
|
115088
115536
|
await writeFile31(configPath, next2, "utf8");
|
|
115089
115537
|
}
|
|
115090
115538
|
}
|
|
115091
115539
|
async function ensureCodexLocalMarketplace(root2) {
|
|
115092
|
-
const configPath =
|
|
115540
|
+
const configPath = join122(codexHome(), "config.toml");
|
|
115093
115541
|
await mkdir40(dirname48(configPath), { recursive: true });
|
|
115094
|
-
const current2 = await
|
|
115542
|
+
const current2 = await readFile101(configPath, "utf8").catch(() => "");
|
|
115095
115543
|
const next2 = upsertCodexLocalMarketplaceConfig(current2, root2);
|
|
115096
115544
|
if (next2 !== current2) {
|
|
115097
115545
|
await writeFile31(configPath, next2, "utf8");
|
|
115098
115546
|
}
|
|
115099
115547
|
}
|
|
115100
115548
|
async function codexPluginVersion(root2) {
|
|
115101
|
-
const manifestPath =
|
|
115102
|
-
const manifest = JSON.parse(await
|
|
115549
|
+
const manifestPath = join122(root2, "codex", ".codex-plugin", "plugin.json");
|
|
115550
|
+
const manifest = JSON.parse(await readFile101(manifestPath, "utf8"));
|
|
115103
115551
|
if (typeof manifest.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(manifest.version)) {
|
|
115104
115552
|
throw new Error(`Codex plugin manifest has an invalid version: ${manifestPath}`);
|
|
115105
115553
|
}
|
|
115106
115554
|
return manifest.version;
|
|
115107
115555
|
}
|
|
115108
115556
|
function codexPluginCacheDir(version3) {
|
|
115109
|
-
return
|
|
115557
|
+
return join122(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
|
|
115110
115558
|
}
|
|
115111
115559
|
async function replaceDirectoryFromSource(source2, target) {
|
|
115112
115560
|
await mkdir40(dirname48(target), { recursive: true });
|
|
@@ -115129,7 +115577,7 @@ async function replaceDirectoryFromSource(source2, target) {
|
|
|
115129
115577
|
}
|
|
115130
115578
|
}
|
|
115131
115579
|
async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
|
|
115132
|
-
const source2 =
|
|
115580
|
+
const source2 = join122(root2, "codex");
|
|
115133
115581
|
const target = codexPluginCacheDir(version3);
|
|
115134
115582
|
steps.push({
|
|
115135
115583
|
agent: "codex",
|
|
@@ -115141,16 +115589,16 @@ async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
|
|
|
115141
115589
|
await replaceDirectoryFromSource(source2, target);
|
|
115142
115590
|
}
|
|
115143
115591
|
async function bundledProviderSkillNames(root2) {
|
|
115144
|
-
const skillsRoot =
|
|
115592
|
+
const skillsRoot = join122(root2, "skills");
|
|
115145
115593
|
const entries2 = await readdir32(skillsRoot, { withFileTypes: true });
|
|
115146
115594
|
const names = [];
|
|
115147
115595
|
for (const entry of entries2) {
|
|
115148
115596
|
if (!entry.isDirectory() || entry.name === "context")
|
|
115149
115597
|
continue;
|
|
115150
|
-
const skillPath =
|
|
115598
|
+
const skillPath = join122(skillsRoot, entry.name, "SKILL.md");
|
|
115151
115599
|
if (!existsSync34(skillPath))
|
|
115152
115600
|
continue;
|
|
115153
|
-
const skill = await
|
|
115601
|
+
const skill = await readFile101(skillPath, "utf8");
|
|
115154
115602
|
if (!/^\s*context-role:\s*["']?indexer-provider["']?\s*$/mu.test(skill))
|
|
115155
115603
|
continue;
|
|
115156
115604
|
names.push(entry.name);
|
|
@@ -115162,10 +115610,10 @@ async function bundledProviderSkillNames(root2) {
|
|
|
115162
115610
|
return names;
|
|
115163
115611
|
}
|
|
115164
115612
|
async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps) {
|
|
115165
|
-
const sourceRoot2 =
|
|
115613
|
+
const sourceRoot2 = join122(root2, "skills");
|
|
115166
115614
|
for (const name3 of await bundledProviderSkillNames(root2)) {
|
|
115167
|
-
const source2 =
|
|
115168
|
-
const target =
|
|
115615
|
+
const source2 = join122(sourceRoot2, name3);
|
|
115616
|
+
const target = join122(targetRoot, name3);
|
|
115169
115617
|
steps.push({
|
|
115170
115618
|
agent,
|
|
115171
115619
|
command: `materialize lifecycle Provider skill: ${shellQuote8(source2)} -> ${shellQuote8(target)}`,
|
|
@@ -115176,7 +115624,7 @@ async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps
|
|
|
115176
115624
|
}
|
|
115177
115625
|
}
|
|
115178
115626
|
async function installCursor(root2, dryRun, steps) {
|
|
115179
|
-
const source2 =
|
|
115627
|
+
const source2 = join122(root2, "cursor");
|
|
115180
115628
|
const target = cursorPluginRoot();
|
|
115181
115629
|
steps.push({
|
|
115182
115630
|
agent: "cursor",
|
|
@@ -115231,12 +115679,12 @@ async function installCodex(root2, dryRun, steps) {
|
|
|
115231
115679
|
steps.push({ agent: "codex", command: commandLine("codex", addArgs), status: dryRun ? "planned" : "ran" });
|
|
115232
115680
|
steps.push({
|
|
115233
115681
|
agent: "codex",
|
|
115234
|
-
command: `ensure ${shellQuote8(
|
|
115682
|
+
command: `ensure ${shellQuote8(join122(codexHome(), "config.toml"))} registers local marketplace ${shellQuote8(MARKETPLACE_NAME)}`,
|
|
115235
115683
|
status: dryRun ? "planned" : "ran"
|
|
115236
115684
|
});
|
|
115237
115685
|
steps.push({
|
|
115238
115686
|
agent: "codex",
|
|
115239
|
-
command: `ensure ${shellQuote8(
|
|
115687
|
+
command: `ensure ${shellQuote8(join122(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
|
|
115240
115688
|
status: dryRun ? "planned" : "ran"
|
|
115241
115689
|
});
|
|
115242
115690
|
if (dryRun) {
|
|
@@ -115291,13 +115739,13 @@ function pluginRootCandidates() {
|
|
|
115291
115739
|
return [resolve46(envRoot)];
|
|
115292
115740
|
const candidates = [];
|
|
115293
115741
|
for (const dir of packageCandidateDirs()) {
|
|
115294
|
-
candidates.push(
|
|
115295
|
-
candidates.push(
|
|
115742
|
+
candidates.push(join123(dir, "plugins"));
|
|
115743
|
+
candidates.push(join123(dir, "dist", "plugins"));
|
|
115296
115744
|
}
|
|
115297
115745
|
return [...new Set(candidates)];
|
|
115298
115746
|
}
|
|
115299
115747
|
function isInstallablePluginRoot(root2) {
|
|
115300
|
-
return existsSync35(
|
|
115748
|
+
return existsSync35(join123(root2, ".claude-plugin", "marketplace.json")) && existsSync35(join123(root2, ".agents", "plugins", "marketplace.json")) && existsSync35(join123(root2, "claude", ".claude-plugin", "plugin.json")) && existsSync35(join123(root2, "codex", ".codex-plugin", "plugin.json")) && existsSync35(join123(root2, "cursor", ".cursor-plugin", "plugin.json")) && existsSync35(join123(root2, "skills"));
|
|
115301
115749
|
}
|
|
115302
115750
|
function resolveBundledPluginsRoot() {
|
|
115303
115751
|
const candidates = pluginRootCandidates();
|
|
@@ -115544,10 +115992,10 @@ function readQuickstartPath() {
|
|
|
115544
115992
|
try {
|
|
115545
115993
|
let dir = dirname50(fileURLToPath11(import.meta.url));
|
|
115546
115994
|
for (let i2 = 0;i2 < 8; i2++) {
|
|
115547
|
-
const candidate =
|
|
115995
|
+
const candidate = join124(dir, "docs", "quickstart.md");
|
|
115548
115996
|
if (existsSync36(candidate))
|
|
115549
115997
|
return candidate;
|
|
115550
|
-
const pkg =
|
|
115998
|
+
const pkg = join124(dir, "package.json");
|
|
115551
115999
|
if (existsSync36(pkg))
|
|
115552
116000
|
return candidate;
|
|
115553
116001
|
const parent = dirname50(dir);
|
|
@@ -115556,7 +116004,7 @@ function readQuickstartPath() {
|
|
|
115556
116004
|
dir = parent;
|
|
115557
116005
|
}
|
|
115558
116006
|
} catch {}
|
|
115559
|
-
return
|
|
116007
|
+
return join124(dirname50(fileURLToPath11(import.meta.url)), "docs", "quickstart.md");
|
|
115560
116008
|
}
|
|
115561
116009
|
var GREEN = "\x1B[32m";
|
|
115562
116010
|
var RESET = "\x1B[0m";
|
|
@@ -115686,7 +116134,7 @@ function createCliProgram() {
|
|
|
115686
116134
|
format: options.format === "json" ? "json" : "text"
|
|
115687
116135
|
});
|
|
115688
116136
|
});
|
|
115689
|
-
review.command("apply <payload-file>").description("Apply
|
|
116137
|
+
review.command("apply <payload-file>").description("Apply scoped JSON/JSONL decisions and revision instructions").option("--format <format>", "output format: text | json", "text").action(async (payloadInput, options) => {
|
|
115690
116138
|
if (options.format !== "text" && options.format !== "json") {
|
|
115691
116139
|
throw new ContextError(ExitCode.UserError, "--format must be text or json", {
|
|
115692
116140
|
category: ErrorCategory.UserInputInvalid
|
|
@@ -115707,7 +116155,7 @@ function createCliProgram() {
|
|
|
115707
116155
|
})
|
|
115708
116156
|
});
|
|
115709
116157
|
});
|
|
115710
|
-
review.command("approve-all [collection]").description("Approve the complete current review scope under explicit managed or user
|
|
116158
|
+
review.command("approve-all [collection]").description("Approve the complete current review scope under explicit managed authority or user confirmation").option("--all", "approve all current draft candidates across internal collections").option("--managed", "assert explicit current-conversation managed approval").option("--force", "deprecated compatibility flag; use --confirmed for user approval").option("--confirmed", "apply explicit user confirmation to the current report scope").option("--verbose", "include candidate ids and materialized page paths in JSON output").option("--format <format>", "output format: text | json", "text").action(async (collection, options) => {
|
|
115711
116159
|
if (options.format !== "text" && options.format !== "json") {
|
|
115712
116160
|
throw new ContextError(ExitCode.UserError, "--format must be text or json", {
|
|
115713
116161
|
category: ErrorCategory.UserInputInvalid
|
|
@@ -115718,6 +116166,7 @@ function createCliProgram() {
|
|
|
115718
116166
|
...collection !== undefined ? { collection } : {},
|
|
115719
116167
|
...options.all === true ? { all: true } : {},
|
|
115720
116168
|
managed: options.managed === true,
|
|
116169
|
+
confirmed: options.confirmed === true,
|
|
115721
116170
|
force: options.force === true,
|
|
115722
116171
|
verbose: options.verbose === true,
|
|
115723
116172
|
format: options.format === "json" ? "json" : "text",
|