@c4a/context-cli 0.7.35 → 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.
Files changed (34) hide show
  1. package/cli.js +980 -498
  2. package/indexers/contracts/profile-contract.json +245 -245
  3. package/indexers/release-manifest.json +1 -1
  4. package/package.json +12 -12
  5. package/plugins/VERSION +1 -1
  6. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  7. package/plugins/claude/commands/context.md +44 -8
  8. package/plugins/claude/skills/context-plan/SKILL.md +18 -6
  9. package/plugins/claude/skills/context-plan/references/project-planning.md +74 -10
  10. package/plugins/claude/skills/context-plan/templates/PLAN.md +9 -4
  11. package/plugins/codex/.codex-plugin/plugin.json +2 -2
  12. package/plugins/codex/skills/context/SKILL.md +44 -8
  13. package/plugins/codex/skills/context-plan/SKILL.md +18 -6
  14. package/plugins/codex/skills/context-plan/references/project-planning.md +74 -10
  15. package/plugins/codex/skills/context-plan/templates/PLAN.md +9 -4
  16. package/plugins/cursor/.cursor-plugin/plugin.json +1 -1
  17. package/plugins/cursor/commands/c4a-context.md +44 -8
  18. package/plugins/cursor/skills/context-plan/SKILL.md +18 -6
  19. package/plugins/cursor/skills/context-plan/references/project-planning.md +74 -10
  20. package/plugins/cursor/skills/context-plan/templates/PLAN.md +9 -4
  21. package/plugins/skills/context/SKILL.md +44 -8
  22. package/plugins/skills/context-plan/SKILL.md +18 -6
  23. package/plugins/skills/context-plan/references/project-planning.md +74 -10
  24. package/plugins/skills/context-plan/templates/PLAN.md +9 -4
  25. package/providers/context/actions/apply-confirmed-review.yaml +5 -0
  26. package/providers/context/codes.yaml +1 -1
  27. package/providers/context/graphs/workspace.yaml +2 -2
  28. package/providers/context/manifest.json +18 -18
  29. package/providers/context/provider.yaml +1 -1
  30. package/providers/context/resources/dialogue/knowledge-review.md +55 -52
  31. package/providers/context/resources/manuals/guides/knowledge-updates.md +14 -10
  32. package/providers/context/resources/procedures/knowledge-review.md +29 -25
  33. package/providers/context/resources/procedures/knowledge-updates.md +14 -10
  34. package/providers/context/actions/apply-forced-review.yaml +0 -5
package/cli.js CHANGED
@@ -79425,6 +79425,33 @@ var init_productionStage = __esm(() => {
79425
79425
  terminal = new Set(["accepted", "excluded", "replaced"]);
79426
79426
  });
79427
79427
 
79428
+ // src/project/productionSourceSummary.ts
79429
+ function productionSourceSummary(stage) {
79430
+ const pending = new Set(stage.pending_scopes);
79431
+ const unavailable = new Set(stage.gaps.map((gap) => gap.scope));
79432
+ return {
79433
+ stage_source_count: new Set(stage.scopes.map((source2) => source2.scope)).size,
79434
+ pending_scope_count: pending.size,
79435
+ unavailable_source_count: unavailable.size,
79436
+ pending_without_read_failure_count: [...pending].filter((scope2) => !unavailable.has(scope2)).length,
79437
+ planned_article_count: new Set(stage.tasks.filter((task) => !["excluded", "replaced"].includes(task.status)).map((task) => task.path)).size,
79438
+ unavailable_sources: stage.gaps,
79439
+ meaning: "Pending scopes are remaining investigation, not missing knowledge or failed captures. Unavailable sources have current material/baseline read failures; inspect their reasons. Counts can overlap and must not be added. Restore required code dependencies even for document-led work."
79440
+ };
79441
+ }
79442
+ function productionSourceSummaryMarkdown(stage) {
79443
+ const summary = productionSourceSummary(stage);
79444
+ return [
79445
+ "## Source availability and progress",
79446
+ "",
79447
+ `Stage sources: ${summary.stage_source_count}; pending investigation scopes: ${summary.pending_scope_count}; sources with read failures: ${summary.unavailable_source_count}; pending scopes without read failures: ${summary.pending_without_read_failure_count}; planned articles: ${summary.planned_article_count}.`,
79448
+ summary.meaning,
79449
+ "",
79450
+ ...summary.unavailable_sources.map((gap) => `- ${gap.scope}: ${gap.reason}`),
79451
+ ""
79452
+ ];
79453
+ }
79454
+
79428
79455
  // src/project/productionStageStore.ts
79429
79456
  var exports_productionStageStore = {};
79430
79457
  __export(exports_productionStageStore, {
@@ -79544,8 +79571,7 @@ function productionPlanMarkdown(stage) {
79544
79571
  "Then present the updated report and apply context.gate.work_start_scope; an old decision does not approve changed article goals.",
79545
79572
  ""
79546
79573
  ] : [],
79547
- ...stage.gaps.map((gap) => `- ${gap.scope}: ${gap.reason}`),
79548
- ""
79574
+ ...productionSourceSummaryMarkdown(stage)
79549
79575
  ].join(`
79550
79576
  `);
79551
79577
  }
@@ -81069,8 +81095,9 @@ async function resolveProductionExclusions(requirements, readBaseline) {
81069
81095
  import { randomUUID as randomUUID3 } from "node:crypto";
81070
81096
  import { readFile as readFile34, access as access3 } from "node:fs/promises";
81071
81097
  import { join as join39 } from "node:path";
81072
- function refreshRequired(stage, message) {
81098
+ function refreshRequired(stage, message, detail = {}) {
81073
81099
  return new ContextError(ExitCode.UserError, message, {
81100
+ ...detail,
81074
81101
  category: ErrorCategory.UserInputInvalid,
81075
81102
  reason_code: "production-planning-refresh-required",
81076
81103
  next_action: { command: `context action prepare-current --revision ${stage} --format json` }
@@ -81169,6 +81196,7 @@ Restore the authorized source and retry preparation.
81169
81196
  `Existing reader topics: ${join39(directory, "guidance/existing-articles.md")}`,
81170
81197
  `Stage: ${stage.id}`,
81171
81198
  "",
81199
+ ...productionSourceSummaryMarkdown(stage),
81172
81200
  ...scopes.map((scope2) => `- ${scope2.scope}: ${join39(directory, productionSourceFile(scope2.scope))}`),
81173
81201
  "",
81174
81202
  "Use code skeletons and document outlines to identify the authorized capability families and document tasks, then selectively read full material to decide reader topics. Navigation is not a complete feature inventory. Keep unchecked scope pending; do not parse all code or maintain per-symbol disposition just to plan.",
@@ -81192,7 +81220,7 @@ Restore the authorized source and retry preparation.
81192
81220
  capabilities: productionCapabilitiesSchema.parse({}),
81193
81221
  materials: { ...prepared.materials, sources: sourceMaterials, guidance }
81194
81222
  });
81195
- return { stage_state: "active", next: { directory, mode: "single-agent" } };
81223
+ return { stage_state: "active", source_summary: productionSourceSummary(stage), next: { directory, mode: "single-agent" } };
81196
81224
  });
81197
81225
  }
81198
81226
  async function submitProductionPlan(input) {
@@ -81220,9 +81248,12 @@ async function submitProductionPlan(input) {
81220
81248
  throw new TypeError("Pending investigation must name unique authorized stage scopes");
81221
81249
  }
81222
81250
  if (stage.gaps.some((gap) => !pendingScopes.includes(gap.scope)))
81223
- throw refreshRequired(stage.id, "Keep unresolved material gaps in pending_scopes; restore the source if unavailable, then prepare the current stage to refresh its material snapshot");
81251
+ throw refreshRequired(stage.id, "Keep unresolved material gaps in pending_scopes. These are current material/baseline read failures, not evidence that knowledge was never captured. Restore required dependencies before preparing their snapshots; do not label every pending source as unavailable.", {
81252
+ source_summary: productionSourceSummary(stage),
81253
+ missing_pending_scopes: [...new Set(stage.gaps.filter((gap) => !pendingScopes.includes(gap.scope)).map((gap) => gap.scope))]
81254
+ });
81224
81255
  if (plan.articles.some((article) => article.sources.some((scope2) => stage.gaps.some((gap) => gap.scope === scope2)))) {
81225
- throw refreshRequired(stage.id, "An article requires unavailable material in the captured stage. Restore the source if needed, then prepare the current stage; independent source plans may proceed");
81256
+ throw refreshRequired(stage.id, "An article requires unavailable material in the captured stage. Restore the required source, including code dependencies of document-led work, then prepare the current stage; independent source plans may proceed.", { source_summary: productionSourceSummary(stage) });
81226
81257
  }
81227
81258
  for (const source2 of stage.scopes)
81228
81259
  if (!stage.gaps.some((gap) => gap.scope === source2.scope) && await productionSourceBaseline(input.projectRoot, source2.scope) !== source2.baseline) {
@@ -92313,11 +92344,11 @@ function reviewApproveCommand(observation) {
92313
92344
  }
92314
92345
  return "context review apply '<review-decisions-file>' --format json";
92315
92346
  }
92316
- function reviewForceApproveCommand(observation) {
92347
+ function reviewConfirmedApproveCommand(observation) {
92317
92348
  if (observation.draftCandidates <= 0 || observation.draftCollections.length === 0) {
92318
92349
  return;
92319
92350
  }
92320
- 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";
92321
92352
  }
92322
92353
  function workspaceRepairPlan(observation) {
92323
92354
  return { commands: [command("context verify --format json --compact", "read")] };
@@ -92441,8 +92472,8 @@ var init_workflowHostPlans = __esm(() => {
92441
92472
  commands: next === undefined ? [] : [command(next, "write", "agent-required")]
92442
92473
  };
92443
92474
  },
92444
- "context.review.force-approve-current": (observation) => {
92445
- const next = reviewForceApproveCommand(observation);
92475
+ "context.review.confirm-current": (observation) => {
92476
+ const next = reviewConfirmedApproveCommand(observation);
92446
92477
  return {
92447
92478
  commands: next === undefined ? [] : [command(next, "write")]
92448
92479
  };
@@ -98100,7 +98131,7 @@ var init_larkResourceCommand = __esm(() => {
98100
98131
 
98101
98132
  // src/lib/larkMediaDownload.ts
98102
98133
  import { readdir as readdir21, rm as rm20 } from "node:fs/promises";
98103
- import { join as join94 } from "node:path";
98134
+ import { join as join95 } from "node:path";
98104
98135
  async function downloadLarkMedia(input) {
98105
98136
  const shared = ["--as", input.identity, "--token", input.token, "--output", "./resource", "--format", "json"];
98106
98137
  try {
@@ -98110,7 +98141,7 @@ async function downloadLarkMedia(input) {
98110
98141
  if (!input.allowPreview || !isLarkResourcePermissionDenied(error))
98111
98142
  throw error;
98112
98143
  for (const entry of await readdir21(input.cwd))
98113
- await rm20(join94(input.cwd, entry), { recursive: true, force: true });
98144
+ await rm20(join95(input.cwd, entry), { recursive: true, force: true });
98114
98145
  await runLarkResourceCommand(input.runner, ["docs", "+media-preview", ...shared], { cwd: input.cwd });
98115
98146
  return "preview";
98116
98147
  }
@@ -98120,9 +98151,9 @@ var init_larkMediaDownload = __esm(() => {
98120
98151
  });
98121
98152
 
98122
98153
  // src/lib/larkWhiteboardExport.ts
98123
- 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";
98124
98155
  import { tmpdir } from "node:os";
98125
- import { join as join95 } from "node:path";
98156
+ import { join as join96 } from "node:path";
98126
98157
  function stableJson2(value) {
98127
98158
  if (value === undefined)
98128
98159
  return "null";
@@ -98152,7 +98183,7 @@ function canonicalWhiteboardPayload(value) {
98152
98183
  };
98153
98184
  }
98154
98185
  async function exportLarkWhiteboardRaw(runner2, identity2, token) {
98155
- const tempRoot = await mkdtemp4(join95(tmpdir(), "context-lark-whiteboard-"));
98186
+ const tempRoot = await mkdtemp4(join96(tmpdir(), "context-lark-whiteboard-"));
98156
98187
  try {
98157
98188
  await runLarkResourceCommand(runner2, [
98158
98189
  "whiteboard",
@@ -98169,7 +98200,7 @@ async function exportLarkWhiteboardRaw(runner2, identity2, token) {
98169
98200
  "--format",
98170
98201
  "json"
98171
98202
  ], { cwd: tempRoot });
98172
- const rawPayload = JSON.parse(await readFile74(join95(tempRoot, "raw.json"), "utf8"));
98203
+ const rawPayload = JSON.parse(await readFile75(join96(tempRoot, "raw.json"), "utf8"));
98173
98204
  return Buffer.from(`${stableJson2(canonicalWhiteboardPayload(rawPayload))}
98174
98205
  `, "utf8");
98175
98206
  } finally {
@@ -98208,8 +98239,8 @@ var init_larkImagePolicy = __esm(() => {
98208
98239
 
98209
98240
  // src/lib/larkResourceMaterialization.ts
98210
98241
  import { createHash as createHash28 } from "node:crypto";
98211
- import { mkdtemp as mkdtemp5, readFile as readFile75, readdir as readdir22, rm as rm22 } from "node:fs/promises";
98212
- 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";
98213
98244
  import { tmpdir as tmpdir2 } from "node:os";
98214
98245
  function countByKind(items, status) {
98215
98246
  const counts2 = new Map;
@@ -98314,17 +98345,17 @@ function findBooleanField(value, name3) {
98314
98345
  }
98315
98346
  async function downloadedFile(input) {
98316
98347
  if (input.localPath !== undefined) {
98317
- const bytes = await readFile75(input.localPath);
98348
+ const bytes = await readFile76(input.localPath);
98318
98349
  return { path: input.localPath, bytes, mediaType: mediaTypeFor(input.localPath, bytes), representation: "original" };
98319
98350
  }
98320
- const tempRoot = await mkdtemp5(join96(tmpdir2(), "context-lark-resource-"));
98351
+ const tempRoot = await mkdtemp5(join97(tmpdir2(), "context-lark-resource-"));
98321
98352
  try {
98322
98353
  const representation = await downloadLarkMedia({ ...input, cwd: tempRoot });
98323
98354
  const entries2 = (await readdir22(tempRoot, { withFileTypes: true })).filter((entry) => entry.isFile());
98324
98355
  if (entries2.length !== 1)
98325
98356
  throw new Error(`media download produced ${entries2.length} files, expected exactly one`);
98326
98357
  const path3 = entries2[0]?.name ?? "resource.bin";
98327
- const bytes = await readFile75(join96(tempRoot, path3));
98358
+ const bytes = await readFile76(join97(tempRoot, path3));
98328
98359
  return { path: path3, bytes, mediaType: mediaTypeFor(path3, bytes), representation };
98329
98360
  } finally {
98330
98361
  await rm22(tempRoot, { recursive: true, force: true });
@@ -98396,9 +98427,9 @@ async function sheetMaterialization(resource, runner2, identity2) {
98396
98427
  const sheetId = resource.attributes["sheet-id"];
98397
98428
  if (token === undefined || sheetId === undefined)
98398
98429
  throw new Error("embedded Sheet requires token and sheet-id");
98399
- const tempRoot = await mkdtemp5(join96(tmpdir2(), "context-lark-sheet-"));
98430
+ const tempRoot = await mkdtemp5(join97(tmpdir2(), "context-lark-sheet-"));
98400
98431
  try {
98401
- const outputPath = join96(tempRoot, "sheet.json");
98432
+ const outputPath = join97(tempRoot, "sheet.json");
98402
98433
  const stdout = await runLarkResourceCommand(runner2, [
98403
98434
  "sheets",
98404
98435
  "+csv-get",
@@ -98418,7 +98449,7 @@ async function sheetMaterialization(resource, runner2, identity2) {
98418
98449
  if (findBooleanField(receipt2, "truncated") === true || findBooleanField(receipt2, "complete") === false) {
98419
98450
  throw new Error("embedded Sheet read was truncated");
98420
98451
  }
98421
- const payload = JSON.parse(await readFile75(outputPath, "utf8"));
98452
+ const payload = JSON.parse(await readFile76(outputPath, "utf8"));
98422
98453
  const csv = findStringField(payload, new Set(["annotated_csv", "csv", "content", "text"]));
98423
98454
  if (csv === undefined)
98424
98455
  throw new Error("embedded Sheet response has no CSV payload");
@@ -99533,8 +99564,8 @@ var init_sensitiveSourceLiteral = __esm(() => {
99533
99564
 
99534
99565
  // src/project/larkSnapshotBundle.ts
99535
99566
  import { createHash as createHash29 } from "node:crypto";
99536
- import { lstat as lstat10, mkdir as mkdir33, readFile as readFile76, realpath as realpath10, rename as rename8, writeFile as writeFile26 } from "node:fs/promises";
99537
- 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";
99538
99569
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
99539
99570
  function normalizedSnapshotResourcePolicy(policy) {
99540
99571
  return { ...policy, images: policy.images ?? "bundle", gifs: policy.gifs ?? "bundle" };
@@ -99575,13 +99606,13 @@ async function readFileEntry(root2, entry) {
99575
99606
  const parts = safePath(entry.path).split("/");
99576
99607
  let current2 = root2;
99577
99608
  for (const [index2, part] of parts.entries()) {
99578
- current2 = join97(current2, part);
99609
+ current2 = join98(current2, part);
99579
99610
  const stat11 = await lstat10(current2);
99580
99611
  if (stat11.isSymbolicLink() || (index2 === parts.length - 1 ? !stat11.isFile() : !stat11.isDirectory())) {
99581
99612
  throw larkSnapshotError(`Snapshot entry is not a regular file: ${entry.path}`, "path-invalid");
99582
99613
  }
99583
99614
  }
99584
- const bytes = await readFile76(current2);
99615
+ const bytes = await readFile77(current2);
99585
99616
  if (digest6(bytes) !== entry.sha256)
99586
99617
  throw larkSnapshotError(`Snapshot content changed: ${entry.path}`, "digest-mismatch");
99587
99618
  return bytes;
@@ -99644,13 +99675,13 @@ async function writeLarkSnapshotBundle(input) {
99644
99675
  };
99645
99676
  schema2.parse(manifest);
99646
99677
  for (const [path3, bytes] of files) {
99647
- await mkdir33(dirname39(join97(root2, path3)), { recursive: true });
99648
- 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 });
99649
99680
  }
99650
99681
  await validateBundle(root2, manifest);
99651
- await writeFile26(join97(root2, `${FILE2}.pending`), `${JSON.stringify(manifest, null, 2)}
99682
+ await writeFile26(join98(root2, `${FILE2}.pending`), `${JSON.stringify(manifest, null, 2)}
99652
99683
  `, { flag: "wx", mode: 384 });
99653
- await rename8(join97(root2, `${FILE2}.pending`), join97(root2, FILE2));
99684
+ await rename8(join98(root2, `${FILE2}.pending`), join98(root2, FILE2));
99654
99685
  return manifest;
99655
99686
  }
99656
99687
  async function validateBundle(root2, manifest) {
@@ -99723,11 +99754,11 @@ async function validateBundle(root2, manifest) {
99723
99754
  async function readLarkSnapshotBundle(directory, target) {
99724
99755
  try {
99725
99756
  const root2 = await assertSnapshotDirectory(directory);
99726
- const path3 = join97(root2, FILE2);
99757
+ const path3 = join98(root2, FILE2);
99727
99758
  const stat11 = await lstat10(path3);
99728
99759
  if (stat11.isSymbolicLink() || !stat11.isFile())
99729
99760
  throw larkSnapshotError("Snapshot receipt must be a regular file", "path-invalid");
99730
- const manifest = schema2.parse(JSON.parse(await readFile76(path3, "utf8")));
99761
+ const manifest = schema2.parse(JSON.parse(await readFile77(path3, "utf8")));
99731
99762
  assertTarget(manifest.url, target);
99732
99763
  return await validateBundle(root2, manifest);
99733
99764
  } catch (error) {
@@ -99751,8 +99782,8 @@ async function reserveSnapshotDirectory(directory, workspaceRoot) {
99751
99782
  ancestor = dirname39(ancestor);
99752
99783
  }
99753
99784
  }
99754
- const parent = join97(ancestor, ...missing);
99755
- const output = join97(parent, basename10(requested));
99785
+ const parent = join98(ancestor, ...missing);
99786
+ const output = join98(parent, basename10(requested));
99756
99787
  const { findContextProjectRoot: findContextProjectRoot2 } = await Promise.resolve().then(() => (init_workspace(), exports_workspace));
99757
99788
  const owner = findContextProjectRoot2(parent)?.projectRoot ?? workspaceRoot;
99758
99789
  if (owner !== undefined) {
@@ -99825,8 +99856,8 @@ var init_larkSnapshotBundle = __esm(() => {
99825
99856
  });
99826
99857
 
99827
99858
  // src/project/documentCaptureLark.ts
99828
- import { readdir as readdir23, readFile as readFile77 } from "node:fs/promises";
99829
- 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";
99830
99861
  function titleFromMarkdown2(markdown, fallbackPath) {
99831
99862
  const heading2 = markdown.split(`
99832
99863
  `).find((line) => /^#\s+\S/u.test(line));
@@ -99845,7 +99876,7 @@ function countLines3(markdown) {
99845
99876
  }
99846
99877
  async function fileContentMatches(path3, content3) {
99847
99878
  try {
99848
- const current2 = await readFile77(path3);
99879
+ const current2 = await readFile78(path3);
99849
99880
  const expected = typeof content3 === "string" ? Buffer.from(content3, "utf8") : Buffer.from(content3);
99850
99881
  return current2.equals(expected);
99851
99882
  } catch {
@@ -99853,7 +99884,7 @@ async function fileContentMatches(path3, content3) {
99853
99884
  }
99854
99885
  }
99855
99886
  function sourceManifestPath2(entry) {
99856
- return entry.snapshot?.manifest ?? join98(entry.materializedAt, "manifest.json");
99887
+ return entry.snapshot?.manifest ?? join99(entry.materializedAt, "manifest.json");
99857
99888
  }
99858
99889
  function larkRuntimeError(message, detail) {
99859
99890
  return new ContextError(ExitCode.ExternalToolError, message, {
@@ -99980,7 +100011,7 @@ function assetManifestEntry(asset, assetRoot) {
99980
100011
  };
99981
100012
  }
99982
100013
  async function listSnapshotAssetFiles(root2, assetRoot) {
99983
- const assetsRoot = join98(root2, assetRoot);
100014
+ const assetsRoot = join99(root2, assetRoot);
99984
100015
  const files = [];
99985
100016
  const visit4 = async (dir, prefix = assetRoot) => {
99986
100017
  let entries2;
@@ -99993,7 +100024,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
99993
100024
  }
99994
100025
  for (const entry of entries2) {
99995
100026
  const relPath = `${prefix}/${entry.name}`;
99996
- const absolutePath = join98(dir, entry.name);
100027
+ const absolutePath = join99(dir, entry.name);
99997
100028
  if (entry.isDirectory()) {
99998
100029
  await visit4(absolutePath, relPath);
99999
100030
  continue;
@@ -100008,7 +100039,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
100008
100039
  }
100009
100040
  async function staleSnapshotAssetPaths(input) {
100010
100041
  const existingPaths = await listSnapshotAssetFiles(input.materializedAtAbsPath, input.assetRoot);
100011
- 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));
100012
100043
  }
100013
100044
  function normalizeLarkError(error, sourceName) {
100014
100045
  if (error instanceof ContextError)
@@ -100129,9 +100160,9 @@ async function runCaptureLarkPhaseUnlocked(input) {
100129
100160
  locator
100130
100161
  }];
100131
100162
  const manifestPath = sourceManifestPath2(entry);
100132
- const manifestAbsPath = join98(input.projectRoot, manifestPath);
100163
+ const manifestAbsPath = join99(input.projectRoot, manifestPath);
100133
100164
  const materializedAt = entry.materializedAt;
100134
- const materializedAtAbsPath = join98(input.projectRoot, materializedAt);
100165
+ const materializedAtAbsPath = join99(input.projectRoot, materializedAt);
100135
100166
  const manifest = createDocumentSnapshotManifest({
100136
100167
  sourceType: "lark",
100137
100168
  sourceName: resolved.sourceName,
@@ -100165,13 +100196,13 @@ async function runCaptureLarkPhaseUnlocked(input) {
100165
100196
  }));
100166
100197
  try {
100167
100198
  const requestedWrites = [{
100168
- path: join98(materializedAtAbsPath, documentPath),
100199
+ path: join99(materializedAtAbsPath, documentPath),
100169
100200
  bytes: normalized
100170
100201
  }];
100171
100202
  for (const asset of assets) {
100172
100203
  if (asset.bytes !== undefined) {
100173
100204
  requestedWrites.push({
100174
- path: join98(materializedAtAbsPath, asset.entry.path),
100205
+ path: join99(materializedAtAbsPath, asset.entry.path),
100175
100206
  bytes: asset.bytes
100176
100207
  });
100177
100208
  }
@@ -100188,7 +100219,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
100188
100219
  currentPaths: new Set(assets.filter((asset) => asset.bytes !== undefined).map((asset) => asset.entry.path))
100189
100220
  });
100190
100221
  await applyAtomicFileBatch({
100191
- transactionRoot: join98(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
100222
+ transactionRoot: join99(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
100192
100223
  writes,
100193
100224
  removals
100194
100225
  });
@@ -100338,7 +100369,7 @@ __export(exports_articleRetirement, {
100338
100369
  retireArticles: () => retireArticles,
100339
100370
  articleRetirementSchema: () => articleRetirementSchema
100340
100371
  });
100341
- import { readFile as readFile83, readdir as readdir24 } from "node:fs/promises";
100372
+ import { readFile as readFile84, readdir as readdir24 } from "node:fs/promises";
100342
100373
  import { posix as posix10 } from "node:path";
100343
100374
  function rebuildInput(revision) {
100344
100375
  return JSON.stringify({ id: `retirement-${revision.slice(7)}`, operation: "rebuild", timing: "priority", targets: [] });
@@ -100359,7 +100390,7 @@ function invalid2(reason, message, details = {}) {
100359
100390
  }
100360
100391
  async function text9(root2, path3) {
100361
100392
  try {
100362
- return await readFile83(await safeProjectTarget(root2, path3), "utf8");
100393
+ return await readFile84(await safeProjectTarget(root2, path3), "utf8");
100363
100394
  } catch (error) {
100364
100395
  if (error.code === "ENOENT")
100365
100396
  return;
@@ -100621,8 +100652,8 @@ __export(exports_writeLockRecovery, {
100621
100652
  recoverWriterLock: () => recoverWriterLock,
100622
100653
  inspectWriterLock: () => inspectWriterLock
100623
100654
  });
100624
- import { lstat as lstat11, mkdir as mkdir35, readFile as readFile84, readdir as readdir25, rename as rename9, rmdir as rmdir2 } from "node:fs/promises";
100625
- 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";
100626
100657
  import { createHash as createHash31, randomUUID as randomUUID7 } from "node:crypto";
100627
100658
  async function inspectWriterLock(root2) {
100628
100659
  const path3 = await safeProjectTarget(root2, lockRelative);
@@ -100637,7 +100668,7 @@ async function inspectWriterLock(root2) {
100637
100668
  if (!stat11.isDirectory() || stat11.isSymbolicLink())
100638
100669
  throw new Error("Writer lock must be a real directory.");
100639
100670
  const ownerPath = await safeProjectTarget(root2, `${lockRelative}/owner.json`);
100640
- const bytes = await readFile84(ownerPath, "utf8");
100671
+ const bytes = await readFile85(ownerPath, "utf8");
100641
100672
  const owner = JSON.parse(bytes);
100642
100673
  if (owner.protocol !== "context.project-write-lock.v1" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
100643
100674
  throw new Error("Writer lock owner is invalid; preserve the lock for diagnosis.");
@@ -100669,8 +100700,8 @@ async function recoverWriterLock(input) {
100669
100700
  };
100670
100701
  if (input.plan_digest !== before.digest)
100671
100702
  throw new Error("Writer lock changed; preview recovery again.");
100672
- const path3 = join103(input.projectRoot, lockRelative);
100673
- const guard = join103(path3, ".recovery");
100703
+ const path3 = join104(input.projectRoot, lockRelative);
100704
+ const guard = join104(path3, ".recovery");
100674
100705
  await mkdir35(guard);
100675
100706
  let archived = false;
100676
100707
  try {
@@ -100681,7 +100712,7 @@ async function recoverWriterLock(input) {
100681
100712
  if (names.some((name3) => name3 !== "owner.json" && name3 !== ".recovery"))
100682
100713
  throw new Error("Unexpected lock contents; preserve for diagnosis.");
100683
100714
  const archive = `.tmp/context-runtime/locks/recovered-write-${randomUUID7()}.lock`;
100684
- await rename9(path3, join103(input.projectRoot, archive));
100715
+ await rename9(path3, join104(input.projectRoot, archive));
100685
100716
  archived = true;
100686
100717
  return { action: "writer-lock-recovered", archived_lock: archive, next: "context task recover --format json" };
100687
100718
  } finally {
@@ -100705,12 +100736,12 @@ __export(exports_taskRecovery, {
100705
100736
  RECOVERY_COMMAND: () => RECOVERY_COMMAND
100706
100737
  });
100707
100738
  import { existsSync as existsSync26 } from "node:fs";
100708
- import { dirname as dirname42, join as join104 } from "node:path";
100709
- 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";
100710
100741
  async function recoveryText(root2, path3) {
100711
100742
  const target = await safeProjectTarget(root2, path3);
100712
100743
  try {
100713
- return await readFile85(target, "utf8");
100744
+ return await readFile86(target, "utf8");
100714
100745
  } catch (error) {
100715
100746
  if (error.code === "ENOENT")
100716
100747
  return;
@@ -100723,7 +100754,7 @@ async function recoveryJournals(root2) {
100723
100754
  await safeProjectTarget(root2, path3);
100724
100755
  let stat11;
100725
100756
  try {
100726
- stat11 = await lstat12(join104(root2, path3));
100757
+ stat11 = await lstat12(join105(root2, path3));
100727
100758
  } catch (error) {
100728
100759
  if (error.code === "ENOENT")
100729
100760
  return;
@@ -100734,7 +100765,7 @@ async function recoveryJournals(root2) {
100734
100765
  if (stat11.isDirectory()) {
100735
100766
  if (depth > 3)
100736
100767
  throw new TypeError("Unexpected transaction directory depth; preserve it for diagnosis.");
100737
- for (const name3 of (await readdir26(join104(root2, path3))).sort())
100768
+ for (const name3 of (await readdir26(join105(root2, path3))).sort())
100738
100769
  await visit4(`${path3}/${name3}`, depth + 1);
100739
100770
  } else if (stat11.isFile())
100740
100771
  entries2.push({ path: path3, digest: indexerProtocolDigest(await recoveryText(root2, path3)) });
@@ -100746,8 +100777,8 @@ function recoveryResources() {
100746
100777
  try {
100747
100778
  const root2 = dirname42(contextWorkflowProviderPath());
100748
100779
  const resources = {
100749
- skill: join104(root2, "skills/recover-workspace/SKILL.md"),
100750
- 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")
100751
100782
  };
100752
100783
  if (!Object.values(resources).every((path3) => existsSync26(path3)))
100753
100784
  throw new Error("Recovery resources are absent from this Provider.");
@@ -100832,8 +100863,8 @@ var exports_taskLocalSourceAdjustment = {};
100832
100863
  __export(exports_taskLocalSourceAdjustment, {
100833
100864
  adjustLocalRevisionSources: () => adjustLocalRevisionSources
100834
100865
  });
100835
- import { readFile as readFile86 } from "node:fs/promises";
100836
- import { join as join105 } from "node:path";
100866
+ import { readFile as readFile87 } from "node:fs/promises";
100867
+ import { join as join106 } from "node:path";
100837
100868
  async function adjustLocalRevisionSources(root2, input) {
100838
100869
  const { readMaintenance: readMaintenance2 } = await Promise.resolve().then(() => (init_maintenanceStorage(), exports_maintenanceStorage));
100839
100870
  if ((await readMaintenance2(root2)).active && await readProductionStage(root2))
@@ -100876,7 +100907,7 @@ async function adjustLocalRevisionSources(root2, input) {
100876
100907
  if (input.refresh && (!current2.refresh_sources || indexerProtocolDigest([...current2.refresh_sources].sort()) !== indexerProtocolDigest([...selected].sort()))) {
100877
100908
  throw new TypeError("No matching acquisition adjustment exists. Run task adjust without refresh first.");
100878
100909
  }
100879
- const raw = await readFile86(join105(root2, await revisionStoragePath(root2)), "utf8");
100910
+ const raw = await readFile87(join106(root2, await revisionStoragePath(root2)), "utf8");
100880
100911
  let next2;
100881
100912
  const discardIds = new Set;
100882
100913
  if (!input.refresh) {
@@ -100979,7 +101010,7 @@ ${input.instruction}` : revision.instruction
100979
101010
  content: content3
100980
101011
  }];
100981
101012
  if (discardIds.size > 0) {
100982
- 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) => {
100983
101014
  if (error.code === "ENOENT")
100984
101015
  return;
100985
101016
  throw error;
@@ -101104,7 +101135,7 @@ var init_taskSourceAdjustment = __esm(() => {
101104
101135
  });
101105
101136
 
101106
101137
  // src/project/managedDocumentImport.ts
101107
- import { readFile as readFile95 } from "node:fs/promises";
101138
+ import { readFile as readFile96 } from "node:fs/promises";
101108
101139
  async function importManagedDocument(projectRoot, value) {
101109
101140
  const input = inputSchema.parse(value);
101110
101141
  if (input.type !== "sessions" && input.changes !== undefined)
@@ -101113,7 +101144,7 @@ async function importManagedDocument(projectRoot, value) {
101113
101144
  const path3 = await assertManagedDocumentPath(projectRoot, input.type, input.name);
101114
101145
  let previous3;
101115
101146
  try {
101116
- previous3 = await readFile95(path3, "utf8");
101147
+ previous3 = await readFile96(path3, "utf8");
101117
101148
  } catch (error) {
101118
101149
  if (!(error && typeof error === "object" && ("code" in error) && error.code === "ENOENT"))
101119
101150
  throw error;
@@ -101160,7 +101191,7 @@ var init_managedDocumentImport = __esm(() => {
101160
101191
  });
101161
101192
 
101162
101193
  // src/project/larkDocumentImport.ts
101163
- import { readFile as readFile96 } from "node:fs/promises";
101194
+ import { readFile as readFile97 } from "node:fs/promises";
101164
101195
  import { resolve as resolve40 } from "node:path";
101165
101196
  async function importLarkDocument(projectRoot, value) {
101166
101197
  const input = schema4.parse(value);
@@ -101177,7 +101208,7 @@ async function importLarkDocument(projectRoot, value) {
101177
101208
  const responsePages = [];
101178
101209
  for (const path3 of input.response_files) {
101179
101210
  assertActionInputWorkspace(projectRoot, path3);
101180
- responsePages.push(await readFile96(resolve40(projectRoot, path3), "utf8"));
101211
+ responsePages.push(await readFile97(resolve40(projectRoot, path3), "utf8"));
101181
101212
  }
101182
101213
  const mediaFiles = {};
101183
101214
  for (const [token, path3] of Object.entries(input.media_files ?? {})) {
@@ -101244,11 +101275,11 @@ var exports_managedDocumentRename = {};
101244
101275
  __export(exports_managedDocumentRename, {
101245
101276
  renameManagedDocument: () => renameManagedDocument
101246
101277
  });
101247
- import { readFile as readFile97 } from "node:fs/promises";
101248
- 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";
101249
101280
  async function optionalText2(path3) {
101250
101281
  try {
101251
- return await readFile97(path3, "utf8");
101282
+ return await readFile98(path3, "utf8");
101252
101283
  } catch (error) {
101253
101284
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
101254
101285
  return;
@@ -101332,7 +101363,7 @@ async function renameManagedDocument(input) {
101332
101363
  if (path3.split("/").includes("..") || path3.startsWith("/"))
101333
101364
  throw new TypeError("Source references contain an unsafe knowledge path; repair it before renaming.");
101334
101365
  await safeProjectTarget(input.projectRoot, path3);
101335
- const before = await optionalText2(join117(input.projectRoot, path3));
101366
+ const before = await optionalText2(join118(input.projectRoot, path3));
101336
101367
  if (before === undefined)
101337
101368
  continue;
101338
101369
  let after;
@@ -101421,7 +101452,7 @@ var init_managedDocumentRename = __esm(() => {
101421
101452
 
101422
101453
  // src/cli.ts
101423
101454
  import { existsSync as existsSync36, realpathSync as realpathSync3 } from "node:fs";
101424
- import { dirname as dirname50, join as join123 } from "node:path";
101455
+ import { dirname as dirname50, join as join124 } from "node:path";
101425
101456
  import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL3 } from "node:url";
101426
101457
 
101427
101458
  // ../../node_modules/.bun/commander@11.1.0/node_modules/commander/esm.mjs
@@ -101476,7 +101507,7 @@ init_dist();
101476
101507
  init_cliFeedback();
101477
101508
  init_errors3();
101478
101509
  init_exitCode();
101479
- import { join as join92 } from "node:path";
101510
+ import { join as join93 } from "node:path";
101480
101511
 
101481
101512
  // src/project/documentCaptureAvailability.ts
101482
101513
  init_atomicFileBatch();
@@ -102470,6 +102501,7 @@ async function projectStatusSummary(status, projectRoot) {
102470
102501
  packages: status.packages.map((item) => ({ name: item.name, state: item.state })),
102471
102502
  meaning: status.evidenceStatus === "not-checked" ? "Progress only: workspace verification and delivery freshness were not checked. Delivery actions validate their inputs; context verify performs an explicit audit." : "Approved pages may not yet be built. Package freshness describes dist; task completion describes the current stage only."
102472
102503
  },
102504
+ counts_scope: "Workspace inventory, not this request’s additions or article workload. Captured documents, approved articles, pending investigation and source-read failures are different measures; source ready counts alone do not explain failure causes.",
102473
102505
  counts: {
102474
102506
  sources: status.sourceSummary,
102475
102507
  draftCandidates: status.draftCandidates,
@@ -102556,9 +102588,9 @@ init_workflowTypes();
102556
102588
  // src/project/reviewHtml.ts
102557
102589
  init_candidateLedger();
102558
102590
  init_reviewShared();
102559
- 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";
102560
102592
  import { fileURLToPath as fileURLToPath6 } from "node:url";
102561
- 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";
102562
102594
 
102563
102595
  // src/project/reviewSiteModel.ts
102564
102596
  init_siteTheme2();
@@ -105760,6 +105792,7 @@ async function collectReviewSiteModel(root2, candidates) {
105760
105792
  html: old === undefined ? renderReviewMarkdown(next.replace(/^# [^\n]+\n*/u, "")) : reviewBodyDiff(body(old).replace(/^# [^\n]+\n*/u, ""), next.replace(/^# [^\n]+\n*/u, "")),
105761
105793
  sources: [...r.source_refs, ...r.indexer_candidate.sections.flatMap((s) => s.references.map((ref2) => JSON.stringify(ref2)))]
105762
105794
  };
105795
+ page.readVersion = hash3(JSON.stringify([page.id, page.title, page.path, page.html, page.sources]));
105763
105796
  if (found)
105764
105797
  pages.splice(pages.indexOf(found), 1, page);
105765
105798
  else
@@ -105800,146 +105833,394 @@ async function collectReviewSiteModel(root2, candidates) {
105800
105833
  const siteTitle = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site?.title ? [p.site.title] : [])[0];
105801
105834
  const sitePackages = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site ? [p.site] : []) ?? [];
105802
105835
  const themeCss = siteThemeVariables(await resolveSiteTheme(root2, sitePackages.length === 1 ? sitePackages[0]?.theme : undefined));
105803
- 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 };
105804
105837
  }
105805
105838
  var reviewHtmlJson = (value) => JSON.stringify(value).replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
105806
105839
 
105807
- // src/project/reviewFeedbackCode.ts
105808
- function createReviewFeedbackCodec() {
105809
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
105810
- function checksum(text9) {
105811
- let crc = 4294967295;
105812
- for (let i2 = 0;i2 < text9.length; i2++) {
105813
- crc ^= text9.charCodeAt(i2);
105814
- for (let j = 0;j < 8; j++)
105815
- 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
105816
105860
  }
105817
- return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
105818
- }
105819
- function validate(value) {
105820
- 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))
105821
- throw new Error("Invalid review feedback scope or decisions");
105822
- const seen = new Set;
105823
- for (const repair of value.repairs) {
105824
- 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)
105825
- throw new Error("Invalid or conflicting revision instruction");
105826
- 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");
105827
105873
  }
105828
- if (value.statuses.filter((s) => s === "revised").length !== seen.size)
105829
- throw new Error("Missing revision instruction");
105830
- 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
+ });
105831
105881
  }
105832
- function encode(value) {
105833
- validate(value);
105834
- const bytes = new TextEncoder().encode(JSON.stringify({ scope: value.scope, idsHash: value.idsHash, contentHash: value.contentHash, baselineHash: value.baselineHash, statuses: value.statuses }));
105835
- let bits = 0, carry = 0, data2 = "";
105836
- for (const byte of bytes) {
105837
- carry = carry << 8 | byte;
105838
- bits += 8;
105839
- while (bits >= 6) {
105840
- bits -= 6;
105841
- data2 += alphabet[carry >>> bits & 63];
105842
- }
105843
- }
105844
- if (bits)
105845
- data2 += alphabet[carry << 6 - bits & 63];
105846
- const body2 = `CR2.${data2}`;
105847
- const repairs = value.repairs.map((r) => JSON.stringify([r.index, r.instruction])).join(`
105848
- `);
105849
- const trailer = repairs ? `
105850
- ${repairs}` : "";
105851
- const code3 = `${body2}.${checksum(body2 + trailer)}${trailer}`;
105852
- if (code3.length > 250000)
105853
- throw new Error("Review feedback is too large; shorten instructions or review a smaller scope");
105854
- 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);
105855
105922
  }
105856
- function decode2(raw) {
105857
- const code3 = raw.trim();
105858
- if (code3.length > 250000)
105859
- throw new Error("Review feedback exceeds supported size");
105860
- const [header, ...lines] = code3.split(`
105861
- `);
105862
- const trailer = lines.length ? `
105863
- ${lines.join(`
105864
- `)}` : "";
105865
- const match = /^CR2\.([A-Za-z0-9_-]+)\.([a-f0-9]{8})$/.exec(header);
105866
- if (!match || checksum(`CR2.${match[1]}` + trailer) !== match[2])
105867
- throw new Error("Review feedback is damaged or incomplete; copy the full code again");
105868
- let bits = 0, carry = 0;
105869
- const bytes = [];
105870
- for (const char of match[1]) {
105871
- carry = carry << 6 | alphabet.indexOf(char);
105872
- bits += 6;
105873
- if (bits >= 8) {
105874
- bits -= 8;
105875
- 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);
105876
105934
  }
105877
105935
  }
105878
- const headerValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes)));
105879
- const value = validate({ ...headerValue, repairs: lines.map((line) => {
105880
- const row = JSON.parse(line);
105881
- if (!Array.isArray(row) || row.length !== 2)
105882
- throw new Error("Invalid revision instruction line");
105883
- return { index: row[0], instruction: row[1] };
105884
- }) });
105885
- if (encode(value) !== code3)
105886
- throw new Error("Noncanonical review feedback encoding");
105887
- return value;
105888
- }
105889
- return { encode, decode: decode2 };
105936
+ } catch { /* The report remains usable when local storage is unavailable. */ }
105890
105937
  }
105891
105938
 
105892
- // src/project/reviewSiteClient.ts
105893
- var REVIEW_SITE_CLIENT = String.raw`
105894
- const $=id=>document.getElementById(id);
105895
- let language=(navigator.languages?.[0]||navigator.language||'en').startsWith('zh')?'zh':'en';
105896
- 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.']};
105897
- const t=k=>words[k]?.[language==='zh'?0:1]||k;
105898
- const escape=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
105899
- const decisions=new Map(),notes=new Map(),expanded=new Set();let selected=null,active=null;
105900
- const candidates=DATA.pages.filter(p=>p.candidate_id),ordered=[...candidates].sort((a,b)=>a.candidate_id<b.candidate_id?-1:1);
105901
- for(const p of candidates)if(p.revisionInstruction){decisions.set(p.candidate_id,'revised');notes.set(p.candidate_id,p.revisionInstruction)}
105902
- const badge=change=>change==='unchanged'?'':'<span class="badge '+change+'">'+(change==='new'?'New':change==='modify'?'Modify':change==='removed'?'Remove':t(change))+'</span>';
105903
- const state=p=>decisions.get(p.candidate_id)||'pending';
105904
- 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)])}
105905
- 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'}
105906
- function nodeTitle(n){return n.key==='review-unplaced'?t('unplaced'):n.title}
105907
- function nodeLabel(n){return (n.removed?'<del>':'')+escape(nodeTitle(n))+(n.removed?'</del>':'')+(n.oldTitle?'<small class="old-title"> ← '+escape(n.oldTitle)+'</small>':'')}
105908
- 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}
105909
- 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>'}
105910
- 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>'}
105911
- 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}
105912
- 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}
105913
- function showPage(id){selected=id;active=rootFor(id)||active;render()}
105914
- function localizeBody(){document.querySelectorAll('[data-label]').forEach(el=>{el.textContent=t(el.dataset.label)})}
105915
- 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>'}
105916
- 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()}
105917
- 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'):''}}
105918
- 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})}
105919
- 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()}
105920
- function setAllDecision(value){for(const p of candidates)if(!decisions.has(p.candidate_id))decisions.set(p.candidate_id,value);render()}
105921
- 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)}]:[])})}
105922
- 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()}
105923
- let guideTimer;function dismissGuide(){clearInterval(guideTimer);$('copy-guide').hidden=true}
105924
- 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':'中文'}
105925
- 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()}}});
105926
- $('home').onclick=()=>{selected=null;active=null;render();window.scrollTo(0,0)};
105927
- $('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'):'')};
105928
- 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)};
105929
- const addedRoots=DATA.nodes.filter(n=>!n.parent&&n.change==='new'&&!n.removed&&n.key!=='review-unplaced');
105930
- let bulkTimer,bulkDeadline=0;
105931
- 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}
105932
- 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)}
105933
- $('bulk-ack').onchange=updateBulkConfirmation;
105934
- $('bulk-dialog').onclose=()=>clearInterval(bulkTimer);
105935
- $('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()};
105936
- 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(); }
105937
106218
  `;
105938
106219
 
105939
106220
  // src/project/reviewSiteStyles.ts
105940
106221
  init_diagramStyles();
105941
106222
  var REVIEW_SITE_STYLES = String.raw`
105942
- :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}}
105943
106224
  ${readerContentStyles("article")}
105944
106225
  article code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
105945
106226
  article :not(pre)>code{padding:3px 6px;overflow-wrap:break-word}
@@ -105962,10 +106243,85 @@ article.review-new-page .language-mermaid{--context-text:var(--green);--context-
105962
106243
  article .changed .language-mermaid{--context-text:var(--amber);--context-muted-text:var(--amber)}
105963
106244
  article .changed.removed .language-mermaid{--context-text:var(--red);--context-muted-text:var(--red)}
105964
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
+
105965
106321
  `;
105966
106322
 
105967
106323
  // src/project/reviewHtml.ts
105968
- var REVIEW_HTML_ROOT = join90(".tmp", "context-runtime", "review");
106324
+ var REVIEW_HTML_ROOT = join91(".tmp", "context-runtime", "review");
105969
106325
  async function collectReviewCandidates(projectRoot, collection) {
105970
106326
  const rows = await readCandidateRecords(projectRoot);
105971
106327
  const draftRows = rows.filter((row) => row.collection === collection && row.status === "draft");
@@ -105982,23 +106338,39 @@ async function collectAllReviewCandidates(projectRoot) {
105982
106338
  snapshot: await readReviewCandidateSnapshot(projectRoot, record4)
105983
106339
  })));
105984
106340
  }
105985
- function renderReviewHtml(candidates, reviewScope, model, diagramScript) {
105986
- const scope2 = {
105987
- label: reviewScope,
105988
- ids_sha256: candidateIdsHash(candidates.map((c) => c.record.candidate_id).sort()),
105989
- candidates_sha256: candidateSetHash(candidates.map((c) => c.record))
105990
- };
105991
- 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>
105992
- <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>
105993
- <div class="layout"><aside id="tree"></aside><main><article id="article"></article></main></div>
105994
- <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>
105995
- <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>
105996
- <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>
105997
- ${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>`;
105998
106370
  }
105999
106371
  function resolveOutputPath(projectRoot, outPath, reviewScope) {
106000
106372
  if (outPath === undefined)
106001
- return join90(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
106373
+ return join91(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
106002
106374
  return isAbsolute15(outPath) ? outPath : resolve28(projectRoot, outPath);
106003
106375
  }
106004
106376
  async function writeReviewHtml(input) {
@@ -106014,14 +106386,15 @@ async function writeReviewHtml(input) {
106014
106386
  if (model.pages.some((page) => page.html.includes('class="language-mermaid"'))) {
106015
106387
  const here = dirname38(fileURLToPath6(import.meta.url));
106016
106388
  try {
106017
- diagramScript = await readFile71(join90(here, "browser/diagrams.js"), "utf8");
106389
+ diagramScript = await readFile72(join91(here, "browser/diagrams.js"), "utf8");
106018
106390
  } catch (error) {
106019
106391
  if (error.code !== "ENOENT")
106020
106392
  throw error;
106021
- diagramScript = await readFile71(resolve28(here, "../../dist/browser/diagrams.js"), "utf8");
106393
+ diagramScript = await readFile72(resolve28(here, "../../dist/browser/diagrams.js"), "utf8");
106022
106394
  }
106023
106395
  }
106024
- 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);
106025
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 }));
106026
106399
  return {
106027
106400
  navigation: { ready: unplaced.length === 0, unplaced_count: unplaced.length, unplaced },
@@ -106041,8 +106414,8 @@ init_productionRequirements();
106041
106414
  init_dist();
106042
106415
  init_atomicWrite();
106043
106416
  init_reviewShared();
106044
- import { mkdir as mkdir32, readFile as readFile72 } from "node:fs/promises";
106045
- 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";
106046
106419
  var REVIEW_BATCH_MAX_CANDIDATES = 6;
106047
106420
  var REVIEW_BATCH_MAX_BYTES = 512 * 1024;
106048
106421
  async function readerPurposes(projectRoot, sources) {
@@ -106122,12 +106495,12 @@ async function materializeCurrentReviewBatchSet(input) {
106122
106495
  const batches = buildCurrentReviewBatchDocuments(input.candidates);
106123
106496
  const setDigest = digestText(batches.map((batch) => `${batch.task_key}:${batch.digest}`).join(`
106124
106497
  `));
106125
- 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)}`);
106126
106499
  await mkdir32(root2, { recursive: true });
106127
106500
  const entries2 = [];
106128
106501
  for (const batch of batches) {
106129
- const path4 = join91(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
106130
- 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) => {
106131
106504
  if (error.code === "ENOENT")
106132
106505
  return;
106133
106506
  throw error;
@@ -106155,7 +106528,7 @@ async function materializeCurrentReviewBatchSet(input) {
106155
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.",
106156
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.",
106157
106530
  "```json",
106158
- 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: {
106159
106532
  kind: "all",
106160
106533
  count: input.candidates.length,
106161
106534
  ids_sha256: candidateIdsHash(input.candidates.map((item) => item.record.candidate_id).sort()),
@@ -106180,7 +106553,7 @@ async function materializeCurrentReviewBatchSet(input) {
106180
106553
  ].join(`
106181
106554
  `);
106182
106555
  const digest6 = digestText(content3);
106183
- const path3 = join91(root2, "index.md");
106556
+ const path3 = join92(root2, "index.md");
106184
106557
  await atomicWriteFile(path3, `${content3}
106185
106558
  `);
106186
106559
  return {
@@ -106213,11 +106586,11 @@ function shellQuote6(value) {
106213
106586
  }
106214
106587
  function receiptSetPath(receipts) {
106215
106588
  const token = digestText(JSON.stringify(receipts)).slice("sha256:".length);
106216
- return join92(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
106589
+ return join93(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
106217
106590
  }
106218
106591
  async function writeReceiptContinuation(input) {
106219
106592
  const path3 = receiptSetPath(input.receipts);
106220
- const absolutePath = join92(input.projectRoot, path3);
106593
+ const absolutePath = join93(input.projectRoot, path3);
106221
106594
  await writeJsonAtomic(absolutePath, input.receipts);
106222
106595
  const contextCommand = input.managed ? [
106223
106596
  "context",
@@ -106312,7 +106685,7 @@ async function materializeContextWorkflowResource(input) {
106312
106685
  const resourceId = workflowResourceId(input.resourceId);
106313
106686
  const content3 = renderContextWorkflowResource(resourceId, status);
106314
106687
  const location = await materializeResource(await loadContextWorkflowProvider(), resourceId, {
106315
- cache: join92(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
106688
+ cache: join93(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
106316
106689
  workspace: found.projectRoot,
106317
106690
  revision: input.revision,
106318
106691
  input: {
@@ -106344,7 +106717,7 @@ async function materializeContextWorkflowResource(input) {
106344
106717
  receipts: afterReadReceipts
106345
106718
  });
106346
106719
  const directResources = (status.workflow.current?.resources.required ?? []).filter((resource) => resource.read_state === "read-required" && resource.path !== undefined && resource.digest !== undefined);
106347
- 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`;
106348
106721
  return {
106349
106722
  protocol: "context.workflow.resource.v1",
106350
106723
  id: resourceId,
@@ -106405,14 +106778,15 @@ async function acknowledgeCurrentWorkflowResources(input) {
106405
106778
  const reevaluated = await reevaluateProjectStatusWorkflow({
106406
106779
  snapshot,
106407
106780
  resourceReceipts: normalizedReceipts,
106408
- resourceReceiptsReference: `@${join92(found.projectRoot, continuation.path)}`
106781
+ resourceReceiptsReference: `@${join93(found.projectRoot, continuation.path)}`
106409
106782
  });
106410
106783
  return {
106411
106784
  ...reevaluated,
106412
106785
  resourceAcknowledgement: {
106413
106786
  protocol: "context.workflow.resource-receipts.v1",
106414
106787
  acknowledged: directResources.length,
106415
- receiptReference: `@${join92(found.projectRoot, continuation.path)}`
106788
+ receiptReference: `@${join93(found.projectRoot, continuation.path)}`,
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."
106416
106790
  }
106417
106791
  };
106418
106792
  }
@@ -106458,9 +106832,9 @@ init_cliFeedback();
106458
106832
  init_errors3();
106459
106833
  init_exitCode();
106460
106834
  init_workspace();
106461
- import { readFile as readFile73 } from "node:fs/promises";
106462
- import { isAbsolute as isAbsolute16, join as join93, sep as sep6, resolve as resolve29 } from "node:path";
106463
- 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");
106464
106838
  function workflowResourceReceiptCwd(value, cwd) {
106465
106839
  if (value === undefined || !value.startsWith("@"))
106466
106840
  return cwd;
@@ -106478,7 +106852,7 @@ async function receiptDocument(value, cwd) {
106478
106852
  let source2 = value;
106479
106853
  if (value.startsWith("@")) {
106480
106854
  try {
106481
- source2 = await readFile73(resolve29(cwd, value.slice(1)), "utf8");
106855
+ source2 = await readFile74(resolve29(cwd, value.slice(1)), "utf8");
106482
106856
  } catch (error) {
106483
106857
  const ioCode = error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : undefined;
106484
106858
  throw new ContextError(ExitCode.UserError, "resource read receipt file is unavailable", {
@@ -106737,14 +107111,14 @@ function compactJsonResult(result, verbose) {
106737
107111
  // src/project/runLog.ts
106738
107112
  import { randomUUID as randomUUID5 } from "node:crypto";
106739
107113
  import { mkdir as mkdir34, writeFile as writeFile27 } from "node:fs/promises";
106740
- import { dirname as dirname40, join as join99 } from "node:path";
107114
+ import { dirname as dirname40, join as join100 } from "node:path";
106741
107115
  var createPhaseRunId = () => {
106742
107116
  const timestamp = new Date().toISOString().replace(/[-:.TZ]/gu, "");
106743
107117
  return `run_${timestamp}_${randomUUID5().slice(0, 8)}`;
106744
107118
  };
106745
107119
  async function writePhaseRunLog(input) {
106746
- const relPath = join99(".tmp", "context-runtime", "runs", `${input.runId}.json`);
106747
- const absPath = join99(input.projectRoot, relPath);
107120
+ const relPath = join100(".tmp", "context-runtime", "runs", `${input.runId}.json`);
107121
+ const absPath = join100(input.projectRoot, relPath);
106748
107122
  await mkdir34(dirname40(absPath), { recursive: true });
106749
107123
  await writeFile27(absPath, `${JSON.stringify({
106750
107124
  run_id: input.runId,
@@ -107687,6 +108061,9 @@ execution scope cleanup failed`, true)
107687
108061
  // src/project/workflow/workflowInProcessActions.ts
107688
108062
  init_workflowFacts();
107689
108063
 
108064
+ // src/project/reviewLegacy.ts
108065
+ init_candidateLedger();
108066
+
107690
108067
  // src/project/reviewCode.ts
107691
108068
  function createReviewCodeCodec() {
107692
108069
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -107826,11 +108203,140 @@ function createReviewCodeCodec() {
107826
108203
  return { encode, decode: decode2 };
107827
108204
  }
107828
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
+
107829
108335
  // src/project/review.ts
107830
108336
  init_cliFeedback();
107831
108337
  init_errors3();
107832
108338
  init_exitCode();
107833
- import { readFile as readFile80 } from "node:fs/promises";
108339
+ import { readFile as readFile81 } from "node:fs/promises";
107834
108340
  import { isAbsolute as isAbsolute19, resolve as resolve31 } from "node:path";
107835
108341
 
107836
108342
  // src/project/reviewApply.ts
@@ -107847,8 +108353,8 @@ init_durableMultiFileTransaction();
107847
108353
  init_durableSingleFileTransaction();
107848
108354
  init_reviewShared();
107849
108355
  import { existsSync as existsSync25 } from "node:fs";
107850
- import { readFile as readFile78 } from "node:fs/promises";
107851
- import { join as join100 } from "node:path";
108356
+ import { readFile as readFile79 } from "node:fs/promises";
108357
+ import { join as join101 } from "node:path";
107852
108358
  function existingTimestamp(markdown) {
107853
108359
  if (markdown === undefined)
107854
108360
  return;
@@ -107875,7 +108381,7 @@ async function prepareApprovedPage(input) {
107875
108381
  next: "Refresh the current production or article revision, then reopen Review before approval."
107876
108382
  });
107877
108383
  }
107878
- const relPath = join100("knowledge", input.record.path);
108384
+ const relPath = join101("knowledge", input.record.path);
107879
108385
  const existingView = findApprovedPageForArticleId(input.record.indexer_candidate.artifact_ref, input.approvedPageIndex);
107880
108386
  const previousPath = input.record.approved_revision?.previous_path;
107881
108387
  if (previousPath !== undefined && (!isSafeKnowledgeTargetPath(previousPath.split("/")[0], previousPath) || previousPath.includes("\\")))
@@ -107890,13 +108396,13 @@ async function prepareApprovedPage(input) {
107890
108396
  next: "Resolve the approved page path migration explicitly before approving this candidate."
107891
108397
  });
107892
108398
  }
107893
- const absPath = join100(input.projectRoot, relPath);
107894
- 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;
107895
108401
  let previous3;
107896
108402
  if (previousPath !== undefined) {
107897
108403
  if (existing !== undefined || existingView?.relPath !== `knowledge/${previousPath}`)
107898
108404
  throw new TypeError("Page move destination or original identity changed; refresh its revision.");
107899
- 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") };
107900
108406
  }
107901
108407
  if (input.record.approved_revision !== undefined) {
107902
108408
  const base = previous3?.content ?? existing;
@@ -107943,7 +108449,7 @@ async function prepareApprovedPage(input) {
107943
108449
  }
107944
108450
  async function readProjectFileMaybe(projectRoot, relPath) {
107945
108451
  try {
107946
- return await readFile78(join100(projectRoot, relPath), "utf8");
108452
+ return await readFile79(join101(projectRoot, relPath), "utf8");
107947
108453
  } catch (error) {
107948
108454
  if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") {
107949
108455
  return;
@@ -107972,31 +108478,19 @@ function reviewFileTarget(input) {
107972
108478
  content: input.targetContent
107973
108479
  };
107974
108480
  }
107975
- function expandEncodedReviewDecisions(payload, scopedIds) {
107976
- 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")) {
107977
- throw new ContextError(ExitCode.UserError, "Invalid scoped review code decisions", {
107978
- category: ErrorCategory.UserInputInvalid,
107979
- next: "Copy a fresh complete review code from the current HTML report."
107980
- });
107981
- }
107982
- return scopedIds.flatMap((candidate_id, index2) => {
107983
- const status = payload.encoded_statuses[index2];
107984
- return status === "pending" ? [] : [{ candidate_id, status }];
107985
- });
107986
- }
107987
108481
  function expandReviewPayload(payload, rows) {
107988
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") : [];
107989
108483
  const scopedIds = scopedRows.map((row) => row.candidate_id).sort();
107990
108484
  if (payload.scope === undefined) {
107991
108485
  throw new ContextError(ExitCode.UserError, "review payload requires an explicit scope from the current review gate", {
107992
108486
  category: ErrorCategory.UserInputInvalid,
107993
- 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.`
107994
108488
  });
107995
108489
  }
107996
108490
  const actualHash = candidateIdsHash(scopedIds);
107997
108491
  const actualCandidatesHash = candidateSetHash(scopedRows);
107998
108492
  const visibleIds = payload.scope.visible_candidate_ids;
107999
- if (payload.scope.kind === "all" && visibleIds === undefined && payload.encoded_statuses === undefined) {
108493
+ if (payload.scope.kind === "all" && visibleIds === undefined) {
108000
108494
  throw new ContextError(ExitCode.UserError, "all-scope review payload requires scope.visible_candidate_ids", {
108001
108495
  category: ErrorCategory.UserInputInvalid,
108002
108496
  expected: {
@@ -108004,7 +108498,7 @@ function expandReviewPayload(payload, rows) {
108004
108498
  ids_sha256: actualHash,
108005
108499
  visible_candidate_ids: scopedIds
108006
108500
  },
108007
- 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."
108008
108502
  });
108009
108503
  }
108010
108504
  const visibleMismatch = visibleIds !== undefined && (visibleIds.length !== scopedIds.length || visibleIds.some((id3, index2) => id3 !== scopedIds[index2]));
@@ -108020,11 +108514,9 @@ function expandReviewPayload(payload, rows) {
108020
108514
  candidates_sha256: actualCandidatesHash,
108021
108515
  visible_candidate_ids: scopedIds
108022
108516
  },
108023
- next: `Rerun context review html ${scopeLabel} and copy a fresh payload.`
108517
+ next: `Rerun context review html ${scopeLabel} and use its current scope template.`
108024
108518
  });
108025
108519
  }
108026
- if (payload.encoded_statuses !== undefined)
108027
- return expandEncodedReviewDecisions(payload, scopedIds);
108028
108520
  if (payload.default === undefined) {
108029
108521
  const scopedSet = new Set(scopedIds);
108030
108522
  for (const decision of payload.decisions) {
@@ -108074,6 +108566,14 @@ async function applyReviewDecisions(input) {
108074
108566
  const nextRows = [...rows];
108075
108567
  const decisions = expandReviewPayload(input.payload, rows);
108076
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
+ }
108077
108577
  if (input.payload.baseline_hash && input.payload.baseline_hash !== await reviewSiteBaselineHash(input.projectRoot, scoped.map((r) => r.approved_revision?.previous_path ?? r.path))) {
108078
108578
  throw new ContextError(ExitCode.WorkspaceStateError, "Review navigation or approved baseline changed; generate a fresh report", {
108079
108579
  category: ErrorCategory.WorkspaceStateInvalid,
@@ -108082,11 +108582,15 @@ async function applyReviewDecisions(input) {
108082
108582
  });
108083
108583
  }
108084
108584
  const repairIds = new Set;
108085
- const repairs = (input.payload.feedback_repairs ?? []).map((repair) => {
108086
- const row = scoped[repair.index];
108087
- if (!Number.isInteger(repair.index) || !row || repairIds.has(repair.index) || !repair.instruction.trim() || input.payload.encoded_statuses?.[repair.index] !== "pending")
108088
- throw new Error("Invalid review revision request");
108089
- 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);
108090
108594
  const quote = (value) => "'" + value.replace(/'/gu, "'\\''") + "'";
108091
108595
  return {
108092
108596
  candidate_id: row.candidate_id,
@@ -108166,7 +108670,7 @@ async function applyReviewDecisions(input) {
108166
108670
  });
108167
108671
  }
108168
108672
  seenApprovedIds.set(approvedRef, row.candidate_id);
108169
- const approvedPath = join100("knowledge", row.path);
108673
+ const approvedPath = join101("knowledge", row.path);
108170
108674
  const previousPathCandidate = seenApprovedPaths.get(knowledgeTargetPathKey(approvedPath));
108171
108675
  if (previousPathCandidate !== undefined) {
108172
108676
  throw new ContextError(ExitCode.UserError, `multiple approved review decisions target the same knowledge path: ${approvedPath}`, {
@@ -108216,7 +108720,7 @@ async function applyReviewDecisions(input) {
108216
108720
  for (const path3 of approvedPageIndex.byRelPath.keys()) {
108217
108721
  if (pagesToWrite.some((page) => page.relPath === path3 || page.previous?.path === path3))
108218
108722
  continue;
108219
- const before = await readFile78(join100(input.projectRoot, path3), "utf8");
108723
+ const before = await readFile79(join101(input.projectRoot, path3), "utf8");
108220
108724
  const local = path3.replace(/^knowledge\//u, "");
108221
108725
  const after = moveKnowledgeLinkTargets2(before, local, local, moved);
108222
108726
  navigationTargets.push(reviewFileTarget({ path: path3, baseContent: before, targetContent: after }));
@@ -108275,11 +108779,11 @@ async function applyReviewDecisions(input) {
108275
108779
  init_reviewShared();
108276
108780
  init_writeLock();
108277
108781
  init_verifyFrontmatter();
108278
- import { readFile as readFile79, writeFile as writeFile28 } from "node:fs/promises";
108782
+ import { readFile as readFile80, writeFile as writeFile28 } from "node:fs/promises";
108279
108783
  function deprecateApprovedPage(input) {
108280
108784
  return withProjectWriteLock(input.projectRoot, "deprecate-article", async () => {
108281
108785
  const page = await approvedPageForArticleId(input.projectRoot, input.viewRef);
108282
- const original = await readFile79(page.path, "utf8");
108786
+ const original = await readFile80(page.path, "utf8");
108283
108787
  const content3 = parseFrontmatterLoose(original).deprecated === true ? original : updateFrontmatter(original, (metadata) => ({ ...metadata, deprecated: true, timestamp: new Date().toISOString() }));
108284
108788
  const changed = content3 !== original;
108285
108789
  if (changed)
@@ -108300,12 +108804,12 @@ init_candidateLedger();
108300
108804
 
108301
108805
  // src/project/localHtmlReport.ts
108302
108806
  import { execFile as execFile11 } from "node:child_process";
108303
- import { isAbsolute as isAbsolute18, join as join101 } from "node:path";
108807
+ import { isAbsolute as isAbsolute18, join as join102 } from "node:path";
108304
108808
  import { pathToFileURL as pathToFileURL2 } from "node:url";
108305
108809
  import { promisify as promisify11 } from "node:util";
108306
108810
  var execFileAsync5 = promisify11(execFile11);
108307
108811
  function htmlReportReference(input) {
108308
- 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);
108309
108813
  return {
108310
108814
  format: "html",
108311
108815
  path: input.path,
@@ -108409,7 +108913,7 @@ function parsePayloadValues(parsed) {
108409
108913
  if (first.schema !== REVIEW_PAYLOAD_SCHEMA) {
108410
108914
  throw new ContextError(ExitCode.UserError, `review payload header schema must be ${REVIEW_PAYLOAD_SCHEMA}`, {
108411
108915
  category: ErrorCategory.UserInputInvalid,
108412
- next: "Copy a fresh review payload from context review html <collection>."
108916
+ next: "Use the current scope template from the review resource."
108413
108917
  });
108414
108918
  }
108415
108919
  const scope2 = parsePayloadScope(first.scope);
@@ -108437,7 +108941,18 @@ function parsePayloadValues(parsed) {
108437
108941
  }
108438
108942
  const decisions = first.decisions ?? parsed.slice(1);
108439
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
+ }
108440
108953
  return {
108954
+ ...Array.isArray(repairs) ? { repairs } : {},
108955
+ ...typeof first.baseline_hash === "string" ? { baseline_hash: first.baseline_hash.toLowerCase() } : {},
108441
108956
  decisions: validatedDecisions,
108442
108957
  ...typeof first.note === "string" ? { note: first.note } : {},
108443
108958
  ...collection !== undefined ? { collection } : {},
@@ -108474,59 +108989,21 @@ function parseReviewPayloadText(raw) {
108474
108989
  throw error;
108475
108990
  }
108476
108991
  }
108477
- async function readReviewPayloadFile(filePath2) {
108992
+ async function readReviewPayloadFile(filePath2, projectRoot) {
108478
108993
  let raw;
108479
108994
  try {
108480
- raw = await readFile80(filePath2, "utf8");
108995
+ raw = await readFile81(filePath2, "utf8");
108481
108996
  } catch (error) {
108482
108997
  const message = error instanceof Error ? error.message : String(error);
108483
108998
  throw new ContextError(ExitCode.UserError, `review payload file cannot be read: ${filePath2}`, {
108484
108999
  category: ErrorCategory.UserInputInvalid,
108485
109000
  path: filePath2,
108486
109001
  reason: message,
108487
- 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."
108488
109003
  });
108489
109004
  }
108490
- if (raw.trim().startsWith("CR")) {
108491
- try {
108492
- if (raw.trim().startsWith("CR2.")) {
108493
- const feedback = createReviewFeedbackCodec().decode(raw);
108494
- const collection2 = feedback.scope === "all" ? undefined : assertCollection(feedback.scope);
108495
- return {
108496
- decisions: [],
108497
- encoded_statuses: feedback.statuses.map((s) => s === "revised" ? "pending" : s),
108498
- feedback_repairs: feedback.repairs,
108499
- baseline_hash: feedback.baselineHash,
108500
- ...collection2 === undefined ? {} : { collection: collection2 },
108501
- scope: {
108502
- kind: collection2 === undefined ? "all" : "collection",
108503
- ...collection2 === undefined ? {} : { collection: collection2 },
108504
- count: feedback.statuses.length,
108505
- ids_sha256: feedback.idsHash,
108506
- candidates_sha256: feedback.contentHash
108507
- }
108508
- };
108509
- }
108510
- const decoded = createReviewCodeCodec().decode(raw);
108511
- const collection = decoded.scope === "all" ? undefined : assertCollection(decoded.scope);
108512
- return {
108513
- decisions: [],
108514
- encoded_statuses: decoded.statuses,
108515
- ...collection === undefined ? {} : { collection },
108516
- scope: {
108517
- kind: collection === undefined ? "all" : "collection",
108518
- ...collection === undefined ? {} : { collection },
108519
- count: decoded.count,
108520
- ids_sha256: decoded.idsHash,
108521
- candidates_sha256: decoded.contentHash
108522
- }
108523
- };
108524
- } catch (error) {
108525
- throw new ContextError(ExitCode.UserError, error instanceof Error ? error.message : String(error), {
108526
- category: ErrorCategory.UserInputInvalid,
108527
- 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."
108528
- });
108529
- }
109005
+ if (/^CR(?:[12]|P1)\./u.test(raw.trim())) {
109006
+ return readLegacyReviewPayload(raw, projectRoot ?? projectRootFromCwd(process.cwd()));
108530
109007
  }
108531
109008
  return parseReviewPayloadText(raw);
108532
109009
  }
@@ -108664,7 +109141,7 @@ async function runReviewHtmlCommand(input) {
108664
109141
  ...openResult.error !== undefined ? [`open_error: ${openResult.error}`] : []
108665
109142
  ] : []
108666
109143
  ],
108667
- 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."
108668
109145
  }));
108669
109146
  }
108670
109147
  async function runReviewListCommand(input) {
@@ -108717,41 +109194,45 @@ async function runReviewListCommand(input) {
108717
109194
  async function runReviewApplyCommand(input) {
108718
109195
  const projectRoot = projectRootFromCwd(input.cwd);
108719
109196
  const payloadPath = isAbsolute19(input.payloadInput) ? input.payloadInput : resolve31(input.cwd, input.payloadInput);
108720
- const payload = await readReviewPayloadFile(payloadPath);
109197
+ const payload = await readReviewPayloadFile(payloadPath, projectRoot);
108721
109198
  const result = await applyReviewDecisions({ projectRoot, payload });
108722
109199
  const continuation = await input.afterApply?.(projectRoot);
108723
109200
  process.stdout.write(formatApplyResult({ ...result, ...continuation === undefined ? {} : { continuation } }, input.format ?? "text"));
108724
109201
  }
108725
109202
  async function runReviewApproveAllCommand(input) {
108726
- if (input.managed === true && input.force === true) {
108727
- 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", {
108728
109205
  category: ErrorCategory.UserInputInvalid,
108729
109206
  code: "review-approval-authority-conflict",
108730
109207
  next: "context status --format json"
108731
109208
  });
108732
109209
  }
108733
- if (input.managed !== true && input.force !== true) {
108734
- 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", {
108735
109212
  category: ErrorCategory.UserInputInvalid,
108736
109213
  code: "review-approval-authority-required",
108737
109214
  next: "context status --format json"
108738
109215
  });
108739
109216
  }
108740
- 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";
108741
109220
  const projectRoot = projectRootFromCwd(input.cwd);
108742
109221
  const scoped = await reviewCommandScope({
108743
109222
  projectRoot,
108744
109223
  ...input.collection !== undefined ? { collection: input.collection } : {},
108745
109224
  ...input.all === true ? { all: true } : {}
108746
109225
  });
109226
+ const confirmedScope = input.confirmed === true ? await readConfirmedReviewScope(projectRoot, scoped.collection ?? "all") : undefined;
108747
109227
  const result = await applyReviewDecisions({
108748
109228
  projectRoot,
108749
109229
  payload: {
109230
+ ...confirmedScope === undefined ? {} : { baseline_hash: confirmedScope.baseline_hash },
108750
109231
  decisions: [],
108751
109232
  default: "approved",
108752
- note: input.managed === true ? "managed-session auto approval" : "explicit user force approval",
109233
+ note: input.managed === true ? "managed-session auto approval" : "explicit user confirmation",
108753
109234
  ...scoped.collection !== undefined ? { collection: scoped.collection } : {},
108754
- scope: scoped.scope
109235
+ scope: confirmedScope?.scope ?? scoped.scope
108755
109236
  }
108756
109237
  });
108757
109238
  const continuation = await input.afterApply?.(projectRoot);
@@ -108785,7 +109266,7 @@ async function runReviewApproveAllCommand(input) {
108785
109266
  process.stdout.write(formatFeedback({
108786
109267
  symbol: "✓",
108787
109268
  action: "approved",
108788
- subject: input.managed === true ? "managed review batch" : "force-approved review batch",
109269
+ subject: input.managed === true ? "managed review batch" : "confirmed review batch",
108789
109270
  headline: `${result.approved} candidate(s) approved`,
108790
109271
  body: [
108791
109272
  `decision source: ${decisionSource}`,
@@ -108954,7 +109435,7 @@ function supportsCommand(command2) {
108954
109435
  return options !== undefined && options.positionals.length === 0 && outputFormat(options) !== undefined;
108955
109436
  }
108956
109437
  if (topLevel === "review" && subcommand === "approve-all") {
108957
- 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"]));
108958
109439
  return options !== undefined && options.positionals.length <= 1 && !(options.flags.has("--all") && options.positionals.length === 1) && outputFormat(options) !== undefined;
108959
109440
  }
108960
109441
  if (topLevel === "package" && subcommand === "template" && command2[2] === "accept") {
@@ -108993,7 +109474,7 @@ async function executeBuild(cwd, command2) {
108993
109474
  });
108994
109475
  }
108995
109476
  async function executeReviewApproveAll(cwd, invocation) {
108996
- 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"]));
108997
109478
  if (options === undefined || options.positionals.length > 1)
108998
109479
  throw new Error("unsupported review approve-all command");
108999
109480
  await runReviewApproveAllCommand({
@@ -109001,7 +109482,7 @@ async function executeReviewApproveAll(cwd, invocation) {
109001
109482
  ...options.positionals[0] === undefined ? {} : { collection: options.positionals[0] },
109002
109483
  ...options.flags.has("--all") ? { all: true } : {},
109003
109484
  managed: invocation.managed || options.flags.has("--managed"),
109004
- force: options.flags.has("--force"),
109485
+ confirmed: options.flags.has("--confirmed"),
109005
109486
  verbose: options.flags.has("--verbose"),
109006
109487
  format: outputFormat(options) ?? "text"
109007
109488
  });
@@ -109329,8 +109810,8 @@ init_exitCode();
109329
109810
  init_maintenanceStorage();
109330
109811
  init_productionFeedback();
109331
109812
  import { randomUUID as randomUUID6 } from "node:crypto";
109332
- import { readFile as readFile81 } from "node:fs/promises";
109333
- import { join as join102 } from "node:path";
109813
+ import { readFile as readFile82 } from "node:fs/promises";
109814
+ import { join as join103 } from "node:path";
109334
109815
  async function beginProductionRevision(input) {
109335
109816
  return withProductionFeedback({ operation: "revision" }, () => withProjectWriteLock(input.projectRoot, "production-revision", async () => {
109336
109817
  await recoverDurableMultiFileTransactions(input.projectRoot);
@@ -109369,7 +109850,7 @@ async function beginProductionRevision(input) {
109369
109850
  const formal = approved.byPath.get(path3);
109370
109851
  if (!prior && !formal)
109371
109852
  throw invalid2("Write the current task first; there is no article draft to revise yet.");
109372
- 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");
109373
109854
  const sections = prior?.indexer_candidate.sections.map((section) => ({ id: section.section_key, references: section.references })) ?? formal?.sections;
109374
109855
  const sources = [];
109375
109856
  for (const source2 of owner.sources) {
@@ -109546,7 +110027,7 @@ init_cliFeedback();
109546
110027
  init_errors3();
109547
110028
  init_exitCode();
109548
110029
  var import_yaml41 = __toESM(require_dist(), 1);
109549
- import { readFile as readFile82 } from "node:fs/promises";
110030
+ import { readFile as readFile83 } from "node:fs/promises";
109550
110031
  function userInputError2(message, detail = {}) {
109551
110032
  return new ContextError(ExitCode.UserError, message, {
109552
110033
  category: ErrorCategory.UserInputInvalid,
@@ -109615,7 +110096,7 @@ async function readPayloadTextFromStdin(stdin) {
109615
110096
  }
109616
110097
  async function readPayloadText(path3) {
109617
110098
  if (path3 !== "-")
109618
- return readFile82(path3, "utf8");
110099
+ return readFile83(path3, "utf8");
109619
110100
  return readPayloadTextFromStdin(process.stdin);
109620
110101
  }
109621
110102
  async function readYamlOrJsonInput(input) {
@@ -110009,7 +110490,7 @@ init_atomicWrite();
110009
110490
  var import_yaml44 = __toESM(require_dist(), 1);
110010
110491
  import { Buffer as Buffer5 } from "node:buffer";
110011
110492
  import { createHash as createHash32 } from "node:crypto";
110012
- import { join as join106 } from "node:path";
110493
+ import { join as join107 } from "node:path";
110013
110494
  var INLINE_LIMIT = 16 * 1024;
110014
110495
  function record4(value) {
110015
110496
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
@@ -110035,8 +110516,8 @@ async function prepareActionCompletionOutput(input) {
110035
110516
  if (production && Buffer5.byteLength(full) <= INLINE_LIMIT)
110036
110517
  return input.result;
110037
110518
  const digest7 = createHash32("sha256").update(full).digest("hex");
110038
- const root2 = join106(input.projectRoot, ".tmp/context-runtime/action-results");
110039
- const resultFile = join106(root2, `${digest7}.json`);
110519
+ const root2 = join107(input.projectRoot, ".tmp/context-runtime/action-results");
110520
+ const resultFile = join107(root2, `${digest7}.json`);
110040
110521
  await atomicWriteFile(resultFile, full);
110041
110522
  if (production)
110042
110523
  return {
@@ -110455,7 +110936,7 @@ init_cliFeedback();
110455
110936
  import { existsSync as existsSync27 } from "node:fs";
110456
110937
  import { readdir as readdir27, rm as rm23 } from "node:fs/promises";
110457
110938
  import { homedir } from "node:os";
110458
- import { join as join107 } from "node:path";
110939
+ import { join as join108 } from "node:path";
110459
110940
  var ORPHAN_MARKER = ".orphaned_at";
110460
110941
  var CLAUDE_PLUGIN_CACHE_ROOT_ENV = "C4A_CLAUDE_PLUGIN_CACHE_ROOT";
110461
110942
  var CLAUDE_PLUGIN_CACHE_HOME_ENV = "C4A_CLAUDE_PLUGIN_CACHE_HOME";
@@ -110472,19 +110953,19 @@ async function cleanClaudePluginCache(opts = {}) {
110472
110953
  for (const mp of marketplaces) {
110473
110954
  if (!mp.isDirectory())
110474
110955
  continue;
110475
- const mpDir = join107(cacheRoot, mp.name);
110956
+ const mpDir = join108(cacheRoot, mp.name);
110476
110957
  const plugins = await readdir27(mpDir, { withFileTypes: true });
110477
110958
  for (const pl of plugins) {
110478
110959
  if (!pl.isDirectory())
110479
110960
  continue;
110480
- const plDir = join107(mpDir, pl.name);
110961
+ const plDir = join108(mpDir, pl.name);
110481
110962
  const versions = await readdir27(plDir, { withFileTypes: true });
110482
110963
  for (const ver of versions) {
110483
110964
  if (!ver.isDirectory())
110484
110965
  continue;
110485
110966
  scanned += 1;
110486
- const verDir = join107(plDir, ver.name);
110487
- const markerPath = join107(verDir, ORPHAN_MARKER);
110967
+ const verDir = join108(plDir, ver.name);
110968
+ const markerPath = join108(verDir, ORPHAN_MARKER);
110488
110969
  if (!existsSync27(markerPath))
110489
110970
  continue;
110490
110971
  const label2 = `${mp.name}/${pl.name}/${ver.name}`;
@@ -110516,7 +110997,7 @@ function resolveClaudePluginCacheRoot(opts) {
110516
110997
  if (explicitRoot)
110517
110998
  return explicitRoot;
110518
110999
  const home = opts.home ?? process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV] ?? homedir();
110519
- return join107(home, ".claude", "plugins", "cache");
111000
+ return join108(home, ".claude", "plugins", "cache");
110520
111001
  }
110521
111002
  async function isEmptyDir(dir) {
110522
111003
  try {
@@ -110548,13 +111029,13 @@ init_exitCode();
110548
111029
 
110549
111030
  // src/lib/packageVersion.ts
110550
111031
  import { existsSync as existsSync28, readFileSync as readFileSync9 } from "node:fs";
110551
- import { dirname as dirname43, join as join108 } from "node:path";
111032
+ import { dirname as dirname43, join as join109 } from "node:path";
110552
111033
  import { fileURLToPath as fileURLToPath8 } from "node:url";
110553
111034
  function readPackageVersion() {
110554
111035
  try {
110555
111036
  let dir = dirname43(fileURLToPath8(import.meta.url));
110556
111037
  for (let depth = 0;depth < 8; depth += 1) {
110557
- const packagePath = join108(dir, "package.json");
111038
+ const packagePath = join109(dir, "package.json");
110558
111039
  if (existsSync28(packagePath)) {
110559
111040
  const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
110560
111041
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -110577,7 +111058,7 @@ function readPackageVersion() {
110577
111058
 
110578
111059
  // src/project/sourceCommands.ts
110579
111060
  init_src2();
110580
- import { readFile as readFile98 } from "node:fs/promises";
111061
+ import { readFile as readFile99 } from "node:fs/promises";
110581
111062
  import { isAbsolute as isAbsolute23, resolve as resolve41 } from "node:path";
110582
111063
  init_cliFeedback();
110583
111064
  init_errors3();
@@ -110940,15 +111421,15 @@ async function restoreRepositorySources(input) {
110940
111421
  init_src2();
110941
111422
  init_documentCapture();
110942
111423
  import { existsSync as existsSync30 } from "node:fs";
110943
- import { readFile as readFile88 } from "node:fs/promises";
110944
- import { join as join110 } from "node:path";
111424
+ import { readFile as readFile89 } from "node:fs/promises";
111425
+ import { join as join111 } from "node:path";
110945
111426
 
110946
111427
  // src/project/sourceCommandViews.ts
110947
111428
  init_documentSiteDetection();
110948
111429
  init_workspace();
110949
111430
  init_documentBatchManifest();
110950
- import { readFile as readFile87 } from "node:fs/promises";
110951
- import { join as join109 } from "node:path";
111431
+ import { readFile as readFile88 } from "node:fs/promises";
111432
+ import { join as join110 } from "node:path";
110952
111433
  function repoSourceAgentView(source2) {
110953
111434
  return {
110954
111435
  id: source2.id ?? source2.name,
@@ -111009,7 +111490,7 @@ async function fileSourceAgentViewWithNextAction(input) {
111009
111490
  };
111010
111491
  }
111011
111492
  function documentSourceManifestPath(source2) {
111012
- return source2.snapshot?.manifest ?? join109(source2.materializedAt, "manifest.json");
111493
+ return source2.snapshot?.manifest ?? join110(source2.materializedAt, "manifest.json");
111013
111494
  }
111014
111495
  async function fileSourceDocumentSiteHint(input) {
111015
111496
  const detection = await detectDocumentSiteFiles({
@@ -111019,7 +111500,7 @@ async function fileSourceDocumentSiteHint(input) {
111019
111500
  let snapshotConfigured = false;
111020
111501
  const manifest = documentSourceManifestPath(input.source);
111021
111502
  try {
111022
- 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);
111023
111504
  snapshotConfigured = manifestUsesMdxJsonDocs(parsed);
111024
111505
  } catch {
111025
111506
  snapshotConfigured = false;
@@ -111052,11 +111533,11 @@ async function larkSourceAgentViewWithNextAction(input) {
111052
111533
  init_documentBatchManifest();
111053
111534
  init_documentSnapshotFidelity();
111054
111535
  function documentSourceManifestPath2(source2) {
111055
- return source2.snapshot?.manifest ?? join110(source2.materializedAt, "manifest.json");
111536
+ return source2.snapshot?.manifest ?? join111(source2.materializedAt, "manifest.json");
111056
111537
  }
111057
111538
  async function documentSnapshotState(input) {
111058
111539
  const manifest = documentSourceManifestPath2(input.source);
111059
- const manifestPath = join110(input.projectRoot, manifest);
111540
+ const manifestPath = join111(input.projectRoot, manifest);
111060
111541
  if (!existsSync30(manifestPath)) {
111061
111542
  return {
111062
111543
  snapshotReady: false,
@@ -111067,7 +111548,7 @@ async function documentSnapshotState(input) {
111067
111548
  };
111068
111549
  }
111069
111550
  try {
111070
- 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);
111071
111552
  if (parsed === null) {
111072
111553
  return {
111073
111554
  snapshotReady: false,
@@ -111135,7 +111616,7 @@ async function documentSnapshotState(input) {
111135
111616
  const missing = [
111136
111617
  ...parsed.files.map((file) => file.path),
111137
111618
  ...(parsed.assets ?? []).filter((asset) => asset.content_hash !== undefined).map((asset) => asset.path)
111138
- ].find((path3) => !existsSync30(join110(input.projectRoot, input.source.materializedAt, path3)));
111619
+ ].find((path3) => !existsSync30(join111(input.projectRoot, input.source.materializedAt, path3)));
111139
111620
  if (missing !== undefined) {
111140
111621
  return {
111141
111622
  snapshotReady: false,
@@ -111236,8 +111717,8 @@ init_errors3();
111236
111717
  init_exitCode();
111237
111718
  var import_yaml47 = __toESM(require_dist(), 1);
111238
111719
  import { createHash as createHash33 } from "node:crypto";
111239
- import { readFile as readFile89, realpath as realpath12 } from "node:fs/promises";
111240
- 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";
111241
111722
  init_writeLock();
111242
111723
  var SOURCE_NAME_PATTERN3 = /^[a-z0-9][a-z0-9._-]*$/u;
111243
111724
  function isDateSourceNamespace(value) {
@@ -111333,7 +111814,7 @@ function assertSafeFileInclude(value) {
111333
111814
  }
111334
111815
  async function readRegistryDocument(projectRoot, registryPath2) {
111335
111816
  try {
111336
- const content3 = await readFile89(join111(projectRoot, registryPath2), "utf8");
111817
+ const content3 = await readFile90(join112(projectRoot, registryPath2), "utf8");
111337
111818
  return content3.trim().length === 0 ? { sources: [] } : import_yaml47.default.parse(content3);
111338
111819
  } catch (error) {
111339
111820
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
@@ -111451,7 +111932,7 @@ async function addFileSourceUnlocked(input) {
111451
111932
  const record6 = entry2;
111452
111933
  return record6.name !== input.name && record6.id !== input.name;
111453
111934
  }), nextEntry];
111454
- 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 }));
111455
111936
  const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
111456
111937
  const entry = updated.files.find((source2) => source2.name === input.name || source2.id === input.name);
111457
111938
  if (entry === undefined) {
@@ -111510,7 +111991,7 @@ async function addLarkSourceUnlocked(input) {
111510
111991
  const record6 = entry2;
111511
111992
  return record6.name !== input.name && record6.id !== input.name;
111512
111993
  }), nextEntry];
111513
- 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 }));
111514
111995
  const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
111515
111996
  const entry = updated.larks.find((source2) => source2.name === input.name || source2.id === input.name);
111516
111997
  if (entry === undefined) {
@@ -111542,18 +112023,18 @@ init_cliFeedback();
111542
112023
  init_errors3();
111543
112024
  init_exitCode();
111544
112025
  var import_yaml48 = __toESM(require_dist(), 1);
111545
- import { readFile as readFile90 } from "node:fs/promises";
111546
- import { join as join112 } from "node:path";
112026
+ import { readFile as readFile91 } from "node:fs/promises";
112027
+ import { join as join113 } from "node:path";
111547
112028
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
111548
112029
  function identityKeys(entry) {
111549
112030
  return new Set([entry.name, entry.id]);
111550
112031
  }
111551
112032
  async function createLarkBatchRegistration(projectRoot, namespace) {
111552
112033
  const registry2 = await loadSourcesRegistry({ rootDir: projectRoot });
111553
- const registryPath2 = join112(projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH);
112034
+ const registryPath2 = join113(projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH);
111554
112035
  let committedSources;
111555
112036
  try {
111556
- const text10 = await readFile90(registryPath2, "utf8");
112037
+ const text10 = await readFile91(registryPath2, "utf8");
111557
112038
  committedSources = text10.trim().length === 0 ? [] : import_yaml48.default.parse(text10).sources ?? [];
111558
112039
  } catch (error) {
111559
112040
  if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
@@ -111671,8 +112152,8 @@ init_cliFeedback();
111671
112152
  init_errors3();
111672
112153
  init_exitCode();
111673
112154
  import { createHash as createHash34 } from "node:crypto";
111674
- import { lstat as lstat14, readFile as readFile91 } from "node:fs/promises";
111675
- 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";
111676
112157
  var INPUT_SCHEMA = "context.source-batch.input.v1";
111677
112158
  var STATE_SCHEMA = "context.source-batch.state.v1";
111678
112159
  var RUNTIME_PARTS = [".tmp", "context-runtime", "source-batches"];
@@ -111723,14 +112204,14 @@ function paths(projectRoot, jobId) {
111723
112204
  throw journalError("source-batch-id-invalid", "Source batch checkpoint ID must contain exactly 32 lowercase hexadecimal characters.");
111724
112205
  }
111725
112206
  const directory = resolve37(projectRoot, ...RUNTIME_PARTS, jobId);
111726
- return { inputPath: join113(directory, "input.json"), statePath: join113(directory, "state.json") };
112207
+ return { inputPath: join114(directory, "input.json"), statePath: join114(directory, "state.json") };
111727
112208
  }
111728
112209
  async function assertJournalPath(projectRoot, jobId, filename) {
111729
112210
  paths(projectRoot, jobId);
111730
112211
  let current2 = resolve37(projectRoot);
111731
112212
  const parts = [...RUNTIME_PARTS, jobId, filename];
111732
112213
  for (const [index2, part] of parts.entries()) {
111733
- current2 = join113(current2, part);
112214
+ current2 = join114(current2, part);
111734
112215
  let stat11;
111735
112216
  try {
111736
112217
  stat11 = await lstat14(current2);
@@ -111761,7 +112242,7 @@ async function loadSourceBatchJournal(input) {
111761
112242
  const { inputPath } = paths(input.projectRoot, input.jobId);
111762
112243
  try {
111763
112244
  await assertJournalPath(input.projectRoot, input.jobId, "input.json");
111764
- const stored = JSON.parse(await readFile91(inputPath, "utf8"));
112245
+ const stored = JSON.parse(await readFile92(inputPath, "utf8"));
111765
112246
  if (stored === null || typeof stored !== "object" || Array.isArray(stored)) {
111766
112247
  throw journalError("source-batch-input-invalid", "Source batch checkpoint input must be an object.");
111767
112248
  }
@@ -111864,7 +112345,7 @@ async function readSourceBatchJournalStatus(input) {
111864
112345
  let warning;
111865
112346
  try {
111866
112347
  await assertJournalPath(journal.projectRoot, journal.jobId, "state.json");
111867
- const parsed = JSON.parse(await readFile91(journal.statePath, "utf8"));
112348
+ const parsed = JSON.parse(await readFile92(journal.statePath, "utf8"));
111868
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))) {
111869
112350
  throw journalError("source-batch-state-invalid", "Source batch checkpoint progress is invalid.");
111870
112351
  }
@@ -112455,13 +112936,13 @@ init_exitCode();
112455
112936
 
112456
112937
  // src/project/wikiDiscovery.ts
112457
112938
  import { mkdir as mkdir38 } from "node:fs/promises";
112458
- import { join as join115, relative as relative30 } from "node:path";
112939
+ import { join as join116, relative as relative30 } from "node:path";
112459
112940
 
112460
112941
  // src/project/wikiDiscoveryStore.ts
112461
112942
  init_atomicWrite();
112462
112943
  import { createHash as createHash35, randomUUID as randomUUID8 } from "node:crypto";
112463
- 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";
112464
- 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";
112465
112946
  function wikiDiscoveryDigest(value) {
112466
112947
  return createHash35("sha256").update(value).digest("hex");
112467
112948
  }
@@ -112475,7 +112956,7 @@ async function assertWikiDiscoveryPath(root2, path3) {
112475
112956
  throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint must stay inside its workspace");
112476
112957
  let current2 = base;
112477
112958
  for (const part of parts) {
112478
- current2 = join114(current2, part);
112959
+ current2 = join115(current2, part);
112479
112960
  try {
112480
112961
  if ((await lstat15(current2)).isSymbolicLink()) {
112481
112962
  throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint paths must not contain symbolic links");
@@ -112491,7 +112972,7 @@ async function readWikiDiscoveryJson(path3) {
112491
112972
  try {
112492
112973
  if ((await lstat15(path3)).isSymbolicLink())
112493
112974
  throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint must not be a symbolic link");
112494
- return JSON.parse(await readFile92(path3, "utf8"));
112975
+ return JSON.parse(await readFile93(path3, "utf8"));
112495
112976
  } catch (error) {
112496
112977
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
112497
112978
  return;
@@ -112504,12 +112985,12 @@ async function saveWikiDiscoveryJson(path3, value) {
112504
112985
  }
112505
112986
  async function loadWikiDiscoveryReceipts(directory) {
112506
112987
  const result = new Map;
112507
- await assertWikiDiscoveryPath(directory, join114(directory, "pages"));
112508
- await mkdir37(join114(directory, "pages"), { recursive: true });
112509
- 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"))) {
112510
112991
  if (!/^[a-f0-9]{64}\.json$/u.test(file))
112511
112992
  continue;
112512
- const value = await readWikiDiscoveryJson(join114(directory, "pages", file));
112993
+ const value = await readWikiDiscoveryJson(join115(directory, "pages", file));
112513
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) {
112514
112995
  throw new WikiDiscoveryError("checkpoint-invalid", false, "Discovery page checkpoint is invalid; preserve it for inspection");
112515
112996
  }
@@ -112528,14 +113009,14 @@ function ownerRunning(pid) {
112528
113009
  }
112529
113010
  }
112530
113011
  async function acquireWikiDiscoveryLease(directory) {
112531
- const leases = join114(directory, "leases");
113012
+ const leases = join115(directory, "leases");
112532
113013
  await assertWikiDiscoveryPath(directory, leases);
112533
113014
  await mkdir37(leases, { recursive: true });
112534
113015
  for (let attempt = 0;attempt < 8; attempt++) {
112535
113016
  const files = (await readdir28(leases)).filter((file) => /^\d{12}\.json$/u.test(file)).sort();
112536
113017
  const latest = files.at(-1);
112537
113018
  if (latest) {
112538
- const current2 = await readWikiDiscoveryJson(join114(leases, latest));
113019
+ const current2 = await readWikiDiscoveryJson(join115(leases, latest));
112539
113020
  if (!current2 || !Number.isSafeInteger(current2.pid) || current2.pid <= 0 || typeof current2.token !== "string" || typeof current2.released !== "boolean") {
112540
113021
  throw new WikiDiscoveryError("checkpoint-invalid", false, "Discovery lease is invalid; preserve it for inspection");
112541
113022
  }
@@ -112544,9 +113025,9 @@ async function acquireWikiDiscoveryLease(directory) {
112544
113025
  }
112545
113026
  }
112546
113027
  const sequence = latest ? Number(latest.slice(0, 12)) + 1 : 1;
112547
- const path3 = join114(leases, `${String(sequence).padStart(12, "0")}.json`);
113028
+ const path3 = join115(leases, `${String(sequence).padStart(12, "0")}.json`);
112548
113029
  const owner = { pid: process.pid, token: randomUUID8(), released: false };
112549
- const temporary = join114(leases, `.lease-${owner.token}`);
113030
+ const temporary = join115(leases, `.lease-${owner.token}`);
112550
113031
  await writeFile29(temporary, JSON.stringify(owner), { flag: "wx", mode: 384 });
112551
113032
  try {
112552
113033
  await link2(temporary, path3);
@@ -112671,7 +113152,7 @@ async function discoverLarkWiki(input) {
112671
113152
  throw new WikiDiscoveryError("invalid-concurrency", false, "Wiki concurrency must be an integer from 1 to 4");
112672
113153
  }
112673
113154
  const jobId = wikiDiscoveryDigest(url);
112674
- const directory = join115(input.projectRoot, ".tmp", "context-runtime", "wiki-discovery", jobId);
113155
+ const directory = join116(input.projectRoot, ".tmp", "context-runtime", "wiki-discovery", jobId);
112675
113156
  await assertWikiDiscoveryPath(input.projectRoot, directory);
112676
113157
  await mkdir38(directory, { recursive: true });
112677
113158
  const release = await acquireWikiDiscoveryLease(directory);
@@ -112683,7 +113164,7 @@ async function discoverLarkWiki(input) {
112683
113164
  }
112684
113165
  async function runDiscovery(input, job) {
112685
113166
  const { directory, url, identity: identity2 } = job;
112686
- const metadataPath = join115(directory, "job.json");
113167
+ const metadataPath = join116(directory, "job.json");
112687
113168
  let metadata = await readWikiDiscoveryJson(metadataPath);
112688
113169
  if (metadata && (metadata.protocol !== "context.wiki-discovery.v1" || metadata.url !== url || metadata.identity !== identity2)) {
112689
113170
  throw new WikiDiscoveryError("job-identity-mismatch", false, "Resume must preserve the discovery's original URL and identity");
@@ -112732,7 +113213,7 @@ async function runDiscovery(input, job) {
112732
113213
  }
112733
113214
  const key = wikiDiscoveryPageKey(cursor);
112734
113215
  try {
112735
- await saveWikiDiscoveryJson(join115(directory, "pages", `${key}.json`), receipt2);
113216
+ await saveWikiDiscoveryJson(join116(directory, "pages", `${key}.json`), receipt2);
112736
113217
  receipts.set(key, receipt2);
112737
113218
  } catch {
112738
113219
  checkpointError = "A discovery page could not be checkpointed; saved pages remain reusable and unsaved pages must be requested again";
@@ -112750,7 +113231,7 @@ async function finishDiscovery(input, state) {
112750
113231
  const { directory, url, identity: identity2, jobId, metadata, view } = state;
112751
113232
  let checkpointError = state.checkpointError;
112752
113233
  let status = input.signal?.aborted ? "paused" : checkpointError || view.pending.length || view.failed.length || !metadata.root ? "partial" : "completed";
112753
- const manifestPath = join115(directory, "manifest.json");
113234
+ const manifestPath = join116(directory, "manifest.json");
112754
113235
  try {
112755
113236
  await saveWikiDiscoveryJson(manifestPath, {
112756
113237
  protocol: "context.wiki-discovery.manifest.v1",
@@ -112930,8 +113411,8 @@ init_writeLock();
112930
113411
  var import_yaml51 = __toESM(require_dist(), 1);
112931
113412
  import { existsSync as existsSync31 } from "node:fs";
112932
113413
  import { createHash as createHash36 } from "node:crypto";
112933
- import { readFile as readFile93, readdir as readdir29, rm as rm26 } from "node:fs/promises";
112934
- 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";
112935
113416
  function sourceIdentity(source2) {
112936
113417
  if (source2.kind === "source.collection")
112937
113418
  return;
@@ -112963,10 +113444,10 @@ function collectStrings(value, output) {
112963
113444
  }
112964
113445
  }
112965
113446
  async function yamlReferences(input) {
112966
- const absolutePath = join116(input.projectRoot, input.path);
113447
+ const absolutePath = join117(input.projectRoot, input.path);
112967
113448
  if (!existsSync31(absolutePath))
112968
113449
  return false;
112969
- const parsed = import_yaml51.default.parse(await readFile93(absolutePath, "utf8"));
113450
+ const parsed = import_yaml51.default.parse(await readFile94(absolutePath, "utf8"));
112970
113451
  const strings = [];
112971
113452
  collectStrings(parsed, strings);
112972
113453
  return strings.some((value) => stringReferencesSource(value, input.source));
@@ -113092,8 +113573,8 @@ async function registryRemovalWrite(projectRoot, source2) {
113092
113573
  const path3 = registryPath2(source2.type);
113093
113574
  if (path3 === null)
113094
113575
  return;
113095
- const absolutePath = join116(projectRoot, path3);
113096
- 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: [] };
113097
113578
  return {
113098
113579
  path: absolutePath,
113099
113580
  bytes: import_yaml51.default.stringify(removeDocumentEntry(document4, source2))
@@ -113111,7 +113592,7 @@ function safeManagedMaterializedPath(projectRoot, source2) {
113111
113592
  return absolute;
113112
113593
  }
113113
113594
  function safeManagedManifestPath(projectRoot, source2) {
113114
- const manifest = source2.manifest ?? join116(source2.materializedAt, "manifest.json");
113595
+ const manifest = source2.manifest ?? join117(source2.materializedAt, "manifest.json");
113115
113596
  if (isAbsolute22(manifest))
113116
113597
  throw unsafeOwnership(source2, manifest);
113117
113598
  const absolute = resolve39(projectRoot, manifest);
@@ -113240,7 +113721,7 @@ async function createRemovalPlan(projectRoot, selector) {
113240
113721
  source: source2,
113241
113722
  registry: registryPath2(source2.type),
113242
113723
  registryBytes: registryWrite?.bytes ?? null,
113243
- 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,
113244
113725
  references,
113245
113726
  cleanup,
113246
113727
  manifestBytes: manifestWrite?.bytes ?? null
@@ -113273,10 +113754,10 @@ function publicRemovalResult(plan, action) {
113273
113754
  };
113274
113755
  }
113275
113756
  async function pruneExtractRuntime(projectRoot, source2) {
113276
- const fingerprintPath = join116(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
113757
+ const fingerprintPath = join117(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
113277
113758
  const removedPhaseIds = new Set;
113278
113759
  if (existsSync31(fingerprintPath)) {
113279
- const parsed = JSON.parse(await readFile93(fingerprintPath, "utf8"));
113760
+ const parsed = JSON.parse(await readFile94(fingerprintPath, "utf8"));
113280
113761
  const phases = parsed.phases ?? {};
113281
113762
  const next2 = Object.fromEntries(Object.entries(phases).filter(([phaseId, raw]) => {
113282
113763
  if (raw === null || typeof raw !== "object" || Array.isArray(raw))
@@ -113290,27 +113771,27 @@ async function pruneExtractRuntime(projectRoot, source2) {
113290
113771
  await atomicWriteFile(fingerprintPath, `${JSON.stringify({ ...parsed, phases: next2 }, null, 2)}
113291
113772
  `);
113292
113773
  }
113293
- 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");
113294
113775
  if (existsSync31(phaseOwnershipPath) && removedPhaseIds.size > 0) {
113295
- const parsed = JSON.parse(await readFile93(phaseOwnershipPath, "utf8"));
113776
+ const parsed = JSON.parse(await readFile94(phaseOwnershipPath, "utf8"));
113296
113777
  const phases = Object.fromEntries(Object.entries(parsed.phases ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
113297
113778
  await atomicWriteFile(phaseOwnershipPath, `${JSON.stringify({ ...parsed, phases }, null, 2)}
113298
113779
  `);
113299
113780
  }
113300
- const symbolPath = join116(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
113781
+ const symbolPath = join117(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
113301
113782
  if (existsSync31(symbolPath)) {
113302
- const parsed = JSON.parse(await readFile93(symbolPath, "utf8"));
113783
+ const parsed = JSON.parse(await readFile94(symbolPath, "utf8"));
113303
113784
  const symbols = Array.isArray(parsed.symbols) ? parsed.symbols.filter((entry) => entry === null || typeof entry !== "object" || Array.isArray(entry) || entry.source !== source2.name) : [];
113304
113785
  const phaseFingerprints = Object.fromEntries(Object.entries(parsed.phaseFingerprints ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
113305
113786
  await atomicWriteFile(symbolPath, `${JSON.stringify({ ...parsed, phaseFingerprints, symbols }, null, 2)}
113306
113787
  `);
113307
113788
  }
113308
- const snapshotRoot = join116(projectRoot, ".tmp/context-runtime/extract/candidates");
113789
+ const snapshotRoot = join117(projectRoot, ".tmp/context-runtime/extract/candidates");
113309
113790
  const visit4 = async (directory) => {
113310
113791
  if (!existsSync31(directory))
113311
113792
  return;
113312
113793
  for (const entry of await readdir29(directory, { withFileTypes: true })) {
113313
- const path3 = join116(directory, entry.name);
113794
+ const path3 = join117(directory, entry.name);
113314
113795
  if (entry.isDirectory()) {
113315
113796
  await visit4(path3);
113316
113797
  continue;
@@ -113318,7 +113799,7 @@ async function pruneExtractRuntime(projectRoot, source2) {
113318
113799
  if (!entry.isFile() || !entry.name.endsWith(".json"))
113319
113800
  continue;
113320
113801
  try {
113321
- const parsed = JSON.parse(await readFile93(path3, "utf8"));
113802
+ const parsed = JSON.parse(await readFile94(path3, "utf8"));
113322
113803
  const refs = Array.isArray(parsed.source_refs) ? parsed.source_refs : [];
113323
113804
  if (parsed.source === source2.name || refs.some((ref2) => typeof ref2 === "string" && stringReferencesSource(ref2, source2))) {
113324
113805
  await rm26(path3, { force: true });
@@ -113360,7 +113841,7 @@ async function removeProjectSource(input) {
113360
113841
  });
113361
113842
  }
113362
113843
  await applyAtomicFileBatch({
113363
- transactionRoot: join116(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
113844
+ transactionRoot: join117(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
113364
113845
  writes: [...plan.registryWrite === undefined ? [] : [plan.registryWrite], ...plan.manifestWrite !== undefined ? [plan.manifestWrite] : []],
113365
113846
  removals: plan.absoluteRemovals
113366
113847
  });
@@ -113376,7 +113857,7 @@ init_workspace();
113376
113857
  init_writeLock();
113377
113858
  init_durableMultiFileTransaction();
113378
113859
  init_durableSingleFileTransaction();
113379
- import { readFile as readFile94 } from "node:fs/promises";
113860
+ import { readFile as readFile95 } from "node:fs/promises";
113380
113861
  import ts from "typescript";
113381
113862
  function generateSourceConfiguration(text10, selected) {
113382
113863
  const file = ts.createSourceFile("index.ts", text10, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
@@ -113516,7 +113997,7 @@ function generateSourceConfiguration(text10, selected) {
113516
113997
  async function configureRegisteredSources(projectRoot, selected) {
113517
113998
  return withProjectWriteLock(projectRoot, "source-project-configuration", async () => {
113518
113999
  const path3 = "src/index.ts";
113519
- 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"));
113520
114001
  if (config.context?.entry !== path3)
113521
114002
  return {
113522
114003
  status: "manual",
@@ -113525,7 +114006,7 @@ async function configureRegisteredSources(projectRoot, selected) {
113525
114006
  sources: selected
113526
114007
  };
113527
114008
  const target = await safeProjectTarget(projectRoot, path3);
113528
- const text10 = await readFile94(target, "utf8");
114009
+ const text10 = await readFile95(target, "utf8");
113529
114010
  const updated = generateSourceConfiguration(text10, selected);
113530
114011
  if (updated === undefined)
113531
114012
  return {
@@ -113605,7 +114086,7 @@ async function readIncludeList(projectRoot, path3) {
113605
114086
  }
113606
114087
  let content3;
113607
114088
  try {
113608
- content3 = await readFile98(isAbsolute23(trimmed) ? resolve41(trimmed) : resolve41(projectRoot, trimmed), "utf8");
114089
+ content3 = await readFile99(isAbsolute23(trimmed) ? resolve41(trimmed) : resolve41(projectRoot, trimmed), "utf8");
113609
114090
  } catch (error) {
113610
114091
  throw new ContextError(ExitCode.UserError, `cannot read include list: ${trimmed}`, {
113611
114092
  category: ErrorCategory.UserInputInvalid,
@@ -114027,7 +114508,7 @@ init_exitCode();
114027
114508
  var import_yaml54 = __toESM(require_dist(), 1);
114028
114509
  import { constants as constants6, existsSync as existsSync32 } from "node:fs";
114029
114510
  import { lstat as lstat16, open as open5, readdir as readdir30 } from "node:fs/promises";
114030
- 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";
114031
114512
  import { fileURLToPath as fileURLToPath9 } from "node:url";
114032
114513
  function bundledSkillRoot() {
114033
114514
  const directory = dirname45(fileURLToPath9(import.meta.url));
@@ -114057,7 +114538,7 @@ async function readProductionSkills(root2) {
114057
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);
114058
114539
  const skills = [];
114059
114540
  for (const directory of entries2) {
114060
- const entry = join118(root2, directory.name, "SKILL.md");
114541
+ const entry = join119(root2, directory.name, "SKILL.md");
114061
114542
  if (!(await lstat16(entry)).isFile())
114062
114543
  throw new TypeError(`Skill entry must be a regular file: ${entry}`);
114063
114544
  const handle3 = await open5(entry, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
@@ -114465,8 +114946,8 @@ init_cliFeedback();
114465
114946
  init_errors3();
114466
114947
  init_exitCode();
114467
114948
  import { createHash as createHash37 } from "node:crypto";
114468
- 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";
114469
- 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";
114470
114951
  var MARKER = ".context-skill-install.json";
114471
114952
  function conflict(path3) {
114472
114953
  throw new ContextError(ExitCode.UserError, `Local skill install conflict: ${path3}`, {
@@ -114497,7 +114978,7 @@ async function digest8(root2) {
114497
114978
  for (const name3 of (await readdir31(dir)).sort()) {
114498
114979
  if (!prefix && name3 === MARKER)
114499
114980
  continue;
114500
- const path3 = join119(dir, name3);
114981
+ const path3 = join120(dir, name3);
114501
114982
  const entry = await lstat17(path3);
114502
114983
  if (entry.isSymbolicLink())
114503
114984
  conflict(path3);
@@ -114505,7 +114986,7 @@ async function digest8(root2) {
114505
114986
  if (entry.isDirectory())
114506
114987
  await visit4(path3, `${prefix}${name3}/`);
114507
114988
  else if (entry.isFile())
114508
- hash4.update(await readFile99(path3));
114989
+ hash4.update(await readFile100(path3));
114509
114990
  else
114510
114991
  conflict(path3);
114511
114992
  }
@@ -114517,31 +114998,31 @@ async function installLocalSkills(root2, path3, dryRun, agent) {
114517
114998
  if (!path3.trim())
114518
114999
  conflict("--local requires a non-empty path");
114519
115000
  const targetRoot = resolve44(path3);
114520
- const skillsRoot = join119(targetRoot, "skills");
115001
+ const skillsRoot = join120(targetRoot, "skills");
114521
115002
  await checkParents(skillsRoot);
114522
- const sourceRoot2 = join119(root2, "skills");
115003
+ const sourceRoot2 = join120(root2, "skills");
114523
115004
  const commandPlans = [];
114524
115005
  const commandEntries = new Set;
114525
115006
  if (agent === "claude" || agent === "cursor") {
114526
- for (const name3 of await readdir31(join119(root2, "claude", "commands"))) {
115007
+ for (const name3 of await readdir31(join120(root2, "claude", "commands"))) {
114527
115008
  if (name3.endsWith(".md"))
114528
115009
  commandEntries.add(name3.slice(0, -3));
114529
115010
  }
114530
- const commandsRoot = join119(targetRoot, "commands");
115011
+ const commandsRoot = join120(targetRoot, "commands");
114531
115012
  await checkParents(commandsRoot);
114532
- 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 })) {
114533
115014
  if (!entry.isFile() || !entry.name.endsWith(".md"))
114534
115015
  continue;
114535
- let content3 = await readFile99(join119(root2, agent, "commands", entry.name), "utf8");
115016
+ let content3 = await readFile100(join120(root2, agent, "commands", entry.name), "utf8");
114536
115017
  if (agent === "claude" && entry.name !== "context-plan.md") {
114537
115018
  const frontmatter2 = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content3);
114538
115019
  if (!frontmatter2)
114539
- conflict(join119(root2, agent, "commands", entry.name));
115020
+ conflict(join120(root2, agent, "commands", entry.name));
114540
115021
  content3 = `${frontmatter2[1]}disable-model-invocation: true
114541
115022
  ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmatter2[3]}${content3.slice(frontmatter2[0].length)}`;
114542
115023
  }
114543
- const target = join119(commandsRoot, entry.name);
114544
- const marker = join119(commandsRoot, `.context-${entry.name}.json`);
115024
+ const target = join120(commandsRoot, entry.name);
115025
+ const marker = join120(commandsRoot, `.context-${entry.name}.json`);
114545
115026
  for (const file of [target, marker]) {
114546
115027
  const existing = await stat11(file);
114547
115028
  if (existing && (!existing.isFile() || existing.isSymbolicLink()))
@@ -114550,11 +115031,11 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
114550
115031
  if (await stat11(target)) {
114551
115032
  let previous3;
114552
115033
  try {
114553
- previous3 = JSON.parse(await readFile99(marker, "utf8"));
115034
+ previous3 = JSON.parse(await readFile100(marker, "utf8"));
114554
115035
  } catch {
114555
115036
  conflict(target);
114556
115037
  }
114557
- const current2 = createHash37("sha256").update(await readFile99(target)).digest("hex");
115038
+ const current2 = createHash37("sha256").update(await readFile100(target)).digest("hex");
114558
115039
  if (previous3?.owner !== "context-plugin-local" || previous3.digest !== current2)
114559
115040
  conflict(target);
114560
115041
  } else if (await stat11(marker))
@@ -114562,14 +115043,14 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
114562
115043
  commandPlans.push({ name: entry.name, target, marker, content: content3, hash: createHash37("sha256").update(content3).digest("hex") });
114563
115044
  }
114564
115045
  if (!commandPlans.length)
114565
- conflict(join119(root2, agent, "commands"));
115046
+ conflict(join120(root2, agent, "commands"));
114566
115047
  }
114567
115048
  const plans = [];
114568
115049
  for (const entry of (await readdir31(sourceRoot2, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
114569
- if (!entry.isDirectory() || !await stat11(join119(sourceRoot2, entry.name, "SKILL.md")))
115050
+ if (!entry.isDirectory() || !await stat11(join120(sourceRoot2, entry.name, "SKILL.md")))
114570
115051
  continue;
114571
- const source2 = join119(sourceRoot2, entry.name);
114572
- const target = join119(skillsRoot, entry.name);
115052
+ const source2 = join120(sourceRoot2, entry.name);
115053
+ const target = join120(skillsRoot, entry.name);
114573
115054
  if (commandEntries.has(entry.name) && entry.name !== "context-plan") {
114574
115055
  if (await stat11(target))
114575
115056
  conflict(`${target} (use a fresh host directory to avoid duplicate command/skill entries)`);
@@ -114579,12 +115060,12 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
114579
115060
  if (existing) {
114580
115061
  if (!existing.isDirectory() || existing.isSymbolicLink())
114581
115062
  conflict(target);
114582
- const markerPath = join119(target, MARKER);
115063
+ const markerPath = join120(target, MARKER);
114583
115064
  if (!(await stat11(markerPath))?.isFile())
114584
115065
  conflict(target);
114585
115066
  let marker;
114586
115067
  try {
114587
- marker = JSON.parse(await readFile99(markerPath, "utf8"));
115068
+ marker = JSON.parse(await readFile100(markerPath, "utf8"));
114588
115069
  } catch {
114589
115070
  conflict(target);
114590
115071
  }
@@ -114598,15 +115079,15 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
114598
115079
  if (!dryRun) {
114599
115080
  await mkdir39(skillsRoot, { recursive: true });
114600
115081
  for (const plan of plans) {
114601
- const staging = await mkdtemp6(join119(skillsRoot, ".context-install-"));
114602
- const candidate = join119(staging, "candidate");
114603
- 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");
114604
115085
  let moved = false;
114605
115086
  try {
114606
115087
  await cp2(plan.source, candidate, { recursive: true });
114607
115088
  if (agent === "claude" || agent === "cursor" && plan.name === "context-plan") {
114608
- const skillFile = join119(candidate, "SKILL.md");
114609
- const content3 = await readFile99(skillFile, "utf8");
115089
+ const skillFile = join120(candidate, "SKILL.md");
115090
+ const content3 = await readFile100(skillFile, "utf8");
114610
115091
  const parts = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content3);
114611
115092
  if (parts && (plan.name === "context-plan" || /^\s*context-public-entry:\s*["']?false["']?\s*$/mu.test(parts[2]))) {
114612
115093
  const frontmatter2 = parts[2].replace(/^user-invocable:.*\r?\n?/mu, "");
@@ -114614,7 +115095,7 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
114614
115095
  ${frontmatter2}${parts[3]}${content3.slice(parts[0].length)}`);
114615
115096
  }
114616
115097
  }
114617
- 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) }) + `
114618
115099
  `);
114619
115100
  if (plan.exists) {
114620
115101
  await rename10(plan.target, previous3);
@@ -114638,12 +115119,12 @@ ${frontmatter2}${parts[3]}${content3.slice(parts[0].length)}`);
114638
115119
  }
114639
115120
  for (const command3 of commandPlans) {
114640
115121
  await mkdir39(dirname47(command3.target), { recursive: true });
114641
- const staging = await mkdtemp6(join119(dirname47(command3.target), ".context-install-"));
114642
- await writeFile30(join119(staging, "command"), command3.content);
114643
- 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 }) + `
114644
115125
  `);
114645
- await rename10(join119(staging, "command"), command3.target);
114646
- await rename10(join119(staging, "marker"), command3.marker);
115126
+ await rename10(join120(staging, "command"), command3.target);
115127
+ await rename10(join120(staging, "marker"), command3.marker);
114647
115128
  await rm27(staging, { recursive: true, force: true });
114648
115129
  }
114649
115130
  }
@@ -114667,10 +115148,10 @@ init_cliFeedback();
114667
115148
  init_errors3();
114668
115149
  init_exitCode();
114669
115150
  import { lstat as lstat18 } from "node:fs/promises";
114670
- import { join as join120, resolve as resolve45 } from "node:path";
115151
+ import { join as join121, resolve as resolve45 } from "node:path";
114671
115152
  var HOST_DIRS = { claude: ".claude", cursor: ".cursor", codex: ".agents" };
114672
115153
  async function detectLocalAgent(agent, repo) {
114673
- const path3 = join120(repo, HOST_DIRS[agent]);
115154
+ const path3 = join121(repo, HOST_DIRS[agent]);
114674
115155
  try {
114675
115156
  const entry = await lstat18(path3);
114676
115157
  if (!entry.isDirectory() || entry.isSymbolicLink()) {
@@ -114695,7 +115176,7 @@ async function installAutoLocalSkills(root2, path3, dryRun, detect = detectLocal
114695
115176
  if (selectedAgent ? selectedAgent === "all" || agent === selectedAgent : await detect(agent, repo))
114696
115177
  agents.push(agent);
114697
115178
  }
114698
- 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") }];
114699
115180
  const previews = [];
114700
115181
  for (const target of targets)
114701
115182
  previews.push(await installLocalSkills(root2, target.path, true, target.agent));
@@ -114728,7 +115209,7 @@ init_cliFeedback();
114728
115209
  init_errors3();
114729
115210
  init_exitCode();
114730
115211
  import { existsSync as existsSync35 } from "node:fs";
114731
- 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";
114732
115213
  import { fileURLToPath as fileURLToPath10 } from "node:url";
114733
115214
 
114734
115215
  // src/project/pluginInstallTargets.ts
@@ -114737,9 +115218,9 @@ init_errors3();
114737
115218
  init_exitCode();
114738
115219
  import { execFile as execFile13 } from "node:child_process";
114739
115220
  import { existsSync as existsSync34 } from "node:fs";
114740
- 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";
114741
115222
  import { homedir as homedir2 } from "node:os";
114742
- import { dirname as dirname48, join as join121 } from "node:path";
115223
+ import { dirname as dirname48, join as join122 } from "node:path";
114743
115224
  import { promisify as promisify13 } from "node:util";
114744
115225
  var execFileAsync7 = promisify13(execFile13);
114745
115226
  var MARKETPLACE_NAME = "c4a";
@@ -114793,23 +115274,23 @@ async function claudePluginInstalled(pluginId) {
114793
115274
  }
114794
115275
  }
114795
115276
  function codexHome() {
114796
- return process.env.CODEX_HOME?.trim() || join121(homedir2(), ".codex");
115277
+ return process.env.CODEX_HOME?.trim() || join122(homedir2(), ".codex");
114797
115278
  }
114798
115279
  function claudePluginCacheRoot() {
114799
115280
  const explicitRoot = process.env[CLAUDE_PLUGIN_CACHE_ROOT_ENV2]?.trim();
114800
115281
  if (explicitRoot)
114801
115282
  return explicitRoot;
114802
115283
  const home = process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV2]?.trim() || homedir2();
114803
- return join121(home, ".claude", "plugins", "cache");
115284
+ return join122(home, ".claude", "plugins", "cache");
114804
115285
  }
114805
115286
  function sharedSkillsRoot() {
114806
- 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");
114807
115288
  }
114808
115289
  function claudeSkillsRoot() {
114809
- 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");
114810
115291
  }
114811
115292
  function cursorPluginRoot() {
114812
- 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);
114813
115294
  }
114814
115295
  function blockHeader(line) {
114815
115296
  const match = line.match(/^\s*\[([^\]]+)\]\s*$/u);
@@ -114860,8 +115341,8 @@ function pruneLegacyCodexConfigContent(content3) {
114860
115341
  `), removed };
114861
115342
  }
114862
115343
  async function pruneLegacyCodexConfig(dryRun, steps) {
114863
- const configPath = join121(codexHome(), "config.toml");
114864
- const current2 = await readFile100(configPath, "utf8").catch(() => "");
115344
+ const configPath = join122(codexHome(), "config.toml");
115345
+ const current2 = await readFile101(configPath, "utf8").catch(() => "");
114865
115346
  if (!current2)
114866
115347
  return;
114867
115348
  const next2 = pruneLegacyCodexConfigContent(current2);
@@ -114877,7 +115358,7 @@ async function pruneLegacyCodexConfig(dryRun, steps) {
114877
115358
  }
114878
115359
  }
114879
115360
  async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, steps) {
114880
- const cacheRoot = join121(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
115361
+ const cacheRoot = join122(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
114881
115362
  if (!existsSync34(cacheRoot))
114882
115363
  return;
114883
115364
  const versions = (await readdir32(cacheRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== keepVersion).map((entry) => entry.name).sort();
@@ -114889,7 +115370,7 @@ async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, ste
114889
115370
  status: dryRun ? "planned" : "ran"
114890
115371
  });
114891
115372
  if (!dryRun)
114892
- 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 })));
114893
115374
  }
114894
115375
  async function pruneCodexPluginCache(currentVersion, dryRun, steps) {
114895
115376
  await pruneCodexPluginCacheForName(PLUGIN_NAME, currentVersion, dryRun, steps);
@@ -114913,15 +115394,15 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
114913
115394
  for (const marketplace of marketplaces) {
114914
115395
  if (!marketplace.isDirectory())
114915
115396
  continue;
114916
- const pluginDir = join121(cacheRoot, marketplace.name, PLUGIN_NAME);
115397
+ const pluginDir = join122(cacheRoot, marketplace.name, PLUGIN_NAME);
114917
115398
  if (!existsSync34(pluginDir))
114918
115399
  continue;
114919
115400
  const versions = await readdir32(pluginDir, { withFileTypes: true }).catch(() => []);
114920
115401
  for (const version3 of versions) {
114921
115402
  if (!version3.isDirectory())
114922
115403
  continue;
114923
- const versionDir = join121(pluginDir, version3.name);
114924
- if (!existsSync34(join121(versionDir, ORPHAN_MARKER2)))
115404
+ const versionDir = join122(pluginDir, version3.name);
115405
+ if (!existsSync34(join122(versionDir, ORPHAN_MARKER2)))
114925
115406
  continue;
114926
115407
  removed.push(`${marketplace.name}/${PLUGIN_NAME}/${version3.name}`);
114927
115408
  if (!dryRun) {
@@ -114931,7 +115412,7 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
114931
115412
  if (!dryRun && await isEmptyDir2(pluginDir)) {
114932
115413
  await rm28(pluginDir, { recursive: true, force: true });
114933
115414
  }
114934
- const marketplaceDir = join121(cacheRoot, marketplace.name);
115415
+ const marketplaceDir = join122(cacheRoot, marketplace.name);
114935
115416
  if (!dryRun && await isEmptyDir2(marketplaceDir)) {
114936
115417
  await rm28(marketplaceDir, { recursive: true, force: true });
114937
115418
  }
@@ -114952,7 +115433,7 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
114952
115433
  return;
114953
115434
  const removed = [];
114954
115435
  for (const pluginName of LEGACY_PLUGIN_NAMES) {
114955
- const pluginDir = join121(cacheRoot, MARKETPLACE_NAME, pluginName);
115436
+ const pluginDir = join122(cacheRoot, MARKETPLACE_NAME, pluginName);
114956
115437
  if (!existsSync34(pluginDir))
114957
115438
  continue;
114958
115439
  removed.push(`${MARKETPLACE_NAME}/${pluginName}`);
@@ -114969,11 +115450,11 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
114969
115450
  }
114970
115451
  }
114971
115452
  async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
114972
- 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);
114973
115454
  const currentVersion = typeof manifest?.version === "string" ? manifest.version : null;
114974
115455
  if (!currentVersion)
114975
115456
  return;
114976
- const pluginDir = join121(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
115457
+ const pluginDir = join122(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
114977
115458
  const staleVersions = (await readdir32(pluginDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== currentVersion).map((entry) => entry.name);
114978
115459
  if (staleVersions.length === 0)
114979
115460
  return;
@@ -114983,7 +115464,7 @@ async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
114983
115464
  status: dryRun ? "planned" : "ran"
114984
115465
  });
114985
115466
  if (!dryRun) {
114986
- 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 })));
114987
115468
  }
114988
115469
  }
114989
115470
  function enableCodexPluginConfig(content3) {
@@ -115047,33 +115528,33 @@ source = ${JSON.stringify(root2)}
115047
115528
  `;
115048
115529
  }
115049
115530
  async function ensureCodexPluginEnabled() {
115050
- const configPath = join121(codexHome(), "config.toml");
115531
+ const configPath = join122(codexHome(), "config.toml");
115051
115532
  await mkdir40(dirname48(configPath), { recursive: true });
115052
- const current2 = await readFile100(configPath, "utf8").catch(() => "");
115533
+ const current2 = await readFile101(configPath, "utf8").catch(() => "");
115053
115534
  const next2 = enableCodexPluginConfig(current2);
115054
115535
  if (next2 !== current2) {
115055
115536
  await writeFile31(configPath, next2, "utf8");
115056
115537
  }
115057
115538
  }
115058
115539
  async function ensureCodexLocalMarketplace(root2) {
115059
- const configPath = join121(codexHome(), "config.toml");
115540
+ const configPath = join122(codexHome(), "config.toml");
115060
115541
  await mkdir40(dirname48(configPath), { recursive: true });
115061
- const current2 = await readFile100(configPath, "utf8").catch(() => "");
115542
+ const current2 = await readFile101(configPath, "utf8").catch(() => "");
115062
115543
  const next2 = upsertCodexLocalMarketplaceConfig(current2, root2);
115063
115544
  if (next2 !== current2) {
115064
115545
  await writeFile31(configPath, next2, "utf8");
115065
115546
  }
115066
115547
  }
115067
115548
  async function codexPluginVersion(root2) {
115068
- const manifestPath = join121(root2, "codex", ".codex-plugin", "plugin.json");
115069
- 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"));
115070
115551
  if (typeof manifest.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(manifest.version)) {
115071
115552
  throw new Error(`Codex plugin manifest has an invalid version: ${manifestPath}`);
115072
115553
  }
115073
115554
  return manifest.version;
115074
115555
  }
115075
115556
  function codexPluginCacheDir(version3) {
115076
- return join121(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
115557
+ return join122(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
115077
115558
  }
115078
115559
  async function replaceDirectoryFromSource(source2, target) {
115079
115560
  await mkdir40(dirname48(target), { recursive: true });
@@ -115096,7 +115577,7 @@ async function replaceDirectoryFromSource(source2, target) {
115096
115577
  }
115097
115578
  }
115098
115579
  async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
115099
- const source2 = join121(root2, "codex");
115580
+ const source2 = join122(root2, "codex");
115100
115581
  const target = codexPluginCacheDir(version3);
115101
115582
  steps.push({
115102
115583
  agent: "codex",
@@ -115108,16 +115589,16 @@ async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
115108
115589
  await replaceDirectoryFromSource(source2, target);
115109
115590
  }
115110
115591
  async function bundledProviderSkillNames(root2) {
115111
- const skillsRoot = join121(root2, "skills");
115592
+ const skillsRoot = join122(root2, "skills");
115112
115593
  const entries2 = await readdir32(skillsRoot, { withFileTypes: true });
115113
115594
  const names = [];
115114
115595
  for (const entry of entries2) {
115115
115596
  if (!entry.isDirectory() || entry.name === "context")
115116
115597
  continue;
115117
- const skillPath = join121(skillsRoot, entry.name, "SKILL.md");
115598
+ const skillPath = join122(skillsRoot, entry.name, "SKILL.md");
115118
115599
  if (!existsSync34(skillPath))
115119
115600
  continue;
115120
- const skill = await readFile100(skillPath, "utf8");
115601
+ const skill = await readFile101(skillPath, "utf8");
115121
115602
  if (!/^\s*context-role:\s*["']?indexer-provider["']?\s*$/mu.test(skill))
115122
115603
  continue;
115123
115604
  names.push(entry.name);
@@ -115129,10 +115610,10 @@ async function bundledProviderSkillNames(root2) {
115129
115610
  return names;
115130
115611
  }
115131
115612
  async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps) {
115132
- const sourceRoot2 = join121(root2, "skills");
115613
+ const sourceRoot2 = join122(root2, "skills");
115133
115614
  for (const name3 of await bundledProviderSkillNames(root2)) {
115134
- const source2 = join121(sourceRoot2, name3);
115135
- const target = join121(targetRoot, name3);
115615
+ const source2 = join122(sourceRoot2, name3);
115616
+ const target = join122(targetRoot, name3);
115136
115617
  steps.push({
115137
115618
  agent,
115138
115619
  command: `materialize lifecycle Provider skill: ${shellQuote8(source2)} -> ${shellQuote8(target)}`,
@@ -115143,7 +115624,7 @@ async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps
115143
115624
  }
115144
115625
  }
115145
115626
  async function installCursor(root2, dryRun, steps) {
115146
- const source2 = join121(root2, "cursor");
115627
+ const source2 = join122(root2, "cursor");
115147
115628
  const target = cursorPluginRoot();
115148
115629
  steps.push({
115149
115630
  agent: "cursor",
@@ -115198,12 +115679,12 @@ async function installCodex(root2, dryRun, steps) {
115198
115679
  steps.push({ agent: "codex", command: commandLine("codex", addArgs), status: dryRun ? "planned" : "ran" });
115199
115680
  steps.push({
115200
115681
  agent: "codex",
115201
- 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)}`,
115202
115683
  status: dryRun ? "planned" : "ran"
115203
115684
  });
115204
115685
  steps.push({
115205
115686
  agent: "codex",
115206
- command: `ensure ${shellQuote8(join121(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
115687
+ command: `ensure ${shellQuote8(join122(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
115207
115688
  status: dryRun ? "planned" : "ran"
115208
115689
  });
115209
115690
  if (dryRun) {
@@ -115258,13 +115739,13 @@ function pluginRootCandidates() {
115258
115739
  return [resolve46(envRoot)];
115259
115740
  const candidates = [];
115260
115741
  for (const dir of packageCandidateDirs()) {
115261
- candidates.push(join122(dir, "plugins"));
115262
- candidates.push(join122(dir, "dist", "plugins"));
115742
+ candidates.push(join123(dir, "plugins"));
115743
+ candidates.push(join123(dir, "dist", "plugins"));
115263
115744
  }
115264
115745
  return [...new Set(candidates)];
115265
115746
  }
115266
115747
  function isInstallablePluginRoot(root2) {
115267
- 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"));
115268
115749
  }
115269
115750
  function resolveBundledPluginsRoot() {
115270
115751
  const candidates = pluginRootCandidates();
@@ -115511,10 +115992,10 @@ function readQuickstartPath() {
115511
115992
  try {
115512
115993
  let dir = dirname50(fileURLToPath11(import.meta.url));
115513
115994
  for (let i2 = 0;i2 < 8; i2++) {
115514
- const candidate = join123(dir, "docs", "quickstart.md");
115995
+ const candidate = join124(dir, "docs", "quickstart.md");
115515
115996
  if (existsSync36(candidate))
115516
115997
  return candidate;
115517
- const pkg = join123(dir, "package.json");
115998
+ const pkg = join124(dir, "package.json");
115518
115999
  if (existsSync36(pkg))
115519
116000
  return candidate;
115520
116001
  const parent = dirname50(dir);
@@ -115523,7 +116004,7 @@ function readQuickstartPath() {
115523
116004
  dir = parent;
115524
116005
  }
115525
116006
  } catch {}
115526
- return join123(dirname50(fileURLToPath11(import.meta.url)), "docs", "quickstart.md");
116007
+ return join124(dirname50(fileURLToPath11(import.meta.url)), "docs", "quickstart.md");
115527
116008
  }
115528
116009
  var GREEN = "\x1B[32m";
115529
116010
  var RESET = "\x1B[0m";
@@ -115653,7 +116134,7 @@ function createCliProgram() {
115653
116134
  format: options.format === "json" ? "json" : "text"
115654
116135
  });
115655
116136
  });
115656
- 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) => {
115657
116138
  if (options.format !== "text" && options.format !== "json") {
115658
116139
  throw new ContextError(ExitCode.UserError, "--format must be text or json", {
115659
116140
  category: ErrorCategory.UserInputInvalid
@@ -115674,7 +116155,7 @@ function createCliProgram() {
115674
116155
  })
115675
116156
  });
115676
116157
  });
115677
- 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) => {
115678
116159
  if (options.format !== "text" && options.format !== "json") {
115679
116160
  throw new ContextError(ExitCode.UserError, "--format must be text or json", {
115680
116161
  category: ErrorCategory.UserInputInvalid
@@ -115685,6 +116166,7 @@ function createCliProgram() {
115685
116166
  ...collection !== undefined ? { collection } : {},
115686
116167
  ...options.all === true ? { all: true } : {},
115687
116168
  managed: options.managed === true,
116169
+ confirmed: options.confirmed === true,
115688
116170
  force: options.force === true,
115689
116171
  verbose: options.verbose === true,
115690
116172
  format: options.format === "json" ? "json" : "text",