@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 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 reviewForceApproveCommand(observation) {
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]} --force --format json` : "context review approve-all --all --force --format json";
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.force-approve-current": (observation) => {
92476
- const next = reviewForceApproveCommand(observation);
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 join94 } from "node:path";
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(join94(input.cwd, entry), { recursive: true, force: true });
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 readFile74, rm as rm21 } from "node:fs/promises";
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 join95 } from "node:path";
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(join95(tmpdir(), "context-lark-whiteboard-"));
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 readFile74(join95(tempRoot, "raw.json"), "utf8"));
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 readFile75, readdir as readdir22, rm as rm22 } from "node:fs/promises";
98243
- import { extname as extname13, join as join96 } from "node:path";
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 readFile75(input.localPath);
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(join96(tmpdir2(), "context-lark-resource-"));
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 readFile75(join96(tempRoot, path3));
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(join96(tmpdir2(), "context-lark-sheet-"));
98430
+ const tempRoot = await mkdtemp5(join97(tmpdir2(), "context-lark-sheet-"));
98431
98431
  try {
98432
- const outputPath = join96(tempRoot, "sheet.json");
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 readFile75(outputPath, "utf8"));
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 readFile76, 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 join97, relative as relative24, resolve as resolve30, sep as sep8 } from "node:path";
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 = join97(current2, part);
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 readFile76(current2);
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(join97(root2, path3)), { recursive: true });
99679
- await writeFile26(join97(root2, path3), bytes, { flag: "wx", mode: 384 });
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(join97(root2, `${FILE2}.pending`), `${JSON.stringify(manifest, null, 2)}
99682
+ await writeFile26(join98(root2, `${FILE2}.pending`), `${JSON.stringify(manifest, null, 2)}
99683
99683
  `, { flag: "wx", mode: 384 });
99684
- await rename8(join97(root2, `${FILE2}.pending`), join97(root2, FILE2));
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 = join97(root2, FILE2);
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 readFile76(path3, "utf8")));
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 = join97(ancestor, ...missing);
99786
- const output = join97(parent, basename10(requested));
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 readFile77 } from "node:fs/promises";
99860
- import { basename as basename11, extname as extname14, join as join98 } from "node:path";
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 readFile77(path3);
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 ?? join98(entry.materializedAt, "manifest.json");
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 = join98(root2, assetRoot);
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 = join98(dir, entry.name);
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) => join98(input.materializedAtAbsPath, 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 = join98(input.projectRoot, manifestPath);
100163
+ const manifestAbsPath = join99(input.projectRoot, manifestPath);
100164
100164
  const materializedAt = entry.materializedAt;
100165
- const materializedAtAbsPath = join98(input.projectRoot, materializedAt);
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: join98(materializedAtAbsPath, documentPath),
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: join98(materializedAtAbsPath, asset.entry.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: join98(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
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 readFile83, readdir as readdir24 } from "node:fs/promises";
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 readFile83(await safeProjectTarget(root2, path3), "utf8");
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 readFile84, readdir as readdir25, rename as rename9, rmdir as rmdir2 } from "node:fs/promises";
100656
- import { join as join103 } from "node:path";
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 readFile84(ownerPath, "utf8");
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 = join103(input.projectRoot, lockRelative);
100704
- const guard = join103(path3, ".recovery");
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, join103(input.projectRoot, archive));
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 join104 } from "node:path";
100740
- import { lstat as lstat12, readdir as readdir26, readFile as readFile85 } from "node:fs/promises";
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 readFile85(target, "utf8");
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(join104(root2, path3));
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(join104(root2, path3))).sort())
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: join104(root2, "skills/recover-workspace/SKILL.md"),
100781
- issue_template: join104(root2, "resources/templates/recovery-issue.md")
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 readFile86 } from "node:fs/promises";
100867
- import { join as join105 } from "node:path";
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 readFile86(join105(root2, await revisionStoragePath(root2)), "utf8");
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 readFile86(join105(root2, CANDIDATE_LEDGER_FILE), "utf8").catch((error) => {
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 readFile95 } from "node:fs/promises";
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 readFile95(path3, "utf8");
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 readFile96 } from "node:fs/promises";
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 readFile96(resolve40(projectRoot, path3), "utf8"));
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 readFile97 } from "node:fs/promises";
101279
- import { join as join117, posix as posix11 } from "node:path";
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 readFile97(path3, "utf8");
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(join117(input.projectRoot, path3));
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 join123 } from "node:path";
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 join92 } from "node:path";
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 readFile71, writeFile as writeFile25 } from "node:fs/promises";
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 join90, resolve as resolve28 } from "node:path";
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 review", baselineHash: await reviewSiteBaselineHash(root2, candidates.map((c) => c.record.approved_revision?.previous_path ?? c.record.path)), nodes, pages, navigationBaseline };
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/reviewFeedbackCode.ts
105840
- function createReviewFeedbackCodec() {
105841
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
105842
- function checksum(text9) {
105843
- let crc = 4294967295;
105844
- for (let i2 = 0;i2 < text9.length; i2++) {
105845
- crc ^= text9.charCodeAt(i2);
105846
- for (let j = 0;j < 8; j++)
105847
- crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
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
- return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
105850
- }
105851
- function validate(value) {
105852
- 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))
105853
- throw new Error("Invalid review feedback scope or decisions");
105854
- const seen = new Set;
105855
- for (const repair of value.repairs) {
105856
- 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)
105857
- throw new Error("Invalid or conflicting revision instruction");
105858
- seen.add(repair.index);
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
- if (value.statuses.filter((s) => s === "revised").length !== seen.size)
105861
- throw new Error("Missing revision instruction");
105862
- return value;
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
- function encode(value) {
105865
- validate(value);
105866
- const bytes = new TextEncoder().encode(JSON.stringify({ scope: value.scope, idsHash: value.idsHash, contentHash: value.contentHash, baselineHash: value.baselineHash, statuses: value.statuses }));
105867
- let bits = 0, carry = 0, data2 = "";
105868
- for (const byte of bytes) {
105869
- carry = carry << 8 | byte;
105870
- bits += 8;
105871
- while (bits >= 6) {
105872
- bits -= 6;
105873
- data2 += alphabet[carry >>> bits & 63];
105874
- }
105875
- }
105876
- if (bits)
105877
- data2 += alphabet[carry << 6 - bits & 63];
105878
- const body2 = `CR2.${data2}`;
105879
- const repairs = value.repairs.map((r) => JSON.stringify([r.index, r.instruction])).join(`
105880
- `);
105881
- const trailer = repairs ? `
105882
- ${repairs}` : "";
105883
- const code3 = `${body2}.${checksum(body2 + trailer)}${trailer}`;
105884
- if (code3.length > 250000)
105885
- throw new Error("Review feedback is too large; shorten instructions or review a smaller scope");
105886
- return code3;
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 => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[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
- function decode2(raw) {
105889
- const code3 = raw.trim();
105890
- if (code3.length > 250000)
105891
- throw new Error("Review feedback exceeds supported size");
105892
- const [header, ...lines] = code3.split(`
105893
- `);
105894
- const trailer = lines.length ? `
105895
- ${lines.join(`
105896
- `)}` : "";
105897
- const match = /^CR2\.([A-Za-z0-9_-]+)\.([a-f0-9]{8})$/.exec(header);
105898
- if (!match || checksum(`CR2.${match[1]}` + trailer) !== match[2])
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
- const headerValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes)));
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
- // src/project/reviewSiteClient.ts
105925
- var REVIEW_SITE_CLIENT = String.raw`
105926
- const $=id=>document.getElementById(id);
105927
- let language=(navigator.languages?.[0]||navigator.language||'en').startsWith('zh')?'zh':'en';
105928
- const words={approvedContext:['已入库正文,供核对目录;本轮无需再次审核。','Approved content shown for navigation review; no new approval required.'],newRoots:['新增一级目录','New top-level categories'],ackRoots:['我已知晓本次新增一级目录','I acknowledge the new top-level categories'],home:['待审核内容','Pages to review'],unchanged:['本次未变更内容略。','Unchanged content omitted.'],previous:['查看旧文本','Previous text'],removed:['移除或替换的旧内容','Removed or replaced content'],removedNav:['移除的目录条目','Removed navigation entries'],unplaced:['待落位文章','Unplaced articles'],approve:['批准这篇','Approve page'],reject:['拒绝这篇','Reject page'],revise:['修订','Revise'],approved:['已批准','Approved'],rejected:['已拒绝','Rejected'],revised:['已修订','Revision requested'],cancel:['取消','Cancel'],copy:['复制审核码','Copy review code'],allApprove:['全部批准','Approve all'],allReject:['全部拒绝','Reject all'],note:['输入修订意见','Enter revision instructions'],guide:['逐篇阅读并批准或拒绝后,复制审核码回复给 Agent。需要修改的文章请填写修订意见。','Read each page, approve or reject, then copy the review code back to your Agent. Enter instructions for pages needing revision.'],known:['知道了','Got it'],close:['关闭','Close'],notReviewed:['尚未完成审核','Not yet reviewed'],confirmAll:['建议逐篇阅读并确认。除非已读完所有待审核文章,否则请勿一次性全部批准。已有拒绝和修订意见将保留。','Read and confirm each page. Approve all only after reading every pending page. Existing rejections and revisions are preserved.'],confirm:['已阅读,全部批准','Read all, approve'],copied:['审核码已复制','Review code copied'],failed:['复制失败,请手动复制下方完整内容','Copy failed. Copy the complete text below manually.'],instructions:['请回到和 Agent 的会话窗口粘贴已复制内容进行回复即可继续~','Return to your conversation with the Agent and reply with the copied content to continue.'],long:['超过 1000 字符,飞书表单可能不接受。飞书场景下建议 @Bot 后粘贴回复。','Over 1,000 characters: a Feishu form may reject it. Mention @Bot and paste it in a reply instead.'],files:['预期工作区变化','Expected workspace changes'],navigate:['目录与文章','Directories and articles'],pendingNew:['未审批的新增文章','Pending new pages'],pendingModify:['未审批的修改文章','Pending modified pages'],processed:['已经审核和修订的文章','Reviewed or revision requested'],noSelection:['请先选择审核结果或填写修订意见','Select a decision or enter revision instructions first'],baseline:['无 Git 导航基线,目录沿用当前工作区;未推断历史目录变化。','No Git navigation baseline. Current navigation is shown without inferred historic changes.'],placement:['待落位文章不是新的站点栏目;请先按既有分类完成导航规划。','Unplaced articles are not a new site category. Finish their placement in the existing navigation first.']};
105929
- const t=k=>words[k]?.[language==='zh'?0:1]||k;
105930
- const escape=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
105931
- const decisions=new Map(),notes=new Map(),expanded=new Set();let selected=null,active=null;
105932
- const candidates=DATA.pages.filter(p=>p.candidate_id),ordered=[...candidates].sort((a,b)=>a.candidate_id<b.candidate_id?-1:1);
105933
- for(const p of candidates)if(p.revisionInstruction){decisions.set(p.candidate_id,'revised');notes.set(p.candidate_id,p.revisionInstruction)}
105934
- const badge=change=>change==='unchanged'?'':'<span class="badge '+change+'">'+(change==='new'?'New':change==='modify'?'Modify':change==='removed'?'Remove':t(change))+'</span>';
105935
- const state=p=>decisions.get(p.candidate_id)||'pending';
105936
- function descendants(key,seen=new Set()){if(seen.has(key))return[];seen.add(key);return DATA.nodes.filter(n=>n.parent===key).flatMap(n=>[n,...descendants(n.key,seen)])}
105937
- function nodeChange(n){if(n.removed)return 'removed';const changes=[n.change,...[n,...descendants(n.key)].flatMap(v=>{const p=DATA.pages.find(p=>p.id===v.page);return p?[p.change]:[]}),...descendants(n.key).map(v=>v.change)];return descendants(n.key).some(v=>v.removed)||changes.includes('modify')?'modify':changes.includes('new')?'new':'unchanged'}
105938
- function nodeTitle(n){return n.key==='review-unplaced'?t('unplaced'):n.title}
105939
- function nodeLabel(n){return (n.removed?'<del>':'')+escape(nodeTitle(n))+(n.removed?'</del>':'')+(n.oldTitle?'<small class="old-title"> ← '+escape(n.oldTitle)+'</small>':'')}
105940
- function totals(){const result={new:0,modify:0,approved:0,rejected:0,revised:0,pending:0};for(const p of candidates){const s=state(p);result[s]++;if(s==='pending')result[p.change==='new'?'new':'modify']++}return result}
105941
- function updateCounts(){const c=totals();$('counts').textContent=c.new+' New / '+c.modify+' Modify / '+(c.approved+c.rejected+c.revised)+' Confirm';$('counter-pop').innerHTML='<div class="counter-grid"><div><b>'+c.new+'</b>'+t('pendingNew')+'</div><div><b>'+c.modify+'</b>'+t('pendingModify')+'</div></div><p>'+t('processed')+': '+(c.approved+c.rejected+c.revised)+'</p><small>'+t('approved')+' '+c.approved+' · '+t('rejected')+' '+c.rejected+' · '+t('revised')+' '+c.revised+'</small>'}
105942
- function button(n,depth){const change=nodeChange(n),page=DATA.pages.find(p=>p.id===n.page);return '<button data-node="'+escape(n.key)+'" class="node '+(DATA.nodes.some(c=>c.parent===n.key)?'directory ':'')+change+(selected===n.page?' selected':'')+'" style="padding-left:'+(26+14*depth)+'px">'+nodeLabel(n)+badge(change)+(page&&page.candidate_id&&state(page)!=='pending'?badge(state(page)):'')+(DATA.nodes.some(c=>c.parent===n.key)?'<span class="caret">›</span>':'')+'</button>'}
105943
- function renderNav(){const roots=DATA.nodes.filter(n=>!n.parent).sort((a,b)=>a.order-b.order);$('top').innerHTML=roots.map(n=>'<button data-root="'+escape(n.key)+'" class="'+nodeChange(n)+(active===n.key?' active':'')+'">'+nodeLabel(n)+badge(nodeChange(n))+'</button>').join('');let html='';function walk(key,depth,seen=new Set()){if(seen.has(key))return;seen.add(key);for(const n of DATA.nodes.filter(n=>n.parent===key).sort((a,b)=>a.order-b.order)){html+=button(n,depth);if(expanded.has(n.key)||n.page===selected||descendants(n.key).some(d=>d.page===selected))walk(n.key,depth+1,seen)}}if(active)walk(active,0);else html=roots.map(n=>button(n,0)).join('');$('tree').innerHTML=html}
105944
- function rootFor(page){let node=DATA.nodes.find(n=>n.page===page);const seen=new Set();while(node?.parent&&!seen.has(node.key)){seen.add(node.key);node=DATA.nodes.find(n=>n.key===node.parent)}return node?.key||null}
105945
- function showPage(id){selected=id;active=rootFor(id)||active;render()}
105946
- function localizeBody(){document.querySelectorAll('[data-label]').forEach(el=>{el.textContent=t(el.dataset.label)})}
105947
- function workspaceTree(){const tree={};for(const p of candidates){let node=tree;for(const part of ('knowledge/'+p.path).split('/'))node=node[part]??=( {} );node.$page=p}function lines(node,level=0){return Object.entries(node).filter(([k])=>k!=='$page').sort(([a],[b])=>a.localeCompare(b)).map(([k,v])=>'<div style="padding-left:'+level*18+'px">'+(v.$page?'<button data-page="'+escape(v.$page.id)+'">'+escape(k)+'</button>'+badge(v.$page.change)+(state(v.$page)!=='pending'?badge(state(v.$page)):''):escape(k)+'/')+'</div>'+lines(v,level+1)).join('')}return '<div class="workspace-tree">'+lines(tree)+'</div>'}
105948
- function renderHome(){const c=totals();$('article').innerHTML='<h1>'+t('home')+'</h1><p class="stats">'+candidates.length+' '+(language==='zh'?'篇候选正文':'candidate pages')+' · '+c.approved+' '+t('approved')+' '+badge('new')+' '+badge('modify')+' '+badge('removed')+'</p>'+(DATA.navigationBaseline==='current'?'<p class="note">'+t('baseline')+'</p>':'')+(DATA.nodes.some(n=>n.key==='review-unplaced')?'<p class="note">'+t('placement')+'</p>':'')+'<h2>'+t('navigate')+'</h2>'+DATA.nodes.filter(n=>!n.parent).map(n=>{const ids=new Set([n,...descendants(n.key)].map(x=>x.page));const pages=candidates.filter(p=>ids.has(p.id));return pages.length?'<section class="home-group"><h3>'+nodeLabel(n)+badge(nodeChange(n))+'</h3><div class="cards">'+pages.map(p=>'<button data-page="'+escape(p.id)+'">'+escape(p.title)+badge(p.change)+(state(p)!=='pending'?badge(state(p)):'')+'</button>').join('')+'</div></section>':''}).join('')+(DATA.nodes.some(n=>n.removed)?'<h2>'+t('removedNav')+'</h2><ul>'+DATA.nodes.filter(n=>n.removed).map(n=>'<li class="removed">'+nodeLabel(n)+badge('removed')+'</li>').join('')+'</ul>':'')+'<h2>'+t('files')+'</h2>'+workspaceTree()}
105949
- function controls(){const p=DATA.pages.find(p=>p.id===selected),s=p?state(p):'pending';$('footer').hidden=!p?.candidate_id;if(!p?.candidate_id)return;$('revision-note').value=notes.get(p.candidate_id)||'';$('revision-note').placeholder=t('note');$('revision-note').disabled=s==='approved'||s==='rejected';for(const [id,v,label]of[['revise-btn','revised','revise'],['reject-btn','rejected','reject'],['approve-btn','approved','approve']]){const b=$(id);b.disabled=s!=='pending'&&s!==v;b.className='btn '+(s===v?'chosen':v==='approved'?'primary':'');b.innerHTML=s===v?'<span class="normal">'+t(v)+'</span><span class="hover-label">'+t('cancel')+'</span>':t(label);b.title=s===v?t('cancel'):''}}
105950
- function render(){document.body.classList.toggle('home',selected===null);$('article').classList.toggle('review-new-page',DATA.pages.some(p=>p.id===selected&&p.change==='new'));renderNav();updateCounts();if(selected===null)renderHome();else{const p=DATA.pages.find(p=>p.id===selected);$('article').innerHTML=p?'<h1>'+escape(p.title)+badge(p.change)+(p.candidate_id&&state(p)!=='pending'?badge(state(p)):'')+'</h1>'+(p.previousPath?'<p class="note">'+escape(p.previousPath)+' → '+escape(p.path)+'</p>':'')+(p.candidate_id?p.html:p.html?'<p class="note">'+t('approvedContext')+'</p>'+p.html:'<div class="review-omitted">'+t('unchanged')+'</div>')+(p.sources.length?'<details><summary>'+(language==='zh'?'来源引用':'Sources')+'</summary><ul>'+p.sources.map(s=>'<li>'+escape(s)+'</li>').join('')+'</ul></details>':''):''}controls();localizeBody();globalThis.contextDiagramViewer?.render($('article'),{dark:document.body.classList.contains('dark'),language})}
105951
- function setDecision(id,value){const current=decisions.get(id);if(current===value){decisions.delete(id);notes.delete(id)}else if(!current){if(value==='revised'){$('revision-note').focus();return}decisions.set(id,value)}render()}
105952
- function setAllDecision(value){for(const p of candidates)if(!decisions.has(p.candidate_id))decisions.set(p.candidate_id,value);render()}
105953
- function payloadText(){const statuses=ordered.map(p=>state(p));return feedbackCodec.encode({scope:SCOPE.label,idsHash:SCOPE.ids_sha256,contentHash:SCOPE.candidates_sha256,baselineHash:DATA.baselineHash,statuses,repairs:ordered.flatMap((p,index)=>state(p)==='revised'?[{index,instruction:notes.get(p.candidate_id)}]:[])})}
105954
- async function copyPayload(){dismissGuide();const c=totals();$('copy-warning').textContent='';$('payload').value='';if(c.pending===candidates.length){$('copy-title').textContent=t('noSelection');$('payload').value=''}else{try{const text=payloadText();$('payload').value=text;await navigator.clipboard.writeText(text);$('copy-title').textContent=t('copied')}catch(e){$('copy-title').textContent=t('failed');if(!$('payload').value)$('payload').value=String(e.message)}}$('copy-summary').textContent=t('approved')+' '+c.approved+' · '+t('rejected')+' '+c.rejected+' · '+t('revised')+' '+c.revised+' · '+t('notReviewed')+' '+c.pending;$('copy-instructions').textContent=t('instructions');$('copy-warning').textContent=Array.from($('payload').value).length>1000?t('long'):'';$('copy-dialog').showModal()}
105955
- let guideTimer;function dismissGuide(){clearInterval(guideTimer);$('copy-guide').hidden=true}
105956
- function labels(){$('all-approved').textContent=t('allApprove');$('all-rejected').textContent=t('allReject');$('payload-open').textContent=t('copy');$('guide-text').textContent=t('guide');$('guide-close').textContent=t('known');$('bulk-title').textContent=t('allApprove');$('bulk-message').textContent=t('confirmAll');$('bulk-cancel').textContent=t('cancel');$('bulk-confirm').textContent=t('confirm');$('payload-close').textContent=t('close');$('language').textContent=language==='zh'?'EN':'中文'}
105957
- document.addEventListener('click',e=>{const b=e.target.closest('button');if(!b)return;if(b.dataset.page){showPage(b.dataset.page);window.scrollTo(0,0)}if(b.dataset.root){active=b.dataset.root;const n=DATA.nodes.find(n=>n.key===active);selected=n?.page||[...descendants(active)].find(d=>d.page)?.page||null;render()}if(b.dataset.node){const n=DATA.nodes.find(n=>n.key===b.dataset.node);if(n.page)showPage(n.page);else{expanded.has(n.key)?expanded.delete(n.key):expanded.add(n.key);renderNav()}}});
105958
- $('home').onclick=()=>{selected=null;active=null;render();window.scrollTo(0,0)};
105959
- $('revision-note').oninput=e=>{const p=DATA.pages.find(p=>p.id===selected);if(!p?.candidate_id)return;const value=e.target.value;notes.set(p.candidate_id,value);if(value.trim())decisions.set(p.candidate_id,'revised');else decisions.delete(p.candidate_id);controls();renderNav();updateCounts();$('article').querySelectorAll('h1 > .approved,h1 > .revised,h1 > .rejected').forEach(e=>e.remove());$('article').querySelector('h1')?.insertAdjacentHTML('beforeend',value.trim()?badge('revised'):'')};
105960
- for(const [id,value]of[['revise-btn','revised'],['reject-btn','rejected'],['approve-btn','approved']])$(id).onclick=()=>{const p=DATA.pages.find(p=>p.id===selected);if(p?.candidate_id)setDecision(p.candidate_id,value)};
105961
- const addedRoots=DATA.nodes.filter(n=>!n.parent&&n.change==='new'&&!n.removed&&n.key!=='review-unplaced');
105962
- let bulkTimer,bulkDeadline=0;
105963
- function updateBulkConfirmation(){const remaining=addedRoots.length?Math.max(0,Math.ceil((bulkDeadline-Date.now())/1000)):0;$('bulk-confirm').disabled=addedRoots.length>0&&(!$('bulk-ack').checked||remaining>0);$('bulk-confirm').textContent=t('confirm')+(remaining?' ('+remaining+'s)':'');return remaining}
105964
- function openBulkConfirmation(){clearInterval(bulkTimer);$('bulk-ack').checked=false;$('bulk-roots').hidden=!addedRoots.length;$('bulk-roots-title').textContent=t('newRoots');$('bulk-roots-list').innerHTML=addedRoots.map(n=>'<li>'+escape(nodeTitle(n))+'</li>').join('');$('bulk-ack-label').textContent=t('ackRoots');bulkDeadline=Date.now()+8000;updateBulkConfirmation();$('bulk-dialog').showModal();if(addedRoots.length)bulkTimer=setInterval(()=>{if(!updateBulkConfirmation())clearInterval(bulkTimer)},200)}
105965
- $('bulk-ack').onchange=updateBulkConfirmation;
105966
- $('bulk-dialog').onclose=()=>clearInterval(bulkTimer);
105967
- $('all-approved').onclick=openBulkConfirmation;$('bulk-confirm').onclick=()=>{updateBulkConfirmation();if(!$('bulk-dialog').open||$('bulk-confirm').disabled)return;setAllDecision('approved');clearInterval(bulkTimer);$('bulk-dialog').close()};$('bulk-cancel').onclick=()=>{clearInterval(bulkTimer);$('bulk-dialog').close()};$('all-rejected').onclick=()=>setAllDecision('rejected');$('payload-open').onclick=copyPayload;$('payload-close').onclick=()=>$('copy-dialog').close();$('guide-close').onclick=dismissGuide;$('theme').onclick=()=>{document.body.classList.toggle('dark');globalThis.contextDiagramViewer?.render($('article'),{dark:document.body.classList.contains('dark'),language})};$('language').onclick=()=>{language=language==='zh'?'en':'zh';labels();render()};
105968
- labels();render();const deadline=Date.now()+10000;guideTimer=setInterval(()=>{const left=Math.max(0,Math.ceil((deadline-Date.now())/1000));$('guide-countdown').textContent=left+'s';if(!left)dismissGuide()},250);
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,.badge.approved{color:var(--green);background:color-mix(in srgb,var(--green) 10%,var(--bg))}.badge.modify,.badge.revised{color:var(--amber);background:color-mix(in srgb,var(--amber) 10%,var(--bg))}.badge.removed,.badge.rejected{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}.counter{position:relative;padding:8px;white-space:nowrap;font-size:12px;font-weight:700;color:var(--blue)}.counter-pop{display:none;position:absolute;top:100%;right:0;min-width:320px;padding:15px;background:var(--bg);border:1px solid var(--line);border-radius:10px;box-shadow:0 8px 30px #17244220;color:var(--text);font-weight:400}.counter:hover .counter-pop,.counter:focus .counter-pop{display:block}.counter-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;font-size:11px}.counter-grid b{display:block;font-size:20px}.counter-pop p{border-top:1px solid var(--line);padding-top:10px}.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 input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--text);font-size:14px;padding:8px 0}footer .btn{min-width:76px}.chosen{color:var(--blue);border-color:var(--blue);background:var(--bg)}.hover-label{display:none}.chosen:hover .normal{display:none}.chosen:hover .hover-label{display:inline}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)}#copy-warning{color:var(--amber)}.bulk-ack{display:flex;align-items:center;gap:8px;font-size:13px}.bulk-ack input{accent-color:var(--blue)}#bulk-roots-list{margin:8px 0 12px;padding-left:22px;color:var(--red)}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}.guide{position:absolute;right:0;top:calc(100% + 18px);width:300px;background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:15px;box-shadow:0 10px 35px #17244224;font-size:12px}.guide:before{content:'';position:absolute;top:-7px;right:42px;width:12px;height:12px;background:var(--bg);border-top:1px solid var(--line);border-left:1px solid var(--line);transform:rotate(45deg)}#guide-countdown{float:right;color:var(--muted);font-size:11px}.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}}
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 = join90(".tmp", "context-runtime", "review");
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(candidates, reviewScope, model, diagramScript) {
106018
- const scope2 = {
106019
- label: reviewScope,
106020
- ids_sha256: candidateIdsHash(candidates.map((c) => c.record.candidate_id).sort()),
106021
- candidates_sha256: candidateSetHash(candidates.map((c) => c.record))
106022
- };
106023
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeReviewHtml(model.title)} · Review</title><style>${REVIEW_SITE_STYLES}${model.themeCss ?? ""}</style></head><body>
106024
- <header><button id="home">${escapeReviewHtml(model.title)}</button><nav id="top"></nav><div class="tools"><div class="counter" tabindex="0"><span id="counts"></span><div class="counter-pop" id="counter-pop"></div></div><button class="btn" id="all-approved"></button><button class="btn" id="all-rejected"></button><button class="btn primary" id="payload-open"></button><div class="guide" id="copy-guide"><span id="guide-countdown">10s</span><p id="guide-text"></p><button class="btn" id="guide-close"></button></div></div><button class="btn" id="theme" aria-label="Theme">◐</button><button class="btn" id="language"></button></header>
106025
- <div class="layout"><aside id="tree"></aside><main><article id="article"></article></main></div>
106026
- <footer id="footer" hidden><input id="revision-note" aria-label="Revision instructions"><button class="btn" id="revise-btn"></button><button class="btn" id="reject-btn"></button><button class="btn primary" id="approve-btn"></button></footer>
106027
- <dialog id="bulk-dialog"><h2 id="bulk-title"></h2><p id="bulk-message"></p><div id="bulk-roots" hidden><strong id="bulk-roots-title"></strong><ul id="bulk-roots-list"></ul><label class="bulk-ack"><input type="checkbox" id="bulk-ack"><span id="bulk-ack-label"></span></label></div><div class="dialog-actions"><button class="btn" id="bulk-cancel"></button><button class="btn primary" id="bulk-confirm"></button></div></dialog>
106028
- <dialog id="copy-dialog"><h2 id="copy-title"></h2><p id="copy-summary"></p><p id="copy-instructions"></p><textarea id="payload" readonly aria-label="Review code"></textarea><p id="copy-warning"></p><button class="btn" id="payload-close"></button></dialog>
106029
- ${diagramScript ? `<script>${diagramScript}</script>` : ""}<script>const DATA=${reviewHtmlJson(model)};const SCOPE=${reviewHtmlJson(scope2)};const feedbackCodec=(${createReviewFeedbackCodec.toString()})();${REVIEW_SITE_CLIENT}</script></body></html>`;
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 join90(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
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 readFile71(join90(here, "browser/diagrams.js"), "utf8");
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 readFile71(resolve28(here, "../../dist/browser/diagrams.js"), "utf8");
106393
+ diagramScript = await readFile72(resolve28(here, "../../dist/browser/diagrams.js"), "utf8");
106054
106394
  }
106055
106395
  }
106056
- await writeFile25(outPath, renderReviewHtml(candidates, reviewScope, model, diagramScript), "utf8");
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 readFile72 } from "node:fs/promises";
106077
- import { join as join91 } from "node:path";
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 = join91(input.projectRoot, ".tmp", "context-runtime", "review", `current-${setDigest.slice("sha256:".length)}`);
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 = join91(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
106162
- const existing = await readFile72(path4, "utf8").catch((error) => {
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 = join91(root2, "index.md");
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 join92(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
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 = join92(input.projectRoot, path3);
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: join92(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
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(`@${join92(found.projectRoot, continuation.path)}`)} --format json`;
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: `@${join92(found.projectRoot, continuation.path)}`
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: `@${join92(found.projectRoot, continuation.path)}`,
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 readFile73 } from "node:fs/promises";
106495
- import { isAbsolute as isAbsolute16, join as join93, sep as sep6, resolve as resolve29 } from "node:path";
106496
- var RECEIPT_DIRECTORY = join93(".tmp", "context-runtime", "workflow", "read-receipts");
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 readFile73(resolve29(cwd, value.slice(1)), "utf8");
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 join99 } from "node:path";
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 = join99(".tmp", "context-runtime", "runs", `${input.runId}.json`);
106780
- const absPath = join99(input.projectRoot, relPath);
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 readFile80 } from "node:fs/promises";
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 readFile78 } from "node:fs/promises";
107884
- import { join as join100 } from "node:path";
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 = join100("knowledge", input.record.path);
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 = join100(input.projectRoot, relPath);
107927
- const existing = existsSync25(absPath) ? await readFile78(absPath, "utf8") : undefined;
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 readFile78(join100(input.projectRoot, "knowledge", previousPath), "utf8") };
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 readFile78(join100(projectRoot, relPath), "utf8");
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 copy a fresh scoped payload." : `Rerun context review html ${payload.collection} --format json and copy a fresh scoped payload.`
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 && payload.encoded_statuses === 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 copy a fresh scoped payload."
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 copy a fresh payload.`
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.feedback_repairs ?? []).map((repair) => {
108119
- const row = scoped[repair.index];
108120
- if (!Number.isInteger(repair.index) || !row || repairIds.has(repair.index) || !repair.instruction.trim() || input.payload.encoded_statuses?.[repair.index] !== "pending")
108121
- throw new Error("Invalid review revision request");
108122
- repairIds.add(repair.index);
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 = join100("knowledge", row.path);
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 readFile78(join100(input.projectRoot, path3), "utf8");
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 readFile79, writeFile as writeFile28 } from "node:fs/promises";
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 readFile79(page.path, "utf8");
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 join101 } from "node:path";
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 : join101(input.projectRoot, 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: "Copy a fresh review payload from context review html <collection>."
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 readFile80(filePath2, "utf8");
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 review Payload copied from the review HTML page."
109002
+ next: "Pass the scoped JSON or JSONL decisions prepared from the current review resource."
108521
109003
  });
108522
109004
  }
108523
- if (raw.trim().startsWith("CR")) {
108524
- try {
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}` : "Apply the exact decision Payload returned by this report."
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 --force, not both", {
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 --force user confirmation", {
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
- const decisionSource = input.managed === true ? "managed-session" : "explicit-user-force-approval";
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 force approval",
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" : "force-approved 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", "--force", "--verbose"]));
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", "--force", "--verbose"]));
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
- force: options.flags.has("--force"),
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 readFile81 } from "node:fs/promises";
109366
- import { join as join102 } from "node:path";
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 readFile81(await safeProjectTarget(input.projectRoot, join102("knowledge", path3)), "utf8");
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 readFile82 } from "node:fs/promises";
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 readFile82(path3, "utf8");
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 join106 } from "node:path";
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 = join106(input.projectRoot, ".tmp/context-runtime/action-results");
110072
- const resultFile = join106(root2, `${digest7}.json`);
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 join107 } from "node:path";
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 = join107(cacheRoot, mp.name);
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 = join107(mpDir, pl.name);
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 = join107(plDir, ver.name);
110520
- const markerPath = join107(verDir, ORPHAN_MARKER);
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 join107(home, ".claude", "plugins", "cache");
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 join108 } from "node:path";
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 = join108(dir, "package.json");
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 readFile98 } from "node:fs/promises";
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 readFile88 } from "node:fs/promises";
110977
- import { join as join110 } from "node:path";
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 readFile87 } from "node:fs/promises";
110984
- import { join as join109 } from "node:path";
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 ?? join109(source2.materializedAt, "manifest.json");
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 readFile87(join109(input.projectRoot, manifest), "utf8")), input.source.name);
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 ?? join110(source2.materializedAt, "manifest.json");
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 = join110(input.projectRoot, manifest);
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 readFile88(manifestPath, "utf8")), input.source.name);
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(join110(input.projectRoot, input.source.materializedAt, path3)));
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 readFile89, realpath as realpath12 } from "node:fs/promises";
111273
- import { basename as basename13, extname as extname15, isAbsolute as isAbsolute21, join as join111, relative as relative28, resolve as resolve36 } from "node:path";
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 readFile89(join111(projectRoot, registryPath2), "utf8");
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(join111(input.projectRoot, DEFAULT_FILE_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
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(join111(input.projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
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 readFile90 } from "node:fs/promises";
111579
- import { join as join112 } from "node:path";
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 = join112(projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH);
112034
+ const registryPath2 = join113(projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH);
111587
112035
  let committedSources;
111588
112036
  try {
111589
- const text10 = await readFile90(registryPath2, "utf8");
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 readFile91 } from "node:fs/promises";
111708
- import { join as join113, resolve as resolve37 } from "node:path";
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: join113(directory, "input.json"), statePath: join113(directory, "state.json") };
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 = join113(current2, part);
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 readFile91(inputPath, "utf8"));
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 readFile91(journal.statePath, "utf8"));
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 join115, relative as relative30 } from "node:path";
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 readFile92, readdir as readdir28, rm as rm25, writeFile as writeFile29 } from "node:fs/promises";
112497
- import { join as join114, relative as relative29, resolve as resolve38, sep as sep9 } from "node:path";
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 = join114(current2, part);
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 readFile92(path3, "utf8"));
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, join114(directory, "pages"));
112541
- await mkdir37(join114(directory, "pages"), { recursive: true });
112542
- for (const file of await readdir28(join114(directory, "pages"))) {
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(join114(directory, "pages", file));
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 = join114(directory, "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(join114(leases, latest));
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 = join114(leases, `${String(sequence).padStart(12, "0")}.json`);
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 = join114(leases, `.lease-${owner.token}`);
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 = join115(input.projectRoot, ".tmp", "context-runtime", "wiki-discovery", jobId);
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 = join115(directory, "job.json");
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(join115(directory, "pages", `${key}.json`), receipt2);
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 = join115(directory, "manifest.json");
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 readFile93, readdir as readdir29, rm as rm26 } from "node:fs/promises";
112967
- import { isAbsolute as isAbsolute22, join as join116, relative as relative31, resolve as resolve39, sep as sep10 } from "node:path";
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 = join116(input.projectRoot, input.path);
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 readFile93(absolutePath, "utf8"));
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 = join116(projectRoot, path3);
113129
- const document4 = existsSync31(absolutePath) ? import_yaml51.default.parse(await readFile93(absolutePath, "utf8")) : { sources: [] };
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 ?? join116(source2.materializedAt, "manifest.json");
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 readFile93(absoluteRemovals[0], "utf8") : null,
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 = join116(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
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 readFile93(fingerprintPath, "utf8"));
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 = join116(projectRoot, ".tmp/context-runtime/extract/custom-phase-candidates.json");
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 readFile93(phaseOwnershipPath, "utf8"));
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 = join116(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
113781
+ const symbolPath = join117(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
113334
113782
  if (existsSync31(symbolPath)) {
113335
- const parsed = JSON.parse(await readFile93(symbolPath, "utf8"));
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 = join116(projectRoot, ".tmp/context-runtime/extract/candidates");
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 = join116(directory, entry.name);
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 readFile93(path3, "utf8"));
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: join116(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
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 readFile94 } from "node:fs/promises";
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 readFile94(await safeProjectTarget(projectRoot, "package.json"), "utf8"));
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 readFile94(target, "utf8");
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 readFile98(isAbsolute23(trimmed) ? resolve41(trimmed) : resolve41(projectRoot, trimmed), "utf8");
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 join118, resolve as resolve42 } from "node:path";
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 = join118(root2, directory.name, "SKILL.md");
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 readFile99, readdir as readdir31, rename as rename10, rm as rm27, writeFile as writeFile30 } from "node:fs/promises";
114502
- import { dirname as dirname47, join as join119, resolve as resolve44 } from "node:path";
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 = join119(dir, name3);
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 readFile99(path3));
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 = join119(targetRoot, "skills");
115001
+ const skillsRoot = join120(targetRoot, "skills");
114554
115002
  await checkParents(skillsRoot);
114555
- const sourceRoot2 = join119(root2, "skills");
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(join119(root2, "claude", "commands"))) {
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 = join119(targetRoot, "commands");
115011
+ const commandsRoot = join120(targetRoot, "commands");
114564
115012
  await checkParents(commandsRoot);
114565
- for (const entry of await readdir31(join119(root2, agent, "commands"), { withFileTypes: true })) {
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 readFile99(join119(root2, agent, "commands", entry.name), "utf8");
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(join119(root2, agent, "commands", entry.name));
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 = join119(commandsRoot, entry.name);
114577
- const marker = join119(commandsRoot, `.context-${entry.name}.json`);
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 readFile99(marker, "utf8"));
115034
+ previous3 = JSON.parse(await readFile100(marker, "utf8"));
114587
115035
  } catch {
114588
115036
  conflict(target);
114589
115037
  }
114590
- const current2 = createHash37("sha256").update(await readFile99(target)).digest("hex");
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(join119(root2, agent, "commands"));
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(join119(sourceRoot2, entry.name, "SKILL.md")))
115050
+ if (!entry.isDirectory() || !await stat11(join120(sourceRoot2, entry.name, "SKILL.md")))
114603
115051
  continue;
114604
- const source2 = join119(sourceRoot2, entry.name);
114605
- const target = join119(skillsRoot, entry.name);
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 = join119(target, MARKER);
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 readFile99(markerPath, "utf8"));
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(join119(skillsRoot, ".context-install-"));
114635
- const candidate = join119(staging, "candidate");
114636
- const previous3 = join119(staging, "previous");
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 = join119(candidate, "SKILL.md");
114642
- const content3 = await readFile99(skillFile, "utf8");
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(join119(candidate, MARKER), JSON.stringify({ owner: "context-plugin-local", digest: await digest8(candidate) }) + `
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(join119(dirname47(command3.target), ".context-install-"));
114675
- await writeFile30(join119(staging, "command"), command3.content);
114676
- await writeFile30(join119(staging, "marker"), JSON.stringify({ owner: "context-plugin-local", digest: command3.hash }) + `
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(join119(staging, "command"), command3.target);
114679
- await rename10(join119(staging, "marker"), command3.marker);
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 join120, resolve as resolve45 } from "node:path";
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 = join120(repo, HOST_DIRS[agent]);
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: join120(repo, HOST_DIRS[agent]) })) : [{ path: join120(repo, ".agents") }];
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 join122, resolve as resolve46 } from "node:path";
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 readFile100, rename as rename11, rm as rm28, writeFile as writeFile31 } from "node:fs/promises";
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 join121 } from "node:path";
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() || join121(homedir2(), ".codex");
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 join121(home, ".claude", "plugins", "cache");
115284
+ return join122(home, ".claude", "plugins", "cache");
114837
115285
  }
114838
115286
  function sharedSkillsRoot() {
114839
- return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join121(homedir2(), ".agents", "skills");
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() || join121(homedir2(), ".claude", "skills");
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() || join121(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
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 = join121(codexHome(), "config.toml");
114897
- const current2 = await readFile100(configPath, "utf8").catch(() => "");
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 = join121(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
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(join121(cacheRoot, version3), { recursive: true, force: true })));
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 = join121(cacheRoot, marketplace.name, PLUGIN_NAME);
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 = join121(pluginDir, version3.name);
114957
- if (!existsSync34(join121(versionDir, ORPHAN_MARKER2)))
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 = join121(cacheRoot, marketplace.name);
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 = join121(cacheRoot, MARKETPLACE_NAME, pluginName);
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 readFile100(join121(root2, "claude", ".claude-plugin", "plugin.json"), "utf8").then((content3) => JSON.parse(content3)).catch(() => null);
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 = join121(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
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(join121(pluginDir, version3), { recursive: true, force: true })));
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 = join121(codexHome(), "config.toml");
115531
+ const configPath = join122(codexHome(), "config.toml");
115084
115532
  await mkdir40(dirname48(configPath), { recursive: true });
115085
- const current2 = await readFile100(configPath, "utf8").catch(() => "");
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 = join121(codexHome(), "config.toml");
115540
+ const configPath = join122(codexHome(), "config.toml");
115093
115541
  await mkdir40(dirname48(configPath), { recursive: true });
115094
- const current2 = await readFile100(configPath, "utf8").catch(() => "");
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 = join121(root2, "codex", ".codex-plugin", "plugin.json");
115102
- const manifest = JSON.parse(await readFile100(manifestPath, "utf8"));
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 join121(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
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 = join121(root2, "codex");
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 = join121(root2, "skills");
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 = join121(skillsRoot, entry.name, "SKILL.md");
115598
+ const skillPath = join122(skillsRoot, entry.name, "SKILL.md");
115151
115599
  if (!existsSync34(skillPath))
115152
115600
  continue;
115153
- const skill = await readFile100(skillPath, "utf8");
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 = join121(root2, "skills");
115613
+ const sourceRoot2 = join122(root2, "skills");
115166
115614
  for (const name3 of await bundledProviderSkillNames(root2)) {
115167
- const source2 = join121(sourceRoot2, name3);
115168
- const target = join121(targetRoot, name3);
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 = join121(root2, "cursor");
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(join121(codexHome(), "config.toml"))} registers local marketplace ${shellQuote8(MARKETPLACE_NAME)}`,
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(join121(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
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(join122(dir, "plugins"));
115295
- candidates.push(join122(dir, "dist", "plugins"));
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(join122(root2, ".claude-plugin", "marketplace.json")) && existsSync35(join122(root2, ".agents", "plugins", "marketplace.json")) && existsSync35(join122(root2, "claude", ".claude-plugin", "plugin.json")) && existsSync35(join122(root2, "codex", ".codex-plugin", "plugin.json")) && existsSync35(join122(root2, "cursor", ".cursor-plugin", "plugin.json")) && existsSync35(join122(root2, "skills"));
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 = join123(dir, "docs", "quickstart.md");
115995
+ const candidate = join124(dir, "docs", "quickstart.md");
115548
115996
  if (existsSync36(candidate))
115549
115997
  return candidate;
115550
- const pkg = join123(dir, "package.json");
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 join123(dirname50(fileURLToPath11(import.meta.url)), "docs", "quickstart.md");
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 a copied review code (all segments in one file) or JSON/JSONL decisions").option("--format <format>", "output format: text | json", "text").action(async (payloadInput, options) => {
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 force authority").option("--all", "approve all current draft candidates across internal collections").option("--managed", "assert explicit current-conversation managed approval").option("--force", "assert the user's explicit current-conversation force approval").option("--verbose", "include candidate ids and materialized page paths in JSON output").option("--format <format>", "output format: text | json", "text").action(async (collection, options) => {
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",