@c4a/context-cli 0.7.14-beta.1 → 0.7.15

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 (26) hide show
  1. package/cli.js +1082 -1242
  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/parserEntryWorker.js +8 -0
  6. package/plugins/VERSION +1 -1
  7. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  8. package/plugins/claude/commands/context.md +11 -0
  9. package/plugins/codex/.codex-plugin/plugin.json +2 -2
  10. package/plugins/codex/skills/context/SKILL.md +11 -0
  11. package/plugins/cursor/.cursor-plugin/plugin.json +1 -1
  12. package/plugins/cursor/commands/c4a-context.md +11 -0
  13. package/plugins/skills/context/SKILL.md +11 -0
  14. package/providers/context/manifest.json +23 -23
  15. package/providers/context/provider.yaml +1 -1
  16. package/providers/context/resources/dialogue/knowledge-review.md +14 -6
  17. package/providers/context/resources/manuals/guides/knowledge-updates.md +50 -2
  18. package/providers/context/resources/manuals/guides/lark-resources.md +38 -0
  19. package/providers/context/resources/procedures/document-capture.md +7 -0
  20. package/providers/context/resources/procedures/knowledge-review.md +26 -5
  21. package/providers/context/resources/procedures/knowledge-updates.md +50 -2
  22. package/providers/context/resources/procedures/production-requirements.md +23 -0
  23. package/providers/context/resources/procedures/runtime-event-delivery.md +7 -1
  24. package/providers/context/resources/procedures/work-start-report.md +83 -0
  25. package/providers/context/resources/templates/work-start-report.md +7 -1
  26. package/providers/context/skills/work-production-stage/SKILL.md +3 -1
package/cli.js CHANGED
@@ -41982,6 +41982,12 @@ var getSourceType = (sourceDefinition) => {
41982
41982
  }), captureLark = (definition) => {
41983
41983
  const sourceDefinition = bindSourceType(definition.source, "lark", "captureLark source");
41984
41984
  const sourceId = getSourceName(sourceDefinition);
41985
+ for (const key of ["images", "gifs"]) {
41986
+ const value = definition.resources?.[key];
41987
+ if (value !== undefined && value !== "bundle" && value !== "reference-only") {
41988
+ throw new TypeError(`captureLark resources.${key} must be bundle or reference-only`);
41989
+ }
41990
+ }
41985
41991
  const maxBytesPerResource = definition.resources?.maxBytesPerResource ?? 20 * 1024 * 1024;
41986
41992
  const maxTotalBytes = definition.resources?.maxTotalBytes ?? 200 * 1024 * 1024;
41987
41993
  if (!Number.isSafeInteger(maxBytesPerResource) || maxBytesPerResource < 1) {
@@ -42000,6 +42006,8 @@ var getSourceType = (sourceDefinition) => {
42000
42006
  writes: [sourceSnapshotResource(sourceDefinition, "lark")],
42001
42007
  source: sourceDefinition,
42002
42008
  resources: {
42009
+ ...definition.resources?.images === undefined ? {} : { images: definition.resources.images },
42010
+ ...definition.resources?.gifs === undefined ? {} : { gifs: definition.resources.gifs },
42003
42011
  videos: definition.resources?.videos ?? "reference-only",
42004
42012
  maxBytesPerResource,
42005
42013
  maxTotalBytes
@@ -64786,6 +64794,8 @@ Restore the authorized source and retry preparation.
64786
64794
  ...scopes.map((scope2) => `- ${scope2.scope}: ${join25(directory, productionSourceFile(scope2.scope))}`),
64787
64795
  "",
64788
64796
  "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.",
64797
+ "Configured sources are the knowledge workspace coverage boundary, not a new investigation assignment on every request. First distinguish the user's current task, its actual source dependencies, and unrelated configured sources. Reuse approved content; a source-level pending entry alone does not prove missing knowledge or require new articles.",
64798
+ "Report source baseline/read failures separately from content gaps. For an unrelated configured source, explain that its availability check is unresolved outside this task; do not promise a new code investigation. If the Route still requires resolution, report that precise workflow limitation without deleting source configuration, clearing runtime state, or claiming the source was investigated.",
64789
64799
  "Scale planning to the current request. For one or two documents or a clearly bounded module, decide which articles to add or revise and where they belong; do not redesign the whole knowledge base. Start with related existing topics, expand reading only when needed, and reuse applicable decisions. A large module may need several topics, but not investigation of unrelated modules.",
64790
64800
  "Separate the whole requested outcome from the current writing batch. Use one batch unless actual dependencies or useful parallel work justify more; do not invent page counts or dependencies. A first useful delivery does not settle remaining authorized work.",
64791
64801
  "Use question and brief to describe the reader task and useful depth: a checked file/symbol or document section with a concrete next step for navigation, or the behavior, conditions and steps needed for explanation. Reuse or revise existing articles without replacing valid detail with generic summaries; split distinct tasks, not sources or symbols.",
@@ -68458,7 +68468,7 @@ function runtimeEventPendingAgentHint(result) {
68458
68468
  requires_network_access: true,
68459
68469
  plan_command: "context logs plan --format json",
68460
68470
  command: "context logs flush --format json",
68461
- message: "Runtime logs are queued locally. Read the fixed delivery plan, request network access with its audit details, and run only its flush command before handing off the completed step."
68471
+ message: "Runtime logs are queued locally. If delivery is already authorized and the destination is unchanged, run context logs flush --format json directly. Use the delivery plan when the destination or required host network permission is not yet established."
68462
68472
  };
68463
68473
  }
68464
68474
  function queueContextRuntimeEvent(input) {
@@ -68950,6 +68960,10 @@ function renderAgents(projectName, language) {
68950
68960
  "- Context 完成只证明知识工作流状态,不证明 Git 提交范围安全。保留任务开始前已有的工作树变更,只按明确路径暂存;不要用 `git add -A` 把无关修改、删除或未跟踪目录带入提交。",
68951
68961
  "- 发布知识包前可按产品需要定制 `src/package-templates/kb/wikis/index.md`;不要编辑生成后的 `dist/` 页面。",
68952
68962
  "",
68963
+ "## 导航与分类",
68964
+ "",
68965
+ "新增或修订知识前,读取现有知识地图、总览和相关栏目正文,理解并复用分类意图。按当前 Route 的 knowledge-updates 指引安排文章、命名与顺序。新增来源或产品不自动新增顶层目录;确需调整顶层名称、用途或结构时,在开工报告或现有计划展示前后结构与复用不足的理由,取得具体方案确认后应用。已有明确批准不重复询问,常规落位不新增人审。将长期栏目用途简要维护在本文件或现有组织说明中。",
68966
+ "",
68953
68967
  "## 图表风格(可修改)",
68954
68968
  "",
68955
68969
  "- 默认简约:约 1px 细线;文字与线条使用随主题变化的默认色,不加彩色装饰。Mermaid 不硬编码调色板、背景或主题初始化;线宽由支持它的展示端设置,不因此阻塞写作。",
@@ -68992,6 +69006,10 @@ function renderAgents(projectName, language) {
68992
69006
  "- Context completion proves knowledge-workflow state, not Git commit safety. Preserve worktree changes that existed before the task, stage only explicit paths, and never use `git add -A` to mix unrelated modifications, deletions, or untracked directories into the deliverable.",
68993
69007
  "- Customize `src/package-templates/kb/wikis/index.md` for the product before publishing a knowledge package; do not edit generated `dist/` pages.",
68994
69008
  "",
69009
+ "## Navigation and Classification",
69010
+ "",
69011
+ "Before adding or revising knowledge, read the current map, overview and relevant category articles to understand and reuse their intent. Follow the current Route's knowledge-updates guidance for placement, names and order. A new source or product does not automatically warrant a top-level category. Present top-level name, purpose or structure changes with a before/after tree and reuse rationale in the work-start report or current plan, and obtain approval of the concrete proposal before applying it. Reuse explicit prior approval; ordinary placements add no review gate. Keep lasting category intent concise in this file or the existing organization guide.",
69012
+ "",
68995
69013
  "## Diagram style (user editable)",
68996
69014
  "",
68997
69015
  "- Keep diagrams minimal: approximately 1px lines, theme-default text and stroke colors, no colorful decoration. Avoid fixed Mermaid palettes, backgrounds and theme initialization. Configure line width in a capable viewer; unsupported styling never blocks authoring.",
@@ -70276,6 +70294,9 @@ function formatPackageBuildSummary(pkg) {
70276
70294
  lines.push(` warning: ${warning.path} references ${warning.target}, which ${explanation}.`);
70277
70295
  }
70278
70296
  const optimization = pkg.resources.delivery.optimization;
70297
+ for (const warning of optimization?.warnings ?? []) {
70298
+ lines.push(` image replaced with placeholder: ${warning.path}: ${warning.reason}`);
70299
+ }
70279
70300
  if (optimization?.state === "applied") {
70280
70301
  lines.push(` asset optimization: ${optimization.processor}/${optimization.mode}, saved ${optimization.savedBytes} byte(s), largest ${optimization.largestOutputBytes}/${optimization.maxImageBytes} byte(s), total ${optimization.outputBytes}/${optimization.maxTotalImageBytes} byte(s)`);
70281
70302
  } else if (pkg.resources.delivery.state === "git-raw") {
@@ -70758,21 +70779,23 @@ var siteMarkdownConfig, siteThemeScript, siteThemeCss = `
70758
70779
  .VPNavBarSearch .DocSearch-Button-Container { display: flex; flex: 1; min-width: 0; align-items: center; }
70759
70780
  .VPNavBarSearch .DocSearch-Button-Keys { margin-left: auto; }
70760
70781
  .VPSidebar { border-top: 1px solid var(--vp-c-divider); border-right: 1px solid var(--vp-c-divider); scrollbar-width: thin; }
70761
- .VPSidebar .group + .group { border: 0; padding-top: 8px; }
70782
+ .VPSidebar .group, .VPSidebar .group + .group { border: 0; padding-top: 0; }
70762
70783
  .VPSidebarItem .link { min-width: 0; overflow: hidden; }
70763
70784
  .VPSidebarItem .text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px !important; line-height: 20px !important; font-weight: 450 !important; }
70764
- .VPSidebarItem.level-0 { padding-bottom: 8px !important; }
70785
+ .VPSidebarItem.level-0 { padding-bottom: 0 !important; }
70765
70786
  .VPSidebarItem .indicator { display: none; }
70766
- .VPSidebarItem .text { padding: 3px 0 !important; }
70767
- .VPSidebarItem .item { min-height: 32px; padding: 3px 16px; border-radius: 0; }
70787
+ .VPSidebarItem .text { padding: 0 !important; }
70788
+ .VPSidebarItem .item { height: 38px; min-height: 38px; align-items: center; padding: 0 16px 0 26px; border-radius: 0; }
70789
+ .VPSidebarItem.is-link:not(.is-active) > .item > .link > p.text { color: var(--vp-c-text-2); }
70790
+ .VPSidebarItem.is-link:not(.is-active) > .item > .link:hover > p.text { color: var(--vp-c-brand-1); }
70768
70791
  .VPSidebarItem.is-active > .item { background: var(--vp-c-brand-soft); box-shadow: none; }
70769
70792
  .VPSidebarItem.is-active > .item .text { color: var(--vp-c-brand-1) !important; font-weight: 550 !important; }
70770
70793
  .VPSidebarItem .items { margin-left: 0; padding-left: 0 !important; border-left: 0 !important; }
70771
- .VPSidebarItem.level-1 > .item { padding-left: 30px; }
70772
- .VPSidebarItem.level-2 > .item { padding-left: 44px; }
70773
- .VPSidebarItem.level-3 > .item { padding-left: 58px; }
70774
- .VPSidebarItem.level-4 > .item { padding-left: 72px; }
70775
- .VPSidebarItem.level-5 > .item { padding-left: 86px; }
70794
+ .VPSidebarItem.level-1 > .item { padding-left: 40px; }
70795
+ .VPSidebarItem.level-2 > .item { padding-left: 54px; }
70796
+ .VPSidebarItem.level-3 > .item { padding-left: 68px; }
70797
+ .VPSidebarItem.level-4 > .item { padding-left: 82px; }
70798
+ .VPSidebarItem.level-5 > .item { padding-left: 96px; }
70776
70799
  .VPSidebar .group { width: 100% !important; }
70777
70800
  .VPSidebarItem .item:hover { background: var(--vp-c-default-soft); }
70778
70801
  .VPDoc .container, .VPDoc > .container > .content, .VPDoc .content-container { max-width: none !important; min-width: 0 !important; }
@@ -71503,15 +71526,23 @@ function sidebar(entries2) {
71503
71526
  return entries2.map((entry) => ({
71504
71527
  text: entry.title,
71505
71528
  ...entry.href ? { link: entry.href } : {},
71506
- ...entry.children.length ? { collapsed: true, items: sidebar(entry.children) } : {}
71529
+ items: sidebar(entry.children),
71530
+ ...entry.children.length ? { collapsed: true } : {}
71507
71531
  }));
71508
71532
  }
71509
71533
  function siteSections(entries2) {
71534
+ const directoriesFirst = (siblings) => {
71535
+ const ordered = [
71536
+ ...siblings.filter((entry) => entry.children.length > 0),
71537
+ ...siblings.filter((entry) => entry.children.length === 0)
71538
+ ];
71539
+ return ordered.map((entry) => ({ ...entry, children: directoriesFirst(entry.children) }));
71540
+ };
71510
71541
  const pageLinks = (entry) => [
71511
71542
  ...entry.href ? [entry.href.split("#")[0]] : [],
71512
71543
  ...entry.children.flatMap(pageLinks)
71513
71544
  ];
71514
- return entries2.map((entry) => ({
71545
+ return entries2.map((entry) => ({ ...entry, children: directoriesFirst(entry.children) })).map((entry) => ({
71515
71546
  key: entry.key,
71516
71547
  title: entry.title,
71517
71548
  href: `/sections/${createHash16("sha256").update(entry.key).digest("hex").slice(0, 20)}.html`,
@@ -72168,7 +72199,11 @@ async function loadSharpProcessor() {
72168
72199
  }
72169
72200
  return {
72170
72201
  async optimize(bytes, definition2) {
72171
- let pipeline2 = sharp(bytes, { failOn: "error", animated: true }).rotate();
72202
+ const image = sharp(bytes, { failOn: "error", animated: false });
72203
+ const metadata = await image.metadata();
72204
+ if ((metadata.pages ?? 1) > 1)
72205
+ return bytes;
72206
+ let pipeline2 = image.rotate();
72172
72207
  if (definition2.maxDimension !== undefined) {
72173
72208
  pipeline2 = pipeline2.resize({
72174
72209
  width: definition2.maxDimension,
@@ -72192,6 +72227,8 @@ async function adaptiveVariants(input) {
72192
72227
  let smallest = input.asset.bytes.byteLength;
72193
72228
  for (const definition2 of input.definitions) {
72194
72229
  const output = await input.processor.optimize(input.asset.bytes, definition2);
72230
+ if (output === input.asset.bytes)
72231
+ break;
72195
72232
  if (!isWebp(output)) {
72196
72233
  throw new ContextError(ExitCode.WorkspaceStateError, "package asset optimizer returned invalid WebP bytes", {
72197
72234
  category: ErrorCategory.WorkspaceStateInvalid,
@@ -72207,17 +72244,6 @@ async function adaptiveVariants(input) {
72207
72244
  }
72208
72245
  return variants;
72209
72246
  }
72210
- function budgetError(input) {
72211
- return new ContextError(ExitCode.WorkspaceStateError, "bundled images cannot meet the package size budget", {
72212
- category: ErrorCategory.WorkspaceStateInvalid,
72213
- reason_code: "package.assets.image-budget-exceeded",
72214
- output_bytes: input.outputBytes,
72215
- max_image_bytes: input.maxImageBytes,
72216
- max_total_image_bytes: input.maxTotalImageBytes,
72217
- oversized_paths: input.oversized.map((asset) => asset.packageRelPath),
72218
- next: "Reduce or replace the reported source images, then rerun context build."
72219
- });
72220
- }
72221
72247
  async function optimizePackageAssetFiles(input) {
72222
72248
  const maxImageBytes = input.maxImageBytes ?? PACKAGE_ASSET_MAX_IMAGE_BYTES;
72223
72249
  const maxTotalImageBytes = input.maxTotalImageBytes ?? PACKAGE_ASSET_MAX_TOTAL_IMAGE_BYTES;
@@ -72243,23 +72269,38 @@ async function optimizePackageAssetFiles(input) {
72243
72269
  const processor = input.processor ?? await loadSharpProcessor();
72244
72270
  const definitions = input.definition === undefined ? DEFAULT_OPTIMIZATION_PROFILES : [input.definition];
72245
72271
  const variantsByPath = new Map;
72272
+ const omittedImages = new Set;
72273
+ const warnings = [];
72246
72274
  for (const asset of candidates) {
72247
- variantsByPath.set(asset.packageRelPath, await adaptiveVariants({ asset, processor, definitions }));
72275
+ try {
72276
+ variantsByPath.set(asset.packageRelPath, await adaptiveVariants({ asset, processor, definitions }));
72277
+ } catch (error) {
72278
+ if (error instanceof ContextError)
72279
+ throw error;
72280
+ warnings.push({ path: asset.packageRelPath, reason: error instanceof Error ? error.message : String(error) });
72281
+ omittedImages.add(asset.packageRelPath);
72282
+ variantsByPath.set(asset.packageRelPath, [{ bytes: asset.bytes }]);
72283
+ }
72248
72284
  }
72249
72285
  const selectedIndex = new Map;
72250
72286
  for (const asset of candidates) {
72251
72287
  const variants = variantsByPath.get(asset.packageRelPath);
72288
+ if (omittedImages.has(asset.packageRelPath))
72289
+ continue;
72252
72290
  let index2 = input.definition !== undefined && variants.length > 1 ? 1 : 0;
72253
72291
  if (asset.bytes.byteLength > maxImageBytes) {
72254
72292
  const fitting = variants.findIndex((variant) => variant.bytes.byteLength <= maxImageBytes);
72255
72293
  if (fitting < 0) {
72256
- throw budgetError({ outputBytes: originalBytes, maxImageBytes, maxTotalImageBytes, oversized: [asset] });
72294
+ omittedImages.add(asset.packageRelPath);
72295
+ warnings.push({ path: asset.packageRelPath, reason: "Image exceeds the per-image delivery budget after optimization" });
72257
72296
  }
72258
- index2 = fitting;
72297
+ index2 = Math.max(0, fitting);
72259
72298
  }
72260
72299
  selectedIndex.set(asset.packageRelPath, index2);
72261
72300
  }
72262
72301
  const selectedBytes = (asset) => {
72302
+ if (omittedImages.has(asset.packageRelPath))
72303
+ return new Uint8Array;
72263
72304
  const variants = variantsByPath.get(asset.packageRelPath);
72264
72305
  return variants[selectedIndex.get(asset.packageRelPath) ?? 0].bytes;
72265
72306
  };
@@ -72267,6 +72308,8 @@ async function optimizePackageAssetFiles(input) {
72267
72308
  while (outputBytes > maxTotalImageBytes) {
72268
72309
  let best;
72269
72310
  for (const asset of candidates) {
72311
+ if (omittedImages.has(asset.packageRelPath))
72312
+ continue;
72270
72313
  const variants = variantsByPath.get(asset.packageRelPath);
72271
72314
  const currentIndex = selectedIndex.get(asset.packageRelPath) ?? 0;
72272
72315
  const nextIndex = currentIndex + 1;
@@ -72278,7 +72321,13 @@ async function optimizePackageAssetFiles(input) {
72278
72321
  best = { asset, nextIndex, saving };
72279
72322
  }
72280
72323
  if (best === undefined) {
72281
- throw budgetError({ outputBytes, maxImageBytes, maxTotalImageBytes, oversized: [] });
72324
+ const largest = candidates.filter((asset) => !omittedImages.has(asset.packageRelPath)).sort((a, b) => selectedBytes(b).byteLength - selectedBytes(a).byteLength || a.packageRelPath.localeCompare(b.packageRelPath))[0];
72325
+ if (largest === undefined)
72326
+ break;
72327
+ outputBytes -= selectedBytes(largest).byteLength;
72328
+ omittedImages.add(largest.packageRelPath);
72329
+ warnings.push({ path: largest.packageRelPath, reason: "Image exceeds the total delivery budget after optimization" });
72330
+ continue;
72282
72331
  }
72283
72332
  selectedIndex.set(best.asset.packageRelPath, best.nextIndex);
72284
72333
  outputBytes -= best.saving;
@@ -72286,6 +72335,8 @@ async function optimizePackageAssetFiles(input) {
72286
72335
  const optimizedByInputPath = new Map;
72287
72336
  const optimizedTargetByOriginal = new Map;
72288
72337
  for (const asset of candidates) {
72338
+ if (omittedImages.has(asset.packageRelPath))
72339
+ continue;
72289
72340
  const output = selectedBytes(asset);
72290
72341
  if (output.byteLength >= asset.bytes.byteLength)
72291
72342
  continue;
@@ -72293,14 +72344,14 @@ async function optimizePackageAssetFiles(input) {
72293
72344
  optimizedByInputPath.set(asset.packageRelPath, { ...asset, packageRelPath, bytes: output });
72294
72345
  optimizedTargetByOriginal.set(asset.packageRelPath, packageRelPath);
72295
72346
  }
72296
- const assets = input.assets.map((asset) => optimizedByInputPath.get(asset.packageRelPath) ?? asset);
72297
- outputBytes = candidates.reduce((sum, asset) => sum + (optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength), 0);
72298
- const largestOutputBytes = Math.max(0, ...candidates.map((asset) => optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength));
72347
+ const assets = input.assets.filter((asset) => !omittedImages.has(asset.packageRelPath)).map((asset) => optimizedByInputPath.get(asset.packageRelPath) ?? asset);
72348
+ outputBytes = candidates.filter((asset) => !omittedImages.has(asset.packageRelPath)).reduce((sum, asset) => sum + (optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength), 0);
72349
+ const largestOutputBytes = Math.max(0, ...candidates.filter((asset) => !omittedImages.has(asset.packageRelPath)).map((asset) => optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength));
72299
72350
  return {
72300
72351
  assets,
72301
72352
  optimizedTargetByOriginal,
72302
72353
  summary: {
72303
- state: optimizedByInputPath.size > 0 ? "applied" : "configured-no-benefit",
72354
+ state: omittedImages.size > 0 ? "partial" : optimizedByInputPath.size > 0 ? "applied" : "configured-no-benefit",
72304
72355
  candidateFiles: candidates.length,
72305
72356
  originalBytes,
72306
72357
  outputBytes,
@@ -72308,6 +72359,8 @@ async function optimizePackageAssetFiles(input) {
72308
72359
  maxImageBytes,
72309
72360
  maxTotalImageBytes,
72310
72361
  largestOutputBytes,
72362
+ ...warnings.length === 0 ? {} : { warnings },
72363
+ ...omittedImages.size === 0 ? {} : { omittedImages: [...omittedImages] },
72311
72364
  processor: "sharp",
72312
72365
  mode: input.definition?.mode ?? "webp"
72313
72366
  }
@@ -72510,6 +72563,7 @@ async function deliverPackageAssetFiles(input) {
72510
72563
  return {
72511
72564
  assets: optimization.assets,
72512
72565
  targetByOriginal: optimization.optimizedTargetByOriginal,
72566
+ ...optimization.summary.omittedImages === undefined ? {} : { omittedImages: optimization.summary.omittedImages },
72513
72567
  summary: {
72514
72568
  state: "bundled",
72515
72569
  sourceFiles: input.assets.length,
@@ -72707,7 +72761,15 @@ async function writeSelectedPackageKnowledge(input) {
72707
72761
  const results = await Promise.allSettled(projectedPages.slice(offset, offset + 8).map(async (projected) => {
72708
72762
  assertSafeRenderedPath2(projected.pageOutputPath, "knowledge path");
72709
72763
  const outputPath = join62(input.projectRoot, input.pkg.outDir, projected.pageOutputPath);
72710
- const rewritten = replaceMarkdownInlineLinkTargets(projected.content, (link) => {
72764
+ let mediaContent = projected.content;
72765
+ const omitted = new Set((delivered.omittedImages ?? []).map((path2) => packageMarkdownTarget(projected.pageOutputPath, path2)));
72766
+ for (const link of markdownReaderLinks(mediaContent).reverse()) {
72767
+ if (!omitted.has(link.target))
72768
+ continue;
72769
+ const label2 = link.label.replace(/[<>\[\]_*`]/gu, "");
72770
+ mediaContent = mediaContent.slice(0, link.start) + `[Image omitted: ${label2 || "image"}; see article sources]` + mediaContent.slice(link.end);
72771
+ }
72772
+ const rewritten = replaceMarkdownInlineLinkTargets(mediaContent, (link) => {
72711
72773
  for (const [inputPath, outputPath2] of delivered.targetByOriginal) {
72712
72774
  if (link.target === packageMarkdownTarget(projected.pageOutputPath, inputPath)) {
72713
72775
  return /^https:\/\//u.test(outputPath2) ? outputPath2 : packageMarkdownTarget(projected.pageOutputPath, outputPath2);
@@ -73453,7 +73515,8 @@ async function buildProjectPackagesInternal(projectRoot, options) {
73453
73515
  const revisionInterruptedProduction = !!production && !!await readApprovedRevision2(projectRoot);
73454
73516
  await finishApprovedRevision(projectRoot);
73455
73517
  const { readKnowledgeUpdate } = await Promise.resolve().then(() => (init_knowledgeUpdate(), exports_knowledgeUpdate));
73456
- if (!revisionInterruptedProduction && !production?.delivery && !maintenanceActive && !await readTaskRollback(projectRoot) && !await readApprovedRevision2(projectRoot) && !await readKnowledgeUpdate(projectRoot) && (await readProjectCloseStatus(projectRoot)).state === "ready") {
73518
+ const productionEnded = !production || dispatchProductionStage(production, productionCapabilitiesSchema.parse({})).state === "ended";
73519
+ if (productionEnded && !revisionInterruptedProduction && !production?.delivery && !maintenanceActive && !await readTaskRollback(projectRoot) && !await readApprovedRevision2(projectRoot) && !await readKnowledgeUpdate(projectRoot) && (await readProjectCloseStatus(projectRoot)).state === "ready") {
73457
73520
  const { clearCompletedLifecycle: clearCompletedLifecycle2 } = await Promise.resolve().then(() => (init_lifecycleCleanup(), exports_lifecycleCleanup));
73458
73521
  await clearCompletedLifecycle2(projectRoot);
73459
73522
  }
@@ -73559,6 +73622,7 @@ var init_packageBuilder = __esm(() => {
73559
73622
  init_packageSiteAddress();
73560
73623
  init_knowledgeMap2();
73561
73624
  init_approvedFileRead();
73625
+ init_productionStage();
73562
73626
  init_productionStageStore();
73563
73627
  init_productionDelivery();
73564
73628
  init_writeLock();
@@ -82582,7 +82646,7 @@ async function productionWorkflowRoute(input) {
82582
82646
  revision,
82583
82647
  reason_code: resolved.reasonCode,
82584
82648
  availability: resolved.availability,
82585
- summary: report ? `Present the report and wait. After approval, write {stage: ${stage.id}, decision: approved} to ${path2}.` : writing ? "Read the issued task directories. Coordinate them sequentially unless this caller supports independent Agents; only the coordinator submits shared state." : repair ? "Add revision tasks for rejected articles using the Review feedback; accepted production responsibilities remain unchanged." : investigate ? `Continue investigating ${stage.pending_scopes.filter((scope2) => !stage.gaps.some((gap2) => gap2.scope === scope2)).join(", ")}. Submit additional article tasks and the remaining pending_scopes; do not repeat accepted work.` : resolved.node === "resolve-production-gap" ? `Remaining investigation: ${stage.pending_scopes.join(", ") || "none"}. ${stage.gaps.map((gap2) => `${gap2.scope}: ${gap2.reason}`).join("; ")}` : "Prepare the current stage's eligible task directories.",
82649
+ summary: report ? `Present the report and wait. After approval, write {stage: ${stage.id}, decision: approved} to ${path2}.` : writing ? "Read the issued task directories. Coordinate them sequentially unless this caller supports independent Agents; only the coordinator submits shared state." : repair ? "Add revision tasks for rejected articles using the Review feedback; accepted production responsibilities remain unchanged." : investigate ? `Review pending configured scopes: ${stage.pending_scopes.filter((scope2) => !stage.gaps.some((gap2) => gap2.scope === scope2)).join(", ")}. Check their relevance to the current request and existing approved content before assigning investigation. Submit supported article tasks and remaining pending_scopes; do not infer missing articles from this list or repeat accepted work.` : resolved.node === "resolve-production-gap" ? `Source availability gaps: ${stage.gaps.map((gap2) => `${gap2.scope}: ${gap2.reason}`).join("; ")}. These failures do not establish missing knowledge or a new investigation assignment. Identify which sources the current task actually depends on; report unrelated configured-source failures separately. Preserve the configuration and follow the current resolution action.` : "Prepare the current stage's eligible task directories.",
82586
82650
  commands: report || prepare || writing || repair || investigate || gap ? [{
82587
82651
  command: command2,
82588
82652
  effect: "write",
@@ -86520,7 +86584,7 @@ class FidelityTracker {
86520
86584
  var LARK_EMPTY_SUB_PAGE_LIST_CODE = "lark.capture.sub-page-list-empty";
86521
86585
 
86522
86586
  // src/lib/larkDocxResources.ts
86523
- import { createHash as createHash24 } from "node:crypto";
86587
+ import { createHash as createHash25 } from "node:crypto";
86524
86588
  function elementName(node3) {
86525
86589
  return Object.keys(node3).find((key) => key !== ":@" && key !== "#text");
86526
86590
  }
@@ -86596,18 +86660,18 @@ function resourceIdentity(kind, attrs) {
86596
86660
  ];
86597
86661
  return candidates.find((value) => value !== undefined && value.length > 0 && !isTransientLarkMediaUrl(value));
86598
86662
  }
86599
- function registerLarkResource(ctx, blockType, kind, attrs, title) {
86663
+ function registerLarkResource(ctx, blockType, kind, attrs, title2) {
86600
86664
  const identity = resourceIdentity(kind, attrs);
86601
86665
  const href = attrs.href ?? attrs.url;
86602
86666
  const locator = identity !== undefined ? `lark:${kind}:${identity}` : href !== undefined && !isTransientLarkMediaUrl(href) ? href : undefined;
86603
- const resolvedLocator = locator ?? `lark:${kind}:unresolved:${createHash24("sha256").update(JSON.stringify(Object.fromEntries(Object.entries(attrs).sort(([left], [right]) => left.localeCompare(right))))).digest("hex").slice(0, 12)}`;
86667
+ const resolvedLocator = locator ?? `lark:${kind}:unresolved:${createHash25("sha256").update(JSON.stringify(Object.fromEntries(Object.entries(attrs).sort(([left], [right]) => left.localeCompare(right))))).digest("hex").slice(0, 12)}`;
86604
86668
  if (locator === undefined) {
86605
86669
  ctx.tracker.flag(blockType, "resource has no stable token, source id, or non-transient URL", "error");
86606
86670
  }
86607
86671
  const resource = {
86608
86672
  kind,
86609
86673
  locator: resolvedLocator,
86610
- ...title !== undefined && title.length > 0 ? { title } : {},
86674
+ ...title2 !== undefined && title2.length > 0 ? { title: title2 } : {},
86611
86675
  attributes: Object.fromEntries(Object.entries(attrs).filter(([, value]) => value.length > 0 && !isTransientLarkMediaUrl(value)))
86612
86676
  };
86613
86677
  ctx.resources.push(resource);
@@ -86625,32 +86689,32 @@ function renderLarkResource(name3, nodes, attrs, ctx) {
86625
86689
  if (name3 === "cite") {
86626
86690
  if (attrs.type === "user")
86627
86691
  return `@${attrs["user-name"] ?? (normalizeInline(textContent(nodes)) || "user")}`;
86628
- const title2 = attrs.title ?? (normalizeInline(textContent(nodes)) || "Referenced document");
86692
+ const title3 = attrs.title ?? (normalizeInline(textContent(nodes)) || "Referenced document");
86629
86693
  const docId = attrs["doc-id"] ?? attrs.token;
86630
- const resource2 = registerLarkResource(ctx, name3, "cite", attrs, title2);
86694
+ const resource2 = registerLarkResource(ctx, name3, "cite", attrs, title3);
86631
86695
  if (docId === undefined) {
86632
86696
  ctx.tracker.flag(name3, "cite has no doc-id or token", "error");
86633
- return `${escapeMarkdownLabel(title2)} <!-- ${resource2.locator} -->`;
86697
+ return `${escapeMarkdownLabel(title3)} <!-- ${resource2.locator} -->`;
86634
86698
  }
86635
- return `[${escapeMarkdownLabel(title2)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource2.locator} -->`;
86699
+ return `[${escapeMarkdownLabel(title3)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource2.locator} -->`;
86636
86700
  }
86637
86701
  if (name3 === "img" || name3 === "image") {
86638
- const title2 = attrs.alt ?? attrs.name ?? "Image";
86639
- const resource2 = registerLarkResource(ctx, name3, "image", attrs, title2);
86702
+ const title3 = attrs.alt ?? attrs.name ?? "Image";
86703
+ const resource2 = registerLarkResource(ctx, name3, "image", attrs, title3);
86640
86704
  return `
86641
86705
 
86642
- > Image: ${title2} (${resource2.locator})
86706
+ > Image: ${title3} (${resource2.locator})
86643
86707
 
86644
86708
  `;
86645
86709
  }
86646
86710
  if (name3 === "source" || name3 === "file" || name3 === "attachment") {
86647
86711
  const isVideo = attrs.mime?.startsWith("video/") === true || attrs.type === "video";
86648
86712
  const kind2 = isVideo ? "video" : "file";
86649
- const title2 = attrs.name ?? (isVideo ? "Video" : "File");
86650
- const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title2);
86713
+ const title3 = attrs.name ?? (isVideo ? "Video" : "File");
86714
+ const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title3);
86651
86715
  return `
86652
86716
 
86653
- > ${isVideo ? "Video" : "File"}: ${title2} (${resource2.locator})
86717
+ > ${isVideo ? "Video" : "File"}: ${title3} (${resource2.locator})
86654
86718
 
86655
86719
  `;
86656
86720
  }
@@ -86670,7 +86734,7 @@ ${safeFence(source2, attrs.type === "mermaid" ? "mermaid" : "text")}
86670
86734
  }
86671
86735
  if (name3 === "diagram") {
86672
86736
  const source2 = textContent(nodes).trim();
86673
- const resourceAttrs = source2.length > 0 ? { ...attrs, "content-hash": createHash24("sha256").update(source2, "utf8").digest("hex") } : attrs;
86737
+ const resourceAttrs = source2.length > 0 ? { ...attrs, "content-hash": createHash25("sha256").update(source2, "utf8").digest("hex") } : attrs;
86674
86738
  const resource2 = registerLarkResource(ctx, name3, "diagram", resourceAttrs, attrs.title ?? "Diagram");
86675
86739
  if (source2.length > 0) {
86676
86740
  resource2.inline_content = true;
@@ -86689,92 +86753,92 @@ ${safeFence(source2, attrs.type ?? "text")}
86689
86753
  `;
86690
86754
  }
86691
86755
  if (name3 === "chat_card") {
86692
- const title2 = attrs.name ?? "Chat";
86693
- const resource2 = registerLarkResource(ctx, name3, "chat", attrs, title2);
86756
+ const title3 = attrs.name ?? "Chat";
86757
+ const resource2 = registerLarkResource(ctx, name3, "chat", attrs, title3);
86694
86758
  return `
86695
86759
 
86696
- > Chat: ${title2} (${resource2.locator})
86760
+ > Chat: ${title3} (${resource2.locator})
86697
86761
 
86698
86762
  `;
86699
86763
  }
86700
86764
  if (name3 === "readonly-block") {
86701
- const title2 = attrs.type ?? "Read-only embedded block";
86765
+ const title3 = attrs.type ?? "Read-only embedded block";
86702
86766
  const kind2 = attrs.type === "diagram" ? "diagram" : "embed";
86703
- const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title2);
86767
+ const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title3);
86704
86768
  return kind2 === "diagram" ? `
86705
86769
 
86706
86770
  > Diagram: ${resource2.locator}
86707
86771
 
86708
86772
  ` : `
86709
86773
 
86710
- > Embedded block: ${title2} (${resource2.locator})
86774
+ > Embedded block: ${title3} (${resource2.locator})
86711
86775
 
86712
86776
  `;
86713
86777
  }
86714
86778
  const kind = name3 === "sheet" ? "sheet" : "base";
86715
- const title = attrs.title ?? (kind === "sheet" ? "Embedded Sheet" : "Embedded Base");
86716
- const resource = registerLarkResource(ctx, name3, kind, attrs, title);
86779
+ const title2 = attrs.title ?? (kind === "sheet" ? "Embedded Sheet" : "Embedded Base");
86780
+ const resource = registerLarkResource(ctx, name3, kind, attrs, title2);
86717
86781
  const details = [attrs["table-id"], attrs["sheet-id"], attrs["view-id"]].filter(Boolean).join(" / ");
86718
86782
  return `
86719
86783
 
86720
- > ${title}${details.length > 0 ? ` — ${details}` : ""} (${resource.locator})
86784
+ > ${title2}${details.length > 0 ? ` — ${details}` : ""} (${resource.locator})
86721
86785
 
86722
86786
  `;
86723
86787
  }
86724
86788
  function renderLarkSubPage(nodes, attrs, ctx) {
86725
- const title = attrs.title ?? (normalizeInline(textContent(nodes)) || "Untitled subpage");
86789
+ const title2 = attrs.title ?? (normalizeInline(textContent(nodes)) || "Untitled subpage");
86726
86790
  const docId = attrs["doc-id"] ?? attrs.token;
86727
- const resource = registerLarkResource(ctx, "sub-page", "document", attrs, title);
86791
+ const resource = registerLarkResource(ctx, "sub-page", "document", attrs, title2);
86728
86792
  if (docId === undefined) {
86729
86793
  ctx.tracker.flag("sub-page", "sub-page has no doc-id or token", "error");
86730
- return `${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->`;
86794
+ return `${escapeMarkdownLabel(title2)} <!-- ${resource.locator} -->`;
86731
86795
  }
86732
- return `[${escapeMarkdownLabel(title)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource.locator} -->`;
86796
+ return `[${escapeMarkdownLabel(title2)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource.locator} -->`;
86733
86797
  }
86734
86798
  function renderLarkBookmark(nodes, attrs, ctx) {
86735
86799
  const href = attrs.href ?? attrs.url;
86736
- const title = attrs.name ?? attrs.title ?? (normalizeInline(textContent(nodes)) || href) ?? "Bookmark";
86737
- const resource = registerLarkResource(ctx, "bookmark", "bookmark", attrs, title);
86800
+ const title2 = attrs.name ?? attrs.title ?? (normalizeInline(textContent(nodes)) || href) ?? "Bookmark";
86801
+ const resource = registerLarkResource(ctx, "bookmark", "bookmark", attrs, title2);
86738
86802
  if (href === undefined || isTransientLarkMediaUrl(href)) {
86739
86803
  ctx.tracker.flag("bookmark", "bookmark has no stable non-transient URL", "error");
86740
86804
  return `
86741
86805
 
86742
- > Bookmark: ${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->
86806
+ > Bookmark: ${escapeMarkdownLabel(title2)} <!-- ${resource.locator} -->
86743
86807
 
86744
86808
  `;
86745
86809
  }
86746
86810
  return `
86747
86811
 
86748
- > Bookmark: [${escapeMarkdownLabel(title)}](${href}) <!-- ${resource.locator} -->
86812
+ > Bookmark: [${escapeMarkdownLabel(title2)}](${href}) <!-- ${resource.locator} -->
86749
86813
 
86750
86814
  `;
86751
86815
  }
86752
86816
  function renderLarkSyncedReference(attrs, ctx) {
86753
86817
  const sourceToken = attrs["src-token"];
86754
86818
  const sourceBlockId = attrs["src-block-id"];
86755
- const title = attrs.title ?? attrs.name ?? "Synced reference";
86756
- const resource = registerLarkResource(ctx, "synced_reference", "synced-reference", attrs, title);
86819
+ const title2 = attrs.title ?? attrs.name ?? "Synced reference";
86820
+ const resource = registerLarkResource(ctx, "synced_reference", "synced-reference", attrs, title2);
86757
86821
  if (sourceToken === undefined || sourceBlockId === undefined) {
86758
86822
  ctx.tracker.flag("synced_reference", "synced_reference requires both src-token and src-block-id", "error");
86759
86823
  return `
86760
86824
 
86761
- > ${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->
86825
+ > ${escapeMarkdownLabel(title2)} <!-- ${resource.locator} -->
86762
86826
 
86763
86827
  `;
86764
86828
  }
86765
86829
  const target = `${stableDocumentUrl(ctx.sourceUrl, "docx", sourceToken)}#${encodeURIComponent(sourceBlockId)}`;
86766
86830
  return `
86767
86831
 
86768
- > [${escapeMarkdownLabel(title)}](${target}) <!-- ${resource.locator} -->
86832
+ > [${escapeMarkdownLabel(title2)}](${target}) <!-- ${resource.locator} -->
86769
86833
 
86770
86834
  `;
86771
86835
  }
86772
86836
  var init_larkDocxResources = () => {};
86773
86837
 
86774
86838
  // src/lib/larkDocxXml.ts
86775
- import { createHash as createHash25 } from "node:crypto";
86839
+ import { createHash as createHash26 } from "node:crypto";
86776
86840
  function sha256(value) {
86777
- return `sha256:${createHash25("sha256").update(value, "utf8").digest("hex")}`;
86841
+ return `sha256:${createHash26("sha256").update(value, "utf8").digest("hex")}`;
86778
86842
  }
86779
86843
  function elementName2(node3) {
86780
86844
  return Object.keys(node3).find((key) => key !== ":@" && key !== "#text");
@@ -86825,9 +86889,9 @@ function renderList(nodes, ctx, ordered) {
86825
86889
  const name3 = elementName2(item);
86826
86890
  ctx.tracker.discover(name3);
86827
86891
  ctx.tracker.convert(name3);
86828
- const body = normalizeMarkdown(renderChildren(elementChildren2(item, name3), { ...ctx, mode: "inline" }));
86892
+ const body2 = normalizeMarkdown(renderChildren(elementChildren2(item, name3), { ...ctx, mode: "inline" }));
86829
86893
  const marker = ordered ? `${index2 + 1}.` : "-";
86830
- return body.split(`
86894
+ return body2.split(`
86831
86895
  `).map((line, lineIndex) => lineIndex === 0 ? `${marker} ${line}` : ` ${line}`).join(`
86832
86896
  `);
86833
86897
  }).join(`
@@ -86903,15 +86967,15 @@ function renderChecklistItem(blockType, nodes, attrs, ctx) {
86903
86967
  const checked = attrs.checked;
86904
86968
  const state = done ?? checked;
86905
86969
  const stateIsValid = (state === "true" || state === "false") && (done === undefined || checked === undefined || done === checked);
86906
- const body = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86970
+ const body2 = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86907
86971
  if (!stateIsValid) {
86908
86972
  ctx.tracker.flag(blockType, `${blockType} requires one unambiguous boolean done or checked attribute`, "warning", "lark.capture.checkbox-state-invalid", "projection");
86909
86973
  return `
86910
- - [?] ${body}
86974
+ - [?] ${body2}
86911
86975
  `;
86912
86976
  }
86913
86977
  return `
86914
- - [${state === "true" ? "x" : " "}] ${body}
86978
+ - [${state === "true" ? "x" : " "}] ${body2}
86915
86979
  `;
86916
86980
  }
86917
86981
  function meaningfulAttributes(attrs, excluded2) {
@@ -86921,8 +86985,8 @@ function auditableAttributes(attrs) {
86921
86985
  return Object.entries(attrs).filter(([, value]) => value.length > 0).map(([key, value]) => [key, isTransientLarkMediaUrl(value) ? "[redacted-transient-url]" : value]).sort(([left], [right]) => left.localeCompare(right));
86922
86986
  }
86923
86987
  function renderPollOption(blockType, nodes, attrs, ctx) {
86924
- const body = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86925
- const label2 = body || attrs.name || attrs.title || attrs.label || attrs.value;
86988
+ const body2 = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86989
+ const label2 = body2 || attrs.name || attrs.title || attrs.label || attrs.value;
86926
86990
  const details = meaningfulAttributes(attrs, new Set(["name", "title", "label", "value"])).map(([key, value]) => `${key}=${value}`);
86927
86991
  if (label2 === undefined && details.length === 0) {
86928
86992
  ctx.tracker.flag(blockType, `${blockType} has no visible label or metadata`, "warning", "lark.capture.poll-option-empty", "projection");
@@ -86933,10 +86997,10 @@ function renderPollOption(blockType, nodes, attrs, ctx) {
86933
86997
  `;
86934
86998
  }
86935
86999
  function renderPoll(nodes, attrs, ctx) {
86936
- const title = attrs.name ?? attrs.title ?? "Untitled poll";
86937
- const resource = registerLarkResource(ctx, "poll", "poll", attrs, title);
87000
+ const title2 = attrs.name ?? attrs.title ?? "Untitled poll";
87001
+ const resource = registerLarkResource(ctx, "poll", "poll", attrs, title2);
86938
87002
  const href = attrs.href ?? attrs.url;
86939
- const label2 = href !== undefined && !isTransientLarkMediaUrl(href) ? `[${escapeMarkdownLabel2(title)}](${href})` : escapeMarkdownLabel2(title);
87003
+ const label2 = href !== undefined && !isTransientLarkMediaUrl(href) ? `[${escapeMarkdownLabel2(title2)}](${href})` : escapeMarkdownLabel2(title2);
86940
87004
  const details = meaningfulAttributes(attrs, new Set(["name", "title", "href", "url"])).map(([key, value]) => `${key}=${value}`);
86941
87005
  const children = normalizeMarkdown(renderChildren(nodes, { ...ctx, mode: "block" }));
86942
87006
  resource.inline_content = children.length > 0;
@@ -86953,15 +87017,15 @@ ${lines.join(`
86953
87017
  `;
86954
87018
  }
86955
87019
  function renderUnknown(name3, nodes, attrs, ctx) {
86956
- const body = normalizeMarkdown(renderChildren(nodes, ctx));
87020
+ const body2 = normalizeMarkdown(renderChildren(nodes, ctx));
86957
87021
  const exportedAttrs = auditableAttributes(attrs);
86958
- if (body.length === 0 && exportedAttrs.length === 0) {
87022
+ if (body2.length === 0 && exportedAttrs.length === 0) {
86959
87023
  ctx.tracker.skip(name3, "unknown empty block omitted", "warning");
86960
87024
  return "";
86961
87025
  }
86962
87026
  ctx.tracker.convert(name3);
86963
87027
  ctx.tracker.flag(name3, "block was preserved through the generic non-interactive projection; inspect source.xml for the original structure", "warning", "lark.capture.generic-projection", "projection");
86964
- const digest6 = createHash25("sha256").update(JSON.stringify({ name: name3, attributes: exportedAttrs, text: normalizeInline2(textContent2(nodes)) }), "utf8").digest("hex").slice(0, 12);
87028
+ const digest6 = createHash26("sha256").update(JSON.stringify({ name: name3, attributes: exportedAttrs, text: normalizeInline2(textContent2(nodes)) }), "utf8").digest("hex").slice(0, 12);
86965
87029
  const locator = `lark:block:${name3}:${digest6}`;
86966
87030
  ctx.resources.push({
86967
87031
  kind: "embed",
@@ -86972,7 +87036,7 @@ function renderUnknown(name3, nodes, attrs, ctx) {
86972
87036
  const lines = [
86973
87037
  `> Lark block (generic projection): \`${name3}\` <!-- ${locator} -->`,
86974
87038
  ...exportedAttrs.length > 0 ? [`> Exported attributes: ${JSON.stringify(Object.fromEntries(exportedAttrs))}`] : [],
86975
- ...body.length > 0 ? [body] : []
87039
+ ...body2.length > 0 ? [body2] : []
86976
87040
  ];
86977
87041
  return `
86978
87042
 
@@ -87021,10 +87085,10 @@ ${"#".repeat(Math.min(Math.max(level, 1), 6))} ${normalizeInline2(renderChildren
87021
87085
  }
87022
87086
  if (["p", "paragraph", "div", "section"].includes(name3)) {
87023
87087
  ctx.tracker.convert(name3);
87024
- const body = renderChildren(nodes, { ...ctx, mode: "inline" });
87025
- return ctx.mode === "inline" ? body : `
87088
+ const body2 = renderChildren(nodes, { ...ctx, mode: "inline" });
87089
+ return ctx.mode === "inline" ? body2 : `
87026
87090
 
87027
- ${body}
87091
+ ${body2}
87028
87092
 
87029
87093
  `;
87030
87094
  }
@@ -87064,8 +87128,8 @@ ${body}
87064
87128
  }
87065
87129
  if (name3 === "code") {
87066
87130
  ctx.tracker.convert(name3);
87067
- const body = renderChildren(nodes, { ...ctx, mode: "code" });
87068
- return ctx.mode === "code" ? body : `\`${body.replace(/`/gu, "\\`")}\``;
87131
+ const body2 = renderChildren(nodes, { ...ctx, mode: "code" });
87132
+ return ctx.mode === "code" ? body2 : `\`${body2.replace(/`/gu, "\\`")}\``;
87069
87133
  }
87070
87134
  if (name3 === "pre") {
87071
87135
  ctx.tracker.convert(name3);
@@ -87117,10 +87181,10 @@ ${renderTable(nodes, ctx)}
87117
87181
  if (name3 === "callout" || name3 === "blockquote" || name3 === "quote") {
87118
87182
  ctx.tracker.convert(name3);
87119
87183
  const prefix = attrs.emoji === undefined ? "" : `${attrs.emoji} `;
87120
- const body = normalizeMarkdown(renderChildren(nodes, { ...ctx, mode: "block" }));
87184
+ const body2 = normalizeMarkdown(renderChildren(nodes, { ...ctx, mode: "block" }));
87121
87185
  return `
87122
87186
 
87123
- ${body.split(`
87187
+ ${body2.split(`
87124
87188
  `).map((line, index2) => `> ${index2 === 0 ? prefix : ""}${line}`).join(`
87125
87189
  `)}
87126
87190
 
@@ -87176,10 +87240,10 @@ function projectLarkDocxXml(input) {
87176
87240
  mode: "block"
87177
87241
  }));
87178
87242
  const titleNode = rootChildren.find((node3) => elementName2(node3) === "title");
87179
- const title = titleNode === undefined ? undefined : normalizeInline2(textContent2(elementChildren2(titleNode, "title")));
87243
+ const title2 = titleNode === undefined ? undefined : normalizeInline2(textContent2(elementChildren2(titleNode, "title")));
87180
87244
  return {
87181
87245
  markdown,
87182
- ...title !== undefined && title.length > 0 ? { title } : {},
87246
+ ...title2 !== undefined && title2.length > 0 ? { title: title2 } : {},
87183
87247
  auditXml: sanitizeAuditXml(input.xml),
87184
87248
  rawContentHash,
87185
87249
  resources,
@@ -87252,6 +87316,32 @@ var init_larkDocxXml = __esm(() => {
87252
87316
  ]);
87253
87317
  });
87254
87318
 
87319
+ // src/lib/larkImagePolicy.ts
87320
+ function omitLarkImage(resource, policy, items, replacements, mediaType, failure2) {
87321
+ if (resource.kind !== "image" && !mediaType?.startsWith("image/"))
87322
+ return false;
87323
+ const gif = mediaType === "image/gif" || /\.gif$/iu.test(resource.title ?? "") || Object.entries(resource.attributes).some(([key, value]) => ["mime_type", "content_type"].includes(key) && value === "image/gif");
87324
+ if (!failure2 && policy.images !== "reference-only" && !(gif && policy.gifs === "reference-only"))
87325
+ return false;
87326
+ const title2 = (resource.title ?? "image").replace(/[\r\n<>]/gu, " ");
87327
+ replacements.set(resource.locator, `> Image omitted: ${title2}. ${failure2 ? "Resource size limit exceeded." : "Excluded by selected policy."} See the source document. <!-- ${resource.locator} -->`);
87328
+ items.push({
87329
+ kind: resource.kind,
87330
+ locator: resource.locator,
87331
+ status: "reference-only",
87332
+ required: false,
87333
+ asset_paths: [],
87334
+ reason_code: failure2 ? "image-budget-exceeded" : "image-excluded-by-policy",
87335
+ reason: failure2 ?? "Image bytes were not retained; the selected image policy preserves a placeholder and source reference"
87336
+ });
87337
+ return true;
87338
+ }
87339
+ var LarkResourceBudgetError;
87340
+ var init_larkImagePolicy = __esm(() => {
87341
+ LarkResourceBudgetError = class LarkResourceBudgetError extends Error {
87342
+ };
87343
+ });
87344
+
87255
87345
  // src/lib/larkResourceCommand.ts
87256
87346
  function stableJson(value) {
87257
87347
  if (value === undefined)
@@ -87321,9 +87411,9 @@ var init_larkResourceCommand = __esm(() => {
87321
87411
  });
87322
87412
 
87323
87413
  // src/lib/larkResourceMaterialization.ts
87324
- import { createHash as createHash26 } from "node:crypto";
87325
- import { mkdtemp as mkdtemp4, readFile as readFile69, readdir as readdir20, rm as rm19 } from "node:fs/promises";
87326
- import { extname as extname13, join as join89 } from "node:path";
87414
+ import { createHash as createHash27 } from "node:crypto";
87415
+ import { mkdtemp as mkdtemp4, readFile as readFile71, readdir as readdir21, rm as rm19 } from "node:fs/promises";
87416
+ import { extname as extname13, join as join91 } from "node:path";
87327
87417
  import { tmpdir } from "node:os";
87328
87418
  function countByKind(items, status) {
87329
87419
  const counts2 = new Map;
@@ -87335,7 +87425,7 @@ function countByKind(items, status) {
87335
87425
  return Object.fromEntries([...counts2].sort(([left], [right]) => left.localeCompare(right)));
87336
87426
  }
87337
87427
  function resourceDigest(resource) {
87338
- return createHash26("sha256").update(`${resource.kind}\x00${resource.locator}`, "utf8").digest("hex").slice(0, 20);
87428
+ return createHash27("sha256").update(`${resource.kind}\x00${resource.locator}`, "utf8").digest("hex").slice(0, 20);
87339
87429
  }
87340
87430
  function safeLabel(value, fallback) {
87341
87431
  const normalized = (value ?? fallback).replace(/[\r\n]+/gu, " ").trim();
@@ -87428,10 +87518,10 @@ function findBooleanField(value, name3) {
87428
87518
  }
87429
87519
  async function downloadedFile(input) {
87430
87520
  if (input.localPath !== undefined) {
87431
- const bytes = await readFile69(input.localPath);
87521
+ const bytes = await readFile71(input.localPath);
87432
87522
  return { path: input.localPath, bytes, mediaType: mediaTypeFor(input.localPath, bytes) };
87433
87523
  }
87434
- const tempRoot = await mkdtemp4(join89(tmpdir(), "context-lark-resource-"));
87524
+ const tempRoot = await mkdtemp4(join91(tmpdir(), "context-lark-resource-"));
87435
87525
  try {
87436
87526
  await runLarkResourceCommand(input.runner, [
87437
87527
  "docs",
@@ -87448,11 +87538,11 @@ async function downloadedFile(input) {
87448
87538
  "--format",
87449
87539
  "json"
87450
87540
  ], { cwd: tempRoot });
87451
- const entries2 = (await readdir20(tempRoot, { withFileTypes: true })).filter((entry) => entry.isFile());
87541
+ const entries2 = (await readdir21(tempRoot, { withFileTypes: true })).filter((entry) => entry.isFile());
87452
87542
  if (entries2.length !== 1)
87453
87543
  throw new Error(`media download produced ${entries2.length} files, expected exactly one`);
87454
87544
  const path3 = entries2[0]?.name ?? "resource.bin";
87455
- const bytes = await readFile69(join89(tempRoot, path3));
87545
+ const bytes = await readFile71(join91(tempRoot, path3));
87456
87546
  return { path: path3, bytes, mediaType: mediaTypeFor(path3, bytes) };
87457
87547
  } finally {
87458
87548
  await rm19(tempRoot, { recursive: true, force: true });
@@ -87543,9 +87633,9 @@ async function sheetMaterialization(resource, runner2, identity) {
87543
87633
  const sheetId = resource.attributes["sheet-id"];
87544
87634
  if (token === undefined || sheetId === undefined)
87545
87635
  throw new Error("embedded Sheet requires token and sheet-id");
87546
- const tempRoot = await mkdtemp4(join89(tmpdir(), "context-lark-sheet-"));
87636
+ const tempRoot = await mkdtemp4(join91(tmpdir(), "context-lark-sheet-"));
87547
87637
  try {
87548
- const outputPath = join89(tempRoot, "sheet.json");
87638
+ const outputPath = join91(tempRoot, "sheet.json");
87549
87639
  const stdout = await runLarkResourceCommand(runner2, [
87550
87640
  "sheets",
87551
87641
  "+csv-get",
@@ -87565,13 +87655,13 @@ async function sheetMaterialization(resource, runner2, identity) {
87565
87655
  if (findBooleanField(receipt2, "truncated") === true || findBooleanField(receipt2, "complete") === false) {
87566
87656
  throw new Error("embedded Sheet read was truncated");
87567
87657
  }
87568
- const payload = JSON.parse(await readFile69(outputPath, "utf8"));
87658
+ const payload = JSON.parse(await readFile71(outputPath, "utf8"));
87569
87659
  const csv = findStringField(payload, new Set(["annotated_csv", "csv", "content", "text"]));
87570
87660
  if (csv === undefined)
87571
87661
  throw new Error("embedded Sheet response has no CSV payload");
87572
87662
  const digest6 = resourceDigest(resource);
87573
87663
  const path3 = `materialized/sheet/${digest6}.csv`;
87574
- const title = safeLabel(resource.title, "Embedded Sheet");
87664
+ const title2 = safeLabel(resource.title, "Embedded Sheet");
87575
87665
  return {
87576
87666
  asset: {
87577
87667
  path: path3,
@@ -87580,7 +87670,7 @@ async function sheetMaterialization(resource, runner2, identity) {
87580
87670
  role: "evidence",
87581
87671
  source: { kind: resource.kind, locator: resource.locator }
87582
87672
  },
87583
- replacement: `#### ${title}
87673
+ replacement: `#### ${title2}
87584
87674
 
87585
87675
  ${markdownTable2(parseCsv(csv))}
87586
87676
 
@@ -87680,7 +87770,7 @@ async function baseMaterialization(resource, runner2, identity) {
87680
87770
  `;
87681
87771
  const digest6 = resourceDigest(resource);
87682
87772
  const path3 = `materialized/base/${digest6}.json`;
87683
- const title = safeLabel(resource.title, "Embedded Base");
87773
+ const title2 = safeLabel(resource.title, "Embedded Base");
87684
87774
  return {
87685
87775
  asset: {
87686
87776
  path: path3,
@@ -87689,7 +87779,7 @@ async function baseMaterialization(resource, runner2, identity) {
87689
87779
  role: "evidence",
87690
87780
  source: { kind: resource.kind, locator: resource.locator }
87691
87781
  },
87692
- replacement: `#### ${title}
87782
+ replacement: `#### ${title2}
87693
87783
 
87694
87784
  ${markdownTable2(baseRows(records, fieldOrder))}
87695
87785
 
@@ -87701,7 +87791,7 @@ async function whiteboardMaterialization(resource, runner2, identity) {
87701
87791
  if (token === undefined)
87702
87792
  throw new Error(`${resource.kind} has no whiteboard token`);
87703
87793
  const preview = await downloadedFile({ runner: runner2, identity, token, type: "whiteboard" });
87704
- const tempRoot = await mkdtemp4(join89(tmpdir(), "context-lark-whiteboard-"));
87794
+ const tempRoot = await mkdtemp4(join91(tmpdir(), "context-lark-whiteboard-"));
87705
87795
  let rawPayload;
87706
87796
  try {
87707
87797
  await runLarkResourceCommand(runner2, [
@@ -87719,14 +87809,14 @@ async function whiteboardMaterialization(resource, runner2, identity) {
87719
87809
  "--format",
87720
87810
  "json"
87721
87811
  ], { cwd: tempRoot });
87722
- rawPayload = JSON.parse(await readFile69(join89(tempRoot, "raw.json"), "utf8"));
87812
+ rawPayload = JSON.parse(await readFile71(join91(tempRoot, "raw.json"), "utf8"));
87723
87813
  } finally {
87724
87814
  await rm19(tempRoot, { recursive: true, force: true });
87725
87815
  }
87726
87816
  const digest6 = resourceDigest(resource);
87727
87817
  const previewPath = `materialized/${resource.kind}/${digest6}${extensionFor2(preview.mediaType, preview.path)}`;
87728
87818
  const rawPath = `materialized/${resource.kind}/${digest6}.json`;
87729
- const title = markdownLabel(safeLabel(resource.title, resource.kind === "diagram" ? "Diagram" : "Whiteboard"));
87819
+ const title2 = markdownLabel(safeLabel(resource.title, resource.kind === "diagram" ? "Diagram" : "Whiteboard"));
87730
87820
  return {
87731
87821
  assets: [
87732
87822
  {
@@ -87745,20 +87835,20 @@ async function whiteboardMaterialization(resource, runner2, identity) {
87745
87835
  source: { kind: resource.kind, locator: resource.locator }
87746
87836
  }
87747
87837
  ],
87748
- replacement: `![${title}](${sourceAssetTarget(previewPath)})
87838
+ replacement: `![${title2}](${sourceAssetTarget(previewPath)})
87749
87839
 
87750
87840
  [Raw snapshot](${sourceAssetTarget(rawPath)}) <!-- ${resource.locator} -->`
87751
87841
  };
87752
87842
  }
87753
87843
  function placeholderFor(resource) {
87754
- const title = safeLabel(resource.title, resource.kind);
87844
+ const title2 = safeLabel(resource.title, resource.kind);
87755
87845
  switch (resource.kind) {
87756
87846
  case "image":
87757
- return `> Image: ${title} (${resource.locator})`;
87847
+ return `> Image: ${title2} (${resource.locator})`;
87758
87848
  case "video":
87759
- return `> Video: ${title} (${resource.locator})`;
87849
+ return `> Video: ${title2} (${resource.locator})`;
87760
87850
  case "file":
87761
- return `> File: ${title} (${resource.locator})`;
87851
+ return `> File: ${title2} (${resource.locator})`;
87762
87852
  case "whiteboard":
87763
87853
  return `> Whiteboard: ${resource.locator}`;
87764
87854
  case "diagram":
@@ -87780,7 +87870,7 @@ function referenceOnlyReason(resource) {
87780
87870
  }
87781
87871
  function materializationReport(items) {
87782
87872
  const hasRequiredFailure = items.some((item) => item.status === "failed" && item.required && !isNonBlockingDocumentResourceFailureReasonCode(item.reason_code));
87783
- const hasOptionalFailure = items.some((item) => item.status === "failed") || items.some((item) => item.status === "reference-only" && item.kind === "poll" && item.reason?.includes("absent") === true);
87873
+ const hasOptionalFailure = items.some((item) => item.status === "failed" || item.reason_code === "image-budget-exceeded") || items.some((item) => item.status === "reference-only" && item.kind === "poll" && item.reason?.includes("absent") === true);
87784
87874
  return {
87785
87875
  status: hasRequiredFailure ? "error" : hasOptionalFailure ? "warning" : "complete",
87786
87876
  discovered: countByKind(items),
@@ -87800,16 +87890,16 @@ function resourceFailureReasonCode(resource, error) {
87800
87890
  return /\b2890003\b/u.test(message) ? DOCUMENT_RESOURCE_SOURCE_MISSING_REASON_CODE : undefined;
87801
87891
  }
87802
87892
  function unavailableReplacement(resource, reasonCode) {
87803
- const title = markdownLabel(safeLabel(resource.title, resource.kind));
87893
+ const title2 = markdownLabel(safeLabel(resource.title, resource.kind));
87804
87894
  const reason = reasonCode === DOCUMENT_RESOURCE_PERMISSION_DENIED_REASON_CODE ? "export permission denied" : "source no longer exists";
87805
- return `> Resource unavailable: ${title} (${resource.kind}; ${reason}). <!-- ${resource.locator} -->`;
87895
+ return `> Resource unavailable: ${title2} (${resource.kind}; ${reason}). <!-- ${resource.locator} -->`;
87806
87896
  }
87807
87897
  function assertBudget(asset, policy, total) {
87808
87898
  if (asset.bytes.byteLength > policy.maxBytesPerResource) {
87809
- throw new Error(`resource is ${asset.bytes.byteLength} bytes, above maxBytesPerResource=${policy.maxBytesPerResource}`);
87899
+ throw new LarkResourceBudgetError(`resource is ${asset.bytes.byteLength} bytes, above maxBytesPerResource=${policy.maxBytesPerResource}`);
87810
87900
  }
87811
87901
  if (total + asset.bytes.byteLength > policy.maxTotalBytes) {
87812
- throw new Error(`materialized resources exceed maxTotalBytes=${policy.maxTotalBytes}`);
87902
+ throw new LarkResourceBudgetError(`materialized resources exceed maxTotalBytes=${policy.maxTotalBytes}`);
87813
87903
  }
87814
87904
  }
87815
87905
  async function materializeLarkResources(input) {
@@ -87825,6 +87915,8 @@ async function materializeLarkResources(input) {
87825
87915
  return;
87826
87916
  seen.add(key);
87827
87917
  const required = REQUIRED_KINDS.has(resource.kind);
87918
+ if (omitLarkImage(resource, input.policy, items, replacements))
87919
+ return;
87828
87920
  try {
87829
87921
  if (resource.kind === "diagram" && resource.inline_content === true) {
87830
87922
  replacements.set(resource.locator, "");
@@ -87911,6 +88003,8 @@ async function materializeLarkResources(input) {
87911
88003
  type: "media",
87912
88004
  ...input.mediaFiles?.[token] === undefined ? {} : { localPath: input.mediaFiles[token] }
87913
88005
  });
88006
+ if (omitLarkImage(resource, input.policy, items, replacements, downloaded.mediaType))
88007
+ return;
87914
88008
  const digest6 = resourceDigest(resource);
87915
88009
  const extension2 = extensionFor2(downloaded.mediaType, downloaded.path);
87916
88010
  const asset = {
@@ -87923,12 +88017,14 @@ async function materializeLarkResources(input) {
87923
88017
  assertBudget(asset, input.policy, totalBytes);
87924
88018
  totalBytes += asset.bytes.byteLength;
87925
88019
  assets.push(asset);
87926
- const title = markdownLabel(safeLabel(resource.title, resource.kind));
88020
+ const title2 = markdownLabel(safeLabel(resource.title, resource.kind));
87927
88021
  const target = sourceAssetTarget(asset.path);
87928
- const replacement = downloaded.mediaType.startsWith("image/") ? `![${title}](${target}) <!-- ${resource.locator} -->` : `[${title}](${target}) <!-- ${resource.locator} -->`;
88022
+ const replacement = downloaded.mediaType.startsWith("image/") ? `![${title2}](${target}) <!-- ${resource.locator} -->` : `[${title2}](${target}) <!-- ${resource.locator} -->`;
87929
88023
  replacements.set(resource.locator, replacement);
87930
88024
  items.push({ kind: resource.kind, locator: resource.locator, status: "materialized", required, asset_paths: [asset.path] });
87931
88025
  } catch (error) {
88026
+ if (error instanceof LarkResourceBudgetError && omitLarkImage(resource, input.policy, items, replacements, undefined, error.message))
88027
+ return;
87932
88028
  const reasonCode = resourceFailureReasonCode(resource, error);
87933
88029
  if (isNonBlockingDocumentResourceFailureReasonCode(reasonCode)) {
87934
88030
  replacements.set(resource.locator, unavailableReplacement(resource, reasonCode));
@@ -87973,6 +88069,7 @@ function applyLarkResourceReplacements(markdown, resources, replacements) {
87973
88069
  }
87974
88070
  var REQUIRED_KINDS, MEDIA_TYPES_BY_EXTENSION;
87975
88071
  var init_larkResourceMaterialization = __esm(() => {
88072
+ init_larkImagePolicy();
87976
88073
  init_src3();
87977
88074
  init_larkResourceCommand();
87978
88075
  REQUIRED_KINDS = new Set([
@@ -88145,9 +88242,9 @@ function payloadShapeSummary(payload) {
88145
88242
  }
88146
88243
  function extractDocsFetchContent(payload, requestedFormat) {
88147
88244
  const document4 = payload.document && typeof payload.document === "object" ? payload.document : undefined;
88148
- const title = stringValue(payload.title) ?? stringValue(document4?.title);
88245
+ const title2 = stringValue(payload.title) ?? stringValue(document4?.title);
88149
88246
  const markdown = stringValue(payload.markdown) ?? stringValue(document4?.markdown);
88150
- const withTitle = (result) => title === undefined ? result : { ...result, title };
88247
+ const withTitle = (result) => title2 === undefined ? result : { ...result, title: title2 };
88151
88248
  if (markdown !== undefined) {
88152
88249
  if (requestedFormat === "xml") {
88153
88250
  throw new LarkCliError(`${LARK_BIN} docs +fetch returned Markdown despite --doc-format xml; capture stopped because the response cannot provide auditable rich-block fidelity. Upgrade lark-cli and retry.`, 0, "");
@@ -88265,7 +88362,7 @@ function userIdentityUnavailable(error) {
88265
88362
  async function fetchDocsResponse(input, docsFetchPlan, runner2, identity) {
88266
88363
  const chunks = [];
88267
88364
  let contentFormat;
88268
- let title;
88365
+ let title2;
88269
88366
  let revisionId;
88270
88367
  let unsupportedShape;
88271
88368
  const assets = [];
@@ -88295,7 +88392,7 @@ async function fetchDocsResponse(input, docsFetchPlan, runner2, identity) {
88295
88392
  }
88296
88393
  contentFormat = extracted.format;
88297
88394
  if (page === 0 && extracted.title !== undefined)
88298
- title = extracted.title;
88395
+ title2 = extracted.title;
88299
88396
  revisionId ??= extractDocsFetchRevisionId(payload);
88300
88397
  assets.push(...extractDocsFetchAssets(payload));
88301
88398
  if (extracted.body !== undefined && extracted.body.length > 0) {
@@ -88317,19 +88414,19 @@ async function fetchDocsResponse(input, docsFetchPlan, runner2, identity) {
88317
88414
  throw new LarkCliError(`${LARK_BIN} docs +fetch exceeded ${MAX_FETCH_PAGES} pagination calls; likely a server-side issue`, 0, "");
88318
88415
  }
88319
88416
  }
88320
- const body = chunks.join(`
88417
+ const body2 = chunks.join(`
88321
88418
 
88322
88419
  `);
88323
- if (body.trim().length === 0 && (title === undefined || title.length === 0)) {
88420
+ if (body2.trim().length === 0 && (title2 === undefined || title2.length === 0)) {
88324
88421
  if (unsupportedShape !== undefined) {
88325
88422
  throw new LarkCliError(`${LARK_BIN} docs +fetch returned an unsupported payload shape (${unsupportedShape}). Expected data.markdown or data.document.content; this is a format adapter issue, not a permission error.`, 0, "");
88326
88423
  }
88327
88424
  throw new LarkCliError("document is empty — it may not exist or you lack permission", 0, "");
88328
88425
  }
88329
88426
  return {
88330
- body,
88427
+ body: body2,
88331
88428
  contentFormat,
88332
- ...title !== undefined ? { title } : {},
88429
+ ...title2 !== undefined ? { title: title2 } : {},
88333
88430
  ...revisionId !== undefined ? { revisionId } : {},
88334
88431
  assets
88335
88432
  };
@@ -88382,8 +88479,8 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88382
88479
  break;
88383
88480
  fetched = await fetchDocsResponse(input, docsFetchPlan, runner2, accessIdentity);
88384
88481
  }
88385
- let body = fetched.body;
88386
- let title = fetched.title;
88482
+ let body2 = fetched.body;
88483
+ let title2 = fetched.title;
88387
88484
  const revisionId = fetched.revisionId;
88388
88485
  const assets = [...fetched.assets];
88389
88486
  let fidelity = emptyFidelityReport();
@@ -88396,8 +88493,8 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88396
88493
  items: []
88397
88494
  };
88398
88495
  if (projection !== undefined) {
88399
- body = projection.markdown;
88400
- title ??= projection.title;
88496
+ body2 = projection.markdown;
88497
+ title2 ??= projection.title;
88401
88498
  fidelity = projection.fidelity;
88402
88499
  assets.push({
88403
88500
  path: "source.xml",
@@ -88410,7 +88507,7 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88410
88507
  }
88411
88508
  });
88412
88509
  }
88413
- const resources = [...new Map([...projection?.resources ?? [], ...larkMarkdownImageResources(body)].map((resource) => [resource.locator, resource])).values()];
88510
+ const resources = [...new Map([...projection?.resources ?? [], ...larkMarkdownImageResources(body2)].map((resource) => [resource.locator, resource])).values()];
88414
88511
  if (resources.length) {
88415
88512
  const policy = {
88416
88513
  ...DEFAULT_RESOURCE_POLICY,
@@ -88439,8 +88536,8 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88439
88536
  });
88440
88537
  }
88441
88538
  });
88442
- body = applyLarkResourceReplacements(body, resources, materialized.replacements);
88443
- body = replaceLarkMarkdownImages(body, materialized.replacements);
88539
+ body2 = applyLarkResourceReplacements(body2, resources, materialized.replacements);
88540
+ body2 = replaceLarkMarkdownImages(body2, materialized.replacements);
88444
88541
  assets.push(...materialized.assets.map((asset) => ({
88445
88542
  path: asset.path,
88446
88543
  bytes: asset.bytes,
@@ -88455,12 +88552,12 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88455
88552
  resourceMaterialization,
88456
88553
  resources
88457
88554
  }));
88458
- if (title !== undefined && title.length > 0 && !/^#\s/.test(body)) {
88555
+ if (title2 !== undefined && title2.length > 0 && !/^#\s/.test(body2)) {
88459
88556
  return {
88460
- markdown: `# ${title}
88557
+ markdown: `# ${title2}
88461
88558
 
88462
- ${body}`,
88463
- title,
88559
+ ${body2}`,
88560
+ title: title2,
88464
88561
  ...revisionId !== undefined ? { revisionId } : {},
88465
88562
  assets,
88466
88563
  fidelity,
@@ -88470,8 +88567,8 @@ ${body}`,
88470
88567
  };
88471
88568
  }
88472
88569
  return {
88473
- markdown: body,
88474
- ...title !== undefined ? { title } : {},
88570
+ markdown: body2,
88571
+ ...title2 !== undefined ? { title: title2 } : {},
88475
88572
  ...revisionId !== undefined ? { revisionId } : {},
88476
88573
  assets,
88477
88574
  fidelity,
@@ -88564,8 +88661,8 @@ var init_sensitiveSourceLiteral = __esm(() => {
88564
88661
  var LARK_DOCUMENT_NORMALIZER_VERSION = "lark-document-normalizer.v1";
88565
88662
 
88566
88663
  // src/project/documentCaptureLark.ts
88567
- import { readdir as readdir21, readFile as readFile70 } from "node:fs/promises";
88568
- import { basename as basename10, extname as extname14, join as join90 } from "node:path";
88664
+ import { readdir as readdir22, readFile as readFile72 } from "node:fs/promises";
88665
+ import { basename as basename10, extname as extname14, join as join92 } from "node:path";
88569
88666
  function titleFromMarkdown2(markdown, fallbackPath) {
88570
88667
  const heading2 = markdown.split(`
88571
88668
  `).find((line) => /^#\s+\S/u.test(line));
@@ -88584,7 +88681,7 @@ function countLines3(markdown) {
88584
88681
  }
88585
88682
  async function fileContentMatches(path3, content3) {
88586
88683
  try {
88587
- const current2 = await readFile70(path3);
88684
+ const current2 = await readFile72(path3);
88588
88685
  const expected = typeof content3 === "string" ? Buffer.from(content3, "utf8") : Buffer.from(content3);
88589
88686
  return current2.equals(expected);
88590
88687
  } catch {
@@ -88592,7 +88689,7 @@ async function fileContentMatches(path3, content3) {
88592
88689
  }
88593
88690
  }
88594
88691
  function sourceManifestPath2(entry) {
88595
- return entry.snapshot?.manifest ?? join90(entry.materializedAt, "manifest.json");
88692
+ return entry.snapshot?.manifest ?? join92(entry.materializedAt, "manifest.json");
88596
88693
  }
88597
88694
  function larkRuntimeError(message, detail) {
88598
88695
  return new ContextError(ExitCode.ExternalToolError, message, {
@@ -88718,12 +88815,12 @@ function assetManifestEntry(asset, assetRoot) {
88718
88815
  };
88719
88816
  }
88720
88817
  async function listSnapshotAssetFiles(root2, assetRoot) {
88721
- const assetsRoot = join90(root2, assetRoot);
88818
+ const assetsRoot = join92(root2, assetRoot);
88722
88819
  const files = [];
88723
88820
  const visit4 = async (dir, prefix = assetRoot) => {
88724
88821
  let entries2;
88725
88822
  try {
88726
- entries2 = await readdir21(dir, { withFileTypes: true });
88823
+ entries2 = await readdir22(dir, { withFileTypes: true });
88727
88824
  } catch (error) {
88728
88825
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
88729
88826
  return;
@@ -88731,7 +88828,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
88731
88828
  }
88732
88829
  for (const entry of entries2) {
88733
88830
  const relPath = `${prefix}/${entry.name}`;
88734
- const absolutePath = join90(dir, entry.name);
88831
+ const absolutePath = join92(dir, entry.name);
88735
88832
  if (entry.isDirectory()) {
88736
88833
  await visit4(absolutePath, relPath);
88737
88834
  continue;
@@ -88746,7 +88843,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
88746
88843
  }
88747
88844
  async function staleSnapshotAssetPaths(input) {
88748
88845
  const existingPaths = await listSnapshotAssetFiles(input.materializedAtAbsPath, input.assetRoot);
88749
- return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) => join90(input.materializedAtAbsPath, path3));
88846
+ return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) => join92(input.materializedAtAbsPath, path3));
88750
88847
  }
88751
88848
  function normalizeLarkError(error, sourceName) {
88752
88849
  if (error instanceof ContextError)
@@ -88828,7 +88925,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
88828
88925
  });
88829
88926
  }
88830
88927
  const documentPath = normalizeSnapshotRelativePath(entry.module === undefined ? "index.md" : `${entry.module}.md`);
88831
- const title = entry.title ?? fetched.title ?? titleFromMarkdown2(normalized, documentPath);
88928
+ const title2 = entry.title ?? fetched.title ?? titleFromMarkdown2(normalized, documentPath);
88832
88929
  const locator = target.kind === "url" ? target.value : `${target.kind}:${target.value}`;
88833
88930
  const assetRoot = entry.module === undefined ? "assets" : `assets/${entry.module}`;
88834
88931
  const reportPath = normalizeSnapshotRelativePath(`${assetRoot}/capture-report.json`);
@@ -88850,13 +88947,13 @@ async function runCaptureLarkPhaseUnlocked(input) {
88850
88947
  const snapshotFiles = [{
88851
88948
  path: documentPath,
88852
88949
  bytes: normalized,
88853
- title,
88950
+ title: title2,
88854
88951
  locator
88855
88952
  }];
88856
88953
  const manifestPath = sourceManifestPath2(entry);
88857
- const manifestAbsPath = join90(input.projectRoot, manifestPath);
88954
+ const manifestAbsPath = join92(input.projectRoot, manifestPath);
88858
88955
  const materializedAt = entry.materializedAt;
88859
- const materializedAtAbsPath = join90(input.projectRoot, materializedAt);
88956
+ const materializedAtAbsPath = join92(input.projectRoot, materializedAt);
88860
88957
  const manifest = createDocumentSnapshotManifest({
88861
88958
  sourceType: "lark",
88862
88959
  sourceName: resolved.sourceName,
@@ -88890,13 +88987,13 @@ async function runCaptureLarkPhaseUnlocked(input) {
88890
88987
  }));
88891
88988
  try {
88892
88989
  const requestedWrites = [{
88893
- path: join90(materializedAtAbsPath, documentPath),
88990
+ path: join92(materializedAtAbsPath, documentPath),
88894
88991
  bytes: normalized
88895
88992
  }];
88896
88993
  for (const asset of assets) {
88897
88994
  if (asset.bytes !== undefined) {
88898
88995
  requestedWrites.push({
88899
- path: join90(materializedAtAbsPath, asset.entry.path),
88996
+ path: join92(materializedAtAbsPath, asset.entry.path),
88900
88997
  bytes: asset.bytes
88901
88998
  });
88902
88999
  }
@@ -88913,7 +89010,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
88913
89010
  currentPaths: new Set(assets.filter((asset) => asset.bytes !== undefined).map((asset) => asset.entry.path))
88914
89011
  });
88915
89012
  await applyAtomicFileBatch({
88916
- transactionRoot: join90(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
89013
+ transactionRoot: join92(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
88917
89014
  writes,
88918
89015
  removals
88919
89016
  });
@@ -88973,7 +89070,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
88973
89070
  },
88974
89071
  documents: [{
88975
89072
  path: documentPath,
88976
- title,
89073
+ title: title2,
88977
89074
  line_count: countLines3(normalized)
88978
89075
  }],
88979
89076
  assets: assets.map((asset) => ({
@@ -89016,7 +89113,7 @@ __export(exports_actionInputWorkspace, {
89016
89113
  assertActionInputWorkspace: () => assertActionInputWorkspace
89017
89114
  });
89018
89115
  import { realpathSync as realpathSync2 } from "node:fs";
89019
- import { dirname as dirname40, resolve as resolve31 } from "node:path";
89116
+ import { dirname as dirname39, resolve as resolve31 } from "node:path";
89020
89117
  function assertActionInputWorkspace(cwd, inputPath) {
89021
89118
  if (inputPath === "-")
89022
89119
  return;
@@ -89029,7 +89126,7 @@ function assertActionInputWorkspace(cwd, inputPath) {
89029
89126
  } catch {
89030
89127
  return;
89031
89128
  }
89032
- const owner = findContextProjectRoot(dirname40(file));
89129
+ const owner = findContextProjectRoot(dirname39(file));
89033
89130
  if (owner === null || realpathSync2(owner.projectRoot) === realpathSync2(current2.projectRoot))
89034
89131
  return;
89035
89132
  throw new ContextError(ExitCode.WorkspaceStateError, "The completion input belongs to a different Context workspace. Run the workflow CLI and read/write this task's .tmp files in the same intended workspace. No tasks were submitted.", {
@@ -89059,7 +89156,7 @@ __export(exports_articleRetirement, {
89059
89156
  retireArticles: () => retireArticles,
89060
89157
  articleRetirementSchema: () => articleRetirementSchema
89061
89158
  });
89062
- import { readFile as readFile76, readdir as readdir22 } from "node:fs/promises";
89159
+ import { readFile as readFile78, readdir as readdir23 } from "node:fs/promises";
89063
89160
  import { posix as posix10 } from "node:path";
89064
89161
  function rebuildInput(revision) {
89065
89162
  return JSON.stringify({ id: `retirement-${revision.slice(7)}`, operation: "rebuild", timing: "priority", targets: [] });
@@ -89080,7 +89177,7 @@ function invalid2(reason, message, details = {}) {
89080
89177
  }
89081
89178
  async function text9(root2, path3) {
89082
89179
  try {
89083
- return await readFile76(await safeProjectTarget(root2, path3), "utf8");
89180
+ return await readFile78(await safeProjectTarget(root2, path3), "utf8");
89084
89181
  } catch (error) {
89085
89182
  if (error.code === "ENOENT")
89086
89183
  return;
@@ -89118,7 +89215,7 @@ async function retireArticles(input) {
89118
89215
  invalid2("article-retirement-active-review", "Finish the active candidate/revision delivery before retiring approved pages.");
89119
89216
  }
89120
89217
  const before = await text9(input.projectRoot, "knowledge/structure.yaml");
89121
- const structure = before === undefined ? {} : import_yaml41.default.parse(before);
89218
+ const structure = before === undefined ? {} : import_yaml42.default.parse(before);
89122
89219
  const articles = validateArticleStructureEntries(structure.articles ?? []);
89123
89220
  const byPath = new Map(articles.map((article) => [article.path, article]));
89124
89221
  const selected = new Set(value.targets.map((target) => target.path));
@@ -89192,7 +89289,7 @@ async function retireArticles(input) {
89192
89289
  }
89193
89290
  async function inspectTemplates(directory2) {
89194
89291
  const absolute = await safeProjectTarget(input.projectRoot, directory2);
89195
- const entries2 = await readdir22(absolute, { withFileTypes: true }).catch((error) => {
89292
+ const entries2 = await readdir23(absolute, { withFileTypes: true }).catch((error) => {
89196
89293
  if (error.code === "ENOENT")
89197
89294
  return [];
89198
89295
  throw error;
@@ -89241,9 +89338,9 @@ async function retireArticles(input) {
89241
89338
  return [target.replacement ? { ...group, target: { artifact_ref: byPath.get(target.replacement).article_id } } : group];
89242
89339
  });
89243
89340
  const updated = updateKnowledgeMap(map4, { expected_revision: map4.revision, upsert, remove: [] });
89244
- change(KNOWLEDGE_MAP_PATH, await text9(input.projectRoot, KNOWLEDGE_MAP_PATH), import_yaml41.default.stringify(updated));
89341
+ change(KNOWLEDGE_MAP_PATH, await text9(input.projectRoot, KNOWLEDGE_MAP_PATH), import_yaml42.default.stringify(updated));
89245
89342
  }
89246
- change("knowledge/structure.yaml", before, import_yaml41.default.stringify({ ...structure, articles: articles.filter((article) => !selected.has(article.path)) }));
89343
+ change("knowledge/structure.yaml", before, import_yaml42.default.stringify({ ...structure, articles: articles.filter((article) => !selected.has(article.path)) }));
89247
89344
  targets.sort((a, b) => a.path.localeCompare(b.path));
89248
89345
  const revision = indexerProtocolDigest({ value, readDigests, targets });
89249
89346
  const preview = {
@@ -89287,7 +89384,7 @@ async function retireArticles(input) {
89287
89384
  };
89288
89385
  });
89289
89386
  }
89290
- var import_yaml41, articleRetirementSchema, next = "context status --format json";
89387
+ var import_yaml42, articleRetirementSchema, next = "context status --format json";
89291
89388
  var init_articleRetirement = __esm(() => {
89292
89389
  init_zod();
89293
89390
  init_src2();
@@ -89306,7 +89403,7 @@ var init_articleRetirement = __esm(() => {
89306
89403
  init_durableSingleFileTransaction();
89307
89404
  init_durableMultiFileTransaction();
89308
89405
  init_writeLock();
89309
- import_yaml41 = __toESM(require_dist(), 1);
89406
+ import_yaml42 = __toESM(require_dist(), 1);
89310
89407
  articleRetirementSchema = exports_external.object({
89311
89408
  reason: exports_external.string().trim().min(1),
89312
89409
  targets: exports_external.array(exports_external.object({ path: exports_external.string().min(1), replacement: exports_external.string().min(1).optional() }).strict()).min(1)
@@ -89326,14 +89423,14 @@ function schemaOutputFormat(value) {
89326
89423
  }
89327
89424
  function writeSchemaOutput(value, format2) {
89328
89425
  process.stdout.write(format2 === "json" ? `${JSON.stringify(value, null, 2)}
89329
- ` : import_yaml42.default.stringify(value));
89426
+ ` : import_yaml43.default.stringify(value));
89330
89427
  }
89331
- var import_yaml42;
89428
+ var import_yaml43;
89332
89429
  var init_schemaOutput = __esm(() => {
89333
89430
  init_errors3();
89334
89431
  init_cliFeedback();
89335
89432
  init_exitCode();
89336
- import_yaml42 = __toESM(require_dist(), 1);
89433
+ import_yaml43 = __toESM(require_dist(), 1);
89337
89434
  });
89338
89435
 
89339
89436
  // src/project/writeLockRecovery.ts
@@ -89342,9 +89439,9 @@ __export(exports_writeLockRecovery, {
89342
89439
  recoverWriterLock: () => recoverWriterLock,
89343
89440
  inspectWriterLock: () => inspectWriterLock
89344
89441
  });
89345
- import { lstat as lstat10, mkdir as mkdir33, readFile as readFile77, readdir as readdir23, rename as rename8, rmdir as rmdir2 } from "node:fs/promises";
89346
- import { join as join95 } from "node:path";
89347
- import { createHash as createHash28, randomUUID as randomUUID7 } from "node:crypto";
89442
+ import { lstat as lstat10, mkdir as mkdir33, readFile as readFile79, readdir as readdir24, rename as rename8, rmdir as rmdir2 } from "node:fs/promises";
89443
+ import { join as join97 } from "node:path";
89444
+ import { createHash as createHash29, randomUUID as randomUUID7 } from "node:crypto";
89348
89445
  async function inspectWriterLock(root2) {
89349
89446
  const path3 = await safeProjectTarget(root2, lockRelative);
89350
89447
  let stat10;
@@ -89358,7 +89455,7 @@ async function inspectWriterLock(root2) {
89358
89455
  if (!stat10.isDirectory() || stat10.isSymbolicLink())
89359
89456
  throw new Error("Writer lock must be a real directory.");
89360
89457
  const ownerPath = await safeProjectTarget(root2, `${lockRelative}/owner.json`);
89361
- const bytes = await readFile77(ownerPath, "utf8");
89458
+ const bytes = await readFile79(ownerPath, "utf8");
89362
89459
  const owner = JSON.parse(bytes);
89363
89460
  if (owner.protocol !== "context.project-write-lock.v1" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
89364
89461
  throw new Error("Writer lock owner is invalid; preserve the lock for diagnosis.");
@@ -89370,7 +89467,7 @@ async function inspectWriterLock(root2) {
89370
89467
  const code3 = error.code;
89371
89468
  processState = code3 === "ESRCH" ? "not-running" : code3 === "EPERM" ? "running" : "unknown";
89372
89469
  }
89373
- const digest6 = `sha256:${createHash28("sha256").update(`${stat10.dev}:${stat10.ino}:${stat10.birthtimeMs}:${bytes}`).digest("hex")}`;
89470
+ const digest6 = `sha256:${createHash29("sha256").update(`${stat10.dev}:${stat10.ino}:${stat10.birthtimeMs}:${bytes}`).digest("hex")}`;
89374
89471
  return { ...owner, process_state: processState, digest: digest6, path: lockRelative };
89375
89472
  }
89376
89473
  async function recoverWriterLock(input) {
@@ -89390,19 +89487,19 @@ async function recoverWriterLock(input) {
89390
89487
  };
89391
89488
  if (input.plan_digest !== before.digest)
89392
89489
  throw new Error("Writer lock changed; preview recovery again.");
89393
- const path3 = join95(input.projectRoot, lockRelative);
89394
- const guard = join95(path3, ".recovery");
89490
+ const path3 = join97(input.projectRoot, lockRelative);
89491
+ const guard = join97(path3, ".recovery");
89395
89492
  await mkdir33(guard);
89396
89493
  let archived = false;
89397
89494
  try {
89398
89495
  const current2 = await inspectWriterLock(input.projectRoot);
89399
89496
  if (current2?.digest !== before.digest || current2.process_state !== "not-running")
89400
89497
  throw new Error("Writer lock changed or owner resumed; keep the lock.");
89401
- const names = await readdir23(path3);
89498
+ const names = await readdir24(path3);
89402
89499
  if (names.some((name3) => name3 !== "owner.json" && name3 !== ".recovery"))
89403
89500
  throw new Error("Unexpected lock contents; preserve for diagnosis.");
89404
89501
  const archive = `.tmp/context-runtime/locks/recovered-write-${randomUUID7()}.lock`;
89405
- await rename8(path3, join95(input.projectRoot, archive));
89502
+ await rename8(path3, join97(input.projectRoot, archive));
89406
89503
  archived = true;
89407
89504
  return { action: "writer-lock-recovered", archived_lock: archive, next: "context task recover --format json" };
89408
89505
  } finally {
@@ -89426,12 +89523,12 @@ __export(exports_taskRecovery, {
89426
89523
  RECOVERY_COMMAND: () => RECOVERY_COMMAND
89427
89524
  });
89428
89525
  import { existsSync as existsSync26 } from "node:fs";
89429
- import { dirname as dirname41, join as join96 } from "node:path";
89430
- import { lstat as lstat11, readdir as readdir24, readFile as readFile78 } from "node:fs/promises";
89526
+ import { dirname as dirname40, join as join98 } from "node:path";
89527
+ import { lstat as lstat11, readdir as readdir25, readFile as readFile80 } from "node:fs/promises";
89431
89528
  async function recoveryText(root2, path3) {
89432
89529
  const target = await safeProjectTarget(root2, path3);
89433
89530
  try {
89434
- return await readFile78(target, "utf8");
89531
+ return await readFile80(target, "utf8");
89435
89532
  } catch (error) {
89436
89533
  if (error.code === "ENOENT")
89437
89534
  return;
@@ -89444,7 +89541,7 @@ async function recoveryJournals(root2) {
89444
89541
  await safeProjectTarget(root2, path3);
89445
89542
  let stat10;
89446
89543
  try {
89447
- stat10 = await lstat11(join96(root2, path3));
89544
+ stat10 = await lstat11(join98(root2, path3));
89448
89545
  } catch (error) {
89449
89546
  if (error.code === "ENOENT")
89450
89547
  return;
@@ -89455,7 +89552,7 @@ async function recoveryJournals(root2) {
89455
89552
  if (stat10.isDirectory()) {
89456
89553
  if (depth > 3)
89457
89554
  throw new TypeError("Unexpected transaction directory depth; preserve it for diagnosis.");
89458
- for (const name3 of (await readdir24(join96(root2, path3))).sort())
89555
+ for (const name3 of (await readdir25(join98(root2, path3))).sort())
89459
89556
  await visit4(`${path3}/${name3}`, depth + 1);
89460
89557
  } else if (stat10.isFile())
89461
89558
  entries2.push({ path: path3, digest: indexerProtocolDigest(await recoveryText(root2, path3)) });
@@ -89465,10 +89562,10 @@ async function recoveryJournals(root2) {
89465
89562
  }
89466
89563
  function recoveryResources() {
89467
89564
  try {
89468
- const root2 = dirname41(contextWorkflowProviderPath());
89565
+ const root2 = dirname40(contextWorkflowProviderPath());
89469
89566
  const resources = {
89470
- skill: join96(root2, "skills/recover-workspace/SKILL.md"),
89471
- issue_template: join96(root2, "resources/templates/recovery-issue.md")
89567
+ skill: join98(root2, "skills/recover-workspace/SKILL.md"),
89568
+ issue_template: join98(root2, "resources/templates/recovery-issue.md")
89472
89569
  };
89473
89570
  if (!Object.values(resources).every((path3) => existsSync26(path3)))
89474
89571
  throw new Error("Recovery resources are absent from this Provider.");
@@ -89553,8 +89650,8 @@ var exports_taskLocalSourceAdjustment = {};
89553
89650
  __export(exports_taskLocalSourceAdjustment, {
89554
89651
  adjustLocalRevisionSources: () => adjustLocalRevisionSources
89555
89652
  });
89556
- import { readFile as readFile79 } from "node:fs/promises";
89557
- import { join as join97 } from "node:path";
89653
+ import { readFile as readFile81 } from "node:fs/promises";
89654
+ import { join as join99 } from "node:path";
89558
89655
  async function adjustLocalRevisionSources(root2, input) {
89559
89656
  const { readMaintenance: readMaintenance2 } = await Promise.resolve().then(() => (init_maintenanceStorage(), exports_maintenanceStorage));
89560
89657
  if ((await readMaintenance2(root2)).active && await readProductionStage(root2))
@@ -89578,7 +89675,7 @@ async function adjustLocalRevisionSources(root2, input) {
89578
89675
  if (input.refresh && (!current2.refresh_sources || indexerProtocolDigest([...current2.refresh_sources].sort()) !== indexerProtocolDigest([...selected].sort()))) {
89579
89676
  throw new TypeError("No matching acquisition adjustment exists. Run task adjust without refresh first.");
89580
89677
  }
89581
- const raw = await readFile79(join97(root2, await revisionStoragePath(root2)), "utf8");
89678
+ const raw = await readFile81(join99(root2, await revisionStoragePath(root2)), "utf8");
89582
89679
  let next2;
89583
89680
  const discardIds = new Set;
89584
89681
  if (!input.refresh) {
@@ -89681,7 +89778,7 @@ ${input.instruction}` : revision.instruction
89681
89778
  content: content3
89682
89779
  }];
89683
89780
  if (discardIds.size > 0) {
89684
- const ledger = await readFile79(join97(root2, CANDIDATE_LEDGER_FILE), "utf8").catch((error) => {
89781
+ const ledger = await readFile81(join99(root2, CANDIDATE_LEDGER_FILE), "utf8").catch((error) => {
89685
89782
  if (error.code === "ENOENT")
89686
89783
  return;
89687
89784
  throw error;
@@ -89803,7 +89900,7 @@ var init_taskSourceAdjustment = __esm(() => {
89803
89900
  });
89804
89901
 
89805
89902
  // src/project/managedDocumentImport.ts
89806
- import { readFile as readFile85 } from "node:fs/promises";
89903
+ import { readFile as readFile87 } from "node:fs/promises";
89807
89904
  async function importManagedDocument(projectRoot, value) {
89808
89905
  const input = inputSchema.parse(value);
89809
89906
  if (input.type !== "sessions" && input.changes !== undefined)
@@ -89812,7 +89909,7 @@ async function importManagedDocument(projectRoot, value) {
89812
89909
  const path3 = await assertManagedDocumentPath(projectRoot, input.type, input.name);
89813
89910
  let previous3;
89814
89911
  try {
89815
- previous3 = await readFile85(path3, "utf8");
89912
+ previous3 = await readFile87(path3, "utf8");
89816
89913
  } catch (error) {
89817
89914
  if (!(error && typeof error === "object" && ("code" in error) && error.code === "ENOENT"))
89818
89915
  throw error;
@@ -89859,14 +89956,14 @@ var init_managedDocumentImport = __esm(() => {
89859
89956
  });
89860
89957
 
89861
89958
  // src/project/larkDocumentImport.ts
89862
- import { readFile as readFile86 } from "node:fs/promises";
89959
+ import { readFile as readFile88 } from "node:fs/promises";
89863
89960
  async function importLarkDocument(projectRoot, value) {
89864
89961
  const input = schema3.parse(value);
89865
89962
  const responsePages = [];
89866
89963
  for (const path3 of input.response_files) {
89867
89964
  assertActionInputWorkspace(projectRoot, path3);
89868
89965
  const { resolve: resolve8 } = await import("node:path");
89869
- responsePages.push(await readFile86(resolve8(projectRoot, path3), "utf8"));
89966
+ responsePages.push(await readFile88(resolve8(projectRoot, path3), "utf8"));
89870
89967
  }
89871
89968
  const mediaFiles = {};
89872
89969
  for (const [token, path3] of Object.entries(input.media_files ?? {})) {
@@ -89934,11 +90031,11 @@ var exports_managedDocumentRename = {};
89934
90031
  __export(exports_managedDocumentRename, {
89935
90032
  renameManagedDocument: () => renameManagedDocument
89936
90033
  });
89937
- import { readFile as readFile87 } from "node:fs/promises";
89938
- import { join as join105, posix as posix11 } from "node:path";
90034
+ import { readFile as readFile89 } from "node:fs/promises";
90035
+ import { join as join107, posix as posix11 } from "node:path";
89939
90036
  async function optionalText2(path3) {
89940
90037
  try {
89941
- return await readFile87(path3, "utf8");
90038
+ return await readFile89(path3, "utf8");
89942
90039
  } catch (error) {
89943
90040
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
89944
90041
  return;
@@ -90022,12 +90119,12 @@ async function renameManagedDocument(input) {
90022
90119
  if (path3.split("/").includes("..") || path3.startsWith("/"))
90023
90120
  throw new TypeError("Source references contain an unsafe knowledge path; repair it before renaming.");
90024
90121
  await safeProjectTarget(input.projectRoot, path3);
90025
- const before = await optionalText2(join105(input.projectRoot, path3));
90122
+ const before = await optionalText2(join107(input.projectRoot, path3));
90026
90123
  if (before === undefined)
90027
90124
  continue;
90028
90125
  let after;
90029
90126
  if (path3.endsWith(".yaml")) {
90030
- const parsed = import_yaml48.default.parse(before);
90127
+ const parsed = import_yaml49.default.parse(before);
90031
90128
  const updated = replace2(parsed);
90032
90129
  if (path3 === "knowledge/structure.yaml") {
90033
90130
  const rewritten = articles.map((article) => ({ ...article, sections: article.sections.map((section) => ({
@@ -90042,7 +90139,7 @@ async function renameManagedDocument(input) {
90042
90139
  }
90043
90140
  if (JSON.stringify(parsed) === JSON.stringify(updated))
90044
90141
  continue;
90045
- after = import_yaml48.default.stringify(updated);
90142
+ after = import_yaml49.default.stringify(updated);
90046
90143
  } else
90047
90144
  after = before.replace(reference2, nextRef);
90048
90145
  if (path3 === "src/index.ts")
@@ -90094,7 +90191,7 @@ async function renameManagedDocument(input) {
90094
90191
  };
90095
90192
  });
90096
90193
  }
90097
- var import_yaml48;
90194
+ var import_yaml49;
90098
90195
  var init_managedDocumentRename = __esm(() => {
90099
90196
  init_markdownLinks();
90100
90197
  init_src2();
@@ -90106,12 +90203,12 @@ var init_managedDocumentRename = __esm(() => {
90106
90203
  init_durableSingleFileTransaction();
90107
90204
  init_durableMultiFileTransaction();
90108
90205
  init_writeLock();
90109
- import_yaml48 = __toESM(require_dist(), 1);
90206
+ import_yaml49 = __toESM(require_dist(), 1);
90110
90207
  });
90111
90208
 
90112
90209
  // src/cli.ts
90113
90210
  import { existsSync as existsSync36, realpathSync as realpathSync3 } from "node:fs";
90114
- import { dirname as dirname48, join as join109 } from "node:path";
90211
+ import { dirname as dirname47, join as join111 } from "node:path";
90115
90212
  import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL3 } from "node:url";
90116
90213
 
90117
90214
  // ../../node_modules/.bun/commander@11.1.0/node_modules/commander/esm.mjs
@@ -90166,7 +90263,7 @@ init_dist();
90166
90263
  init_cliFeedback();
90167
90264
  init_errors3();
90168
90265
  init_exitCode();
90169
- import { join as join87 } from "node:path";
90266
+ import { join as join89 } from "node:path";
90170
90267
 
90171
90268
  // src/project/status.ts
90172
90269
  init_productionPlanning();
@@ -101001,200 +101098,21 @@ init_workflowFacts();
101001
101098
  init_workflowProvider();
101002
101099
  init_workflowTypes();
101003
101100
 
101004
- // src/project/reviewHtmlTranslations.ts
101005
- var REVIEW_UI_ZH = {
101006
- "Context Review": "Context 内容审核",
101007
- "All approved": "全部批准",
101008
- "Omit all": "全部不收录",
101009
- "Copy review results": "复制审核结果",
101010
- "Toggle theme": "切换明暗主题",
101011
- "Pages to review": "待审页面",
101012
- "candidate filters": "按审核状态筛选",
101013
- approved: "已批准",
101014
- omitted: "不收录",
101015
- pending: "待审核",
101016
- "Search pages or modules": "搜索页面或模块",
101017
- "Page content": "页面内容",
101018
- Previous: "上一页",
101019
- Next: "下一页",
101020
- "Next pending": "下一个待审",
101021
- "Review results": "审核结果",
101022
- "These choices take effect only after you send the review code back to the conversation. Each segment is at most 980 characters. Send every segment before applying.": "将审核码发回会话后,这些选择才会生效。每段不超过 980 个字符;如果有多段,请全部发送后再应用。",
101023
- "Previous segment": "上一段",
101024
- "Next segment": "下一段",
101025
- "review code": "审核码",
101026
- Close: "关闭",
101027
- Copy: "复制",
101028
- "{count} pages · {scope} · {pending} pending · {approved} approved · {rejected} omitted": "{count} 页 · {scope} · {pending} 待审核 · {approved} 已批准 · {rejected} 不收录",
101029
- "All collections": "全部分类",
101030
- "Set all {count} pages in {group} to {status}?": "将 {group} 的全部 {count} 页设为“{status}”?",
101031
- "Set all {count} pages to {status}?": "将全部 {count} 页设为“{status}”?",
101032
- "Choices changed. Copy the updated code before applying.": "选择已变更,请复制更新后的审核码再应用。",
101033
- "Select at least one page decision; pending pages remain for later review.": "请至少选择一页的审核结果;待审页面留待后续处理。",
101034
- "Segment {part}/{total} · {length}/980 characters": "第 {part}/{total} 段 · {length}/980 字符",
101035
- "No review code yet": "暂未生成审核码",
101036
- "{approved} approved · {rejected} not included · {pending} pending": "{approved} 页批准 · {rejected} 页不收录 · {pending} 页待审核",
101037
- "Pages not included": "不收录的页面",
101038
- "Open review results": "查看审核结果",
101039
- "{count} pending pages remain": "还有 {count} 页待审核",
101040
- Copied: "已复制",
101041
- "Copy manually from the textarea": "请从文本框中手动复制",
101042
- "No draft candidates.": "当前没有待审页面。",
101043
- "Nothing to review.": "没有需要审核的内容。",
101044
- "No candidates match the current filters.": "没有符合当前筛选条件的页面。",
101045
- "Adjust the candidate filters to continue reviewing.": "请调整筛选条件,继续审核。",
101046
- "{count} items": "{count} 页",
101047
- "evidence unavailable": "来源不可用",
101048
- "Source snapshot unavailable. Restore it before approving this candidate, or omit the page.": "来源快照不可用。请先恢复来源再批准,或选择不收录此页。",
101049
- "Source Markdown": "Markdown 原文",
101050
- "Source locations": "来源位置",
101051
- Approve: "批准",
101052
- Omit: "不收录",
101053
- "Need changes? Leave this page pending and ask the agent to repair it. Other reviewed pages can be approved.": "需要修改?本页保留待审,并让 Agent 返修。已审核的其他页面可以先批准。"
101054
- };
101055
-
101056
- // src/project/reviewCode.ts
101057
- function createReviewCodeCodec() {
101058
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
101059
- function checksum(text7) {
101060
- let crc = 4294967295;
101061
- for (let i2 = 0;i2 < text7.length; i2++) {
101062
- crc ^= text7.charCodeAt(i2);
101063
- for (let bit = 0;bit < 8; bit++)
101064
- crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
101065
- }
101066
- return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
101067
- }
101068
- function pack(bytes) {
101069
- let value = 0, bits = 0, result = "";
101070
- for (const byte of bytes) {
101071
- value = value << 8 | byte;
101072
- bits += 8;
101073
- while (bits >= 6) {
101074
- bits -= 6;
101075
- result += alphabet[value >>> bits & 63];
101076
- }
101077
- }
101078
- if (bits)
101079
- result += alphabet[value << 6 - bits & 63];
101080
- return result;
101081
- }
101082
- function unpack(text7) {
101083
- if (!/^[A-Za-z0-9_-]*$/.test(text7))
101084
- throw new Error("Invalid review code encoding");
101085
- let value = 0, bits = 0;
101086
- const bytes = [];
101087
- for (const char of text7) {
101088
- value = value << 6 | alphabet.indexOf(char);
101089
- bits += 6;
101090
- if (bits >= 8) {
101091
- bits -= 8;
101092
- bytes.push(value >>> bits & 255);
101093
- }
101094
- }
101095
- if (pack(bytes) !== text7)
101096
- throw new Error("Noncanonical review code encoding");
101097
- return bytes;
101098
- }
101099
- function hash3(text7) {
101100
- if (!/^[a-f0-9]{64}$/.test(text7))
101101
- throw new Error("Review requires a complete candidate digest");
101102
- return pack(Array.from({ length: 32 }, (_, i2) => Number.parseInt(text7.slice(i2 * 2, i2 * 2 + 2), 16)));
101103
- }
101104
- function unhash(text7) {
101105
- const bytes = unpack(text7);
101106
- if (bytes.length !== 32)
101107
- throw new Error("Invalid candidate digest");
101108
- return bytes.map((byte) => byte.toString(16).padStart(2, "0")).join("");
101109
- }
101110
- function encode(scope2, idsHash, contentHash2, statuses) {
101111
- if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !statuses.length || statuses.length > 1e6 || statuses.some((status) => status !== "approved" && status !== "rejected" && status !== "pending")) {
101112
- throw new Error("Select at least one review decision and keep undecided pages pending");
101113
- }
101114
- if (statuses.every((s) => s === "pending"))
101115
- throw new Error("Select at least one review decision");
101116
- const mode = statuses.every((s) => s === "approved") ? "a" : statuses.every((s) => s === "rejected") ? "r" : statuses.includes("pending") ? "p" : "b";
101117
- const bytes = Array(Math.ceil(statuses.length / (mode === "p" ? 4 : 8))).fill(0);
101118
- if (mode === "p")
101119
- statuses.forEach((s, i2) => {
101120
- bytes[i2 >> 2] |= (s === "approved" ? 1 : s === "rejected" ? 2 : 0) << i2 % 4 * 2;
101121
- });
101122
- if (mode === "b")
101123
- statuses.forEach((s, i2) => {
101124
- if (s === "rejected")
101125
- bytes[i2 >> 3] |= 1 << i2 % 8;
101126
- });
101127
- const body = ["CR1", scope2, statuses.length, hash3(idsHash), hash3(contentHash2), mode, mode === "b" || mode === "p" ? pack(bytes) : ""].join(".");
101128
- const code = `${body}.${checksum(body)}`;
101129
- if (code.length <= 980)
101130
- return [code];
101131
- const total = Math.ceil(code.length / 900);
101132
- if (total > 200)
101133
- throw new Error("Review decisions exceed 200 segments; use a smaller collection scope");
101134
- const identity = checksum(code);
101135
- return Array.from({ length: total }, (_, i2) => `CRP1.${identity}.${i2 + 1}.${total}.${code.slice(i2 * 900, (i2 + 1) * 900)}`);
101136
- }
101137
- function decode2(input) {
101138
- if (input.length > 250000)
101139
- throw new Error("Review code exceeds the supported size");
101140
- const lines = input.trim().split(/\s+/);
101141
- let code = lines[0];
101142
- if (code.startsWith("CRP1.")) {
101143
- const parts = new Map;
101144
- let identity = "", total = 0;
101145
- for (const line of lines) {
101146
- const match = /^CRP1\.([a-f0-9]{8})\.([1-9][0-9]*)\.([1-9][0-9]*)\.(.+)$/.exec(line);
101147
- if (!match || line.length > 980)
101148
- throw new Error("Invalid review code segment");
101149
- const index2 = Number(match[2]), count2 = Number(match[3]);
101150
- if (count2 > 200 || index2 > count2 || parts.has(index2) || total && (total !== count2 || identity !== match[1])) {
101151
- throw new Error("Duplicate or mixed review code segments");
101152
- }
101153
- identity = match[1];
101154
- total = count2;
101155
- parts.set(index2, match[4]);
101156
- }
101157
- if (parts.size !== total)
101158
- throw new Error(`Missing review code segments: received ${parts.size} of ${total}; collect all segments before applying`);
101159
- code = Array.from({ length: total }, (_, i2) => parts.get(i2 + 1)).join("");
101160
- if (checksum(code) !== identity)
101161
- throw new Error("Review code segment checksum mismatch");
101162
- } else if (lines.length !== 1 || code.length > 980)
101163
- throw new Error("Copy each complete review code segment unchanged");
101164
- const fields = code.split(".");
101165
- if (fields.length !== 8 || fields[0] !== "CR1" || checksum(fields.slice(0, 7).join(".")) !== fields[7]) {
101166
- throw new Error("Review code is damaged or unsupported; copy it again from the report");
101167
- }
101168
- const [, scope2, countText, ids, content3, mode, data2] = fields;
101169
- if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !/^[1-9][0-9]*$/.test(countText))
101170
- throw new Error("Invalid review scope");
101171
- const count = Number(countText);
101172
- if (count > 1e6 || !["a", "r", "b", "p"].includes(mode))
101173
- throw new Error("Invalid review decisions");
101174
- const bytes = unpack(data2);
101175
- const perByte = mode === "p" ? 4 : 8;
101176
- if (mode === "b" || mode === "p" ? bytes.length !== Math.ceil(count / perByte) || count % perByte !== 0 && bytes.at(-1) >>> count % perByte * (mode === "p" ? 2 : 1) !== 0 : data2 !== "") {
101177
- throw new Error("Invalid review decision bitmap");
101178
- }
101179
- const statuses = Array.from({ length: count }, (_, i2) => {
101180
- if (mode === "p") {
101181
- const value = bytes[i2 >> 2] >>> i2 % 4 * 2 & 3;
101182
- if (value === 3)
101183
- throw new Error("Invalid pending review bitmap");
101184
- return value === 1 ? "approved" : value === 2 ? "rejected" : "pending";
101185
- }
101186
- return mode === "r" || mode === "b" && bytes[i2 >> 3] & 1 << i2 % 8 ? "rejected" : "approved";
101187
- });
101188
- if (statuses.every((s) => s === "pending"))
101189
- throw new Error("Review contains no decisions");
101190
- return { scope: scope2, count, idsHash: unhash(ids), contentHash: unhash(content3), statuses };
101191
- }
101192
- return { encode, decode: decode2 };
101193
- }
101101
+ // src/project/reviewHtml.ts
101102
+ init_candidateLedger();
101103
+ import { mkdir as mkdir30, writeFile as writeFile24 } from "node:fs/promises";
101104
+ import { dirname as dirname37, isAbsolute as isAbsolute15, join as join87, resolve as resolve28 } from "node:path";
101194
101105
 
101195
- // src/project/reviewMarkdown.ts
101106
+ // src/project/reviewSiteModel.ts
101107
+ init_src2();
101196
101108
  init_unified();
101197
101109
  init_remark_parse();
101110
+ var import_yaml40 = __toESM(require_dist(), 1);
101111
+ import { readFile as readFile68 } from "node:fs/promises";
101112
+ import { join as join86 } from "node:path";
101113
+ import { execFile as execFile10 } from "node:child_process";
101114
+ import { promisify as promisify10 } from "node:util";
101115
+ import { createHash as createHash24 } from "node:crypto";
101198
101116
 
101199
101117
  // ../../node_modules/.bun/mdast-util-gfm-autolink-literal@2.0.1/node_modules/mdast-util-gfm-autolink-literal/lib/index.js
101200
101118
  init_development();
@@ -104152,7 +104070,45 @@ function remarkGfm(options) {
104152
104070
  fromMarkdownExtensions.push(gfmFromMarkdown());
104153
104071
  toMarkdownExtensions.push(gfmToMarkdown(settings));
104154
104072
  }
104073
+ // src/project/reviewSiteModel.ts
104074
+ init_workspace();
104075
+
104076
+ // src/project/reviewFeedback.ts
104077
+ import { readdir as readdir20, readFile as readFile67 } from "node:fs/promises";
104078
+ import { join as join85 } from "node:path";
104079
+ async function readPendingReviewFeedback(root2, candidates) {
104080
+ const directory = join85(root2, ".tmp/context-runtime/review-feedback");
104081
+ let files;
104082
+ try {
104083
+ files = await readdir20(directory);
104084
+ } catch (e) {
104085
+ if (e.code === "ENOENT")
104086
+ return [];
104087
+ throw e;
104088
+ }
104089
+ const pending = new Map(candidates.map((c) => [c.record.candidate_id, c.record.fingerprint]));
104090
+ const results = new Map;
104091
+ const receipts = [];
104092
+ for (const file of files.filter((f) => /^[a-f0-9]+\.json$/u.test(f)).sort()) {
104093
+ const receipt2 = JSON.parse(await readFile67(join85(directory, file), "utf8"));
104094
+ receipts.push(receipt2);
104095
+ }
104096
+ for (const receipt2 of receipts.sort((a, b) => a.created_at.localeCompare(b.created_at))) {
104097
+ for (const repair of receipt2.repairs)
104098
+ if (pending.get(repair.candidate_id) === repair.fingerprint)
104099
+ results.set(repair.candidate_id, { candidate_id: repair.candidate_id, path: repair.path, instruction: repair.instruction, command: repair.command });
104100
+ }
104101
+ return [...results.values()];
104102
+ }
104103
+
104104
+ // src/project/reviewSiteModel.ts
104105
+ init_knowledgeMap2();
104106
+ init_approvedKnowledgeMetadata();
104107
+ init_approvedFileRead();
104108
+
104155
104109
  // src/project/reviewMarkdown.ts
104110
+ init_unified();
104111
+ init_remark_parse();
104156
104112
  function escapeReviewHtml(value) {
104157
104113
  return value.replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;").replace(/"/gu, "&quot;");
104158
104114
  }
@@ -104234,172 +104190,286 @@ function renderReviewMarkdown(markdown, pageTitle) {
104234
104190
  return (pageTitle && !hasPageHeading ? `<h1>${escapeReviewHtml(pageTitle)}</h1>` : "") + render(tree);
104235
104191
  }
104236
104192
 
104237
- // src/project/reviewHtml.ts
104238
- init_candidateLedger();
104239
- import { mkdir as mkdir30, writeFile as writeFile24 } from "node:fs/promises";
104240
- import { dirname as dirname38, isAbsolute as isAbsolute15, join as join85, resolve as resolve28 } from "node:path";
104241
-
104242
- // src/project/reviewHtmlPresentation.ts
104243
- import { dirname as dirname37 } from "node:path";
104244
- function candidatePreview(candidate) {
104245
- if (candidate.snapshot === undefined) {
104246
- return "Evidence unavailable. Restore the committed snapshot before approving this candidate.";
104247
- }
104248
- const markdown = candidate.snapshot.markdown ?? candidate.record.review.summary;
104249
- return markdown.trim().slice(0, 1200);
104250
- }
104251
- function candidateGroupKey(candidate) {
104252
- const path3 = candidate.record.indexer_candidate.sections.flatMap((section) => section.references)[0]?.locator.path;
104253
- const group = path3 === undefined ? candidate.record.kind : dirname37(path3).split("\\").join("/");
104254
- return `${candidate.record.module} / ${group === "." ? candidate.record.kind : group}`;
104255
- }
104256
- function candidateGroupLabel(candidate) {
104257
- const path3 = candidate.record.indexer_candidate.sections.flatMap((section) => section.references)[0]?.locator.path;
104258
- const group = path3 === undefined ? candidate.record.kind : dirname37(path3).split("\\").join("/");
104259
- return group === "." ? candidate.record.module : `${candidate.record.module} · ${group}`;
104260
- }
104261
-
104262
- // src/project/reviewHtmlStyles.ts
104263
- var REVIEW_HTML_STYLES = `
104264
- :root, [data-theme="light"] {
104265
- color-scheme: light;
104266
- --sb-bg-base:#f1f1f2; --sb-bg-surface:#f7f7f7; --sb-bg-elevated:#fdfdfd; --sb-bg-hover:#ebebec; --sb-bg-active:#e1e1e4;
104267
- --sb-text-primary:#181818; --sb-text-secondary:#535353; --sb-text-muted:#717171;
104268
- --sb-border-default:#e5e5e5; --sb-border-strong:#d5d5d5;
104269
- --sb-brand:#ef6f2e; --sb-brand-text:rgba(255,255,255,.85); --sb-brand-border:rgba(255,180,130,.65);
104270
- --sb-success:#059669; --sb-error:#dc2626; --sb-warning:#d97706;
104271
- }
104272
- [data-theme="dark"] {
104273
- color-scheme: dark;
104274
- --sb-bg-base:#181818; --sb-bg-surface:#202020; --sb-bg-elevated:#252525; --sb-bg-hover:#2b2b2b; --sb-bg-active:#343434;
104275
- --sb-text-primary:#fbfbfb; --sb-text-secondary:#b5b5b5; --sb-text-muted:#8a8a8a;
104276
- --sb-border-default:#2f2f2f; --sb-border-strong:#3a3a3a;
104277
- --sb-brand:#ef6f2e; --sb-brand-text:rgba(255,255,255,.85); --sb-brand-border:rgba(255,180,130,.65);
104278
- --sb-success:#3ecf8e; --sb-error:#ef4444; --sb-warning:#f59e0b;
104279
- }
104280
- * { box-sizing: border-box; }
104281
- html, body { height:100%; }
104282
- body { margin:0; overflow:hidden; background:var(--sb-bg-base); color:var(--sb-text-primary); font:14px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
104283
- button, textarea { font: inherit; }
104284
- .shell { height:100vh; padding:22px; display:grid; grid-template-rows:auto minmax(0, 1fr); gap:18px; overflow:hidden; }
104285
- .header { display:grid; grid-template-columns:minmax(0, 1fr) auto; align-items:center; gap:16px; }
104286
- .titleline { min-width:0; display:flex; flex-direction:column; align-items:flex-start; gap:4px; }
104287
- #count-state { max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
104288
- .toolbar { display:flex; align-items:center; gap:10px; justify-content:flex-end; white-space:nowrap; }
104289
- .bulk-actions { display:flex; align-items:center; gap:8px; }
104290
- h1 { margin:0; font-size:28px; font-weight:500; letter-spacing:0; }
104291
- .subtle { color:var(--sb-text-secondary); }
104292
- .layout { min-height:0; display:grid; grid-template-columns:minmax(260px, 375px) minmax(0, 1fr); gap:22px; align-items:stretch; }
104293
- .panel { min-height:0; background:var(--sb-bg-surface); border:1px solid var(--sb-border-default); border-radius:8px; overflow:hidden; }
104294
- .candidate-panel { display:grid; grid-template-rows:auto auto minmax(0, 1fr); }
104295
- .detail-panel { display:grid; grid-template-rows:auto minmax(0, 1fr); }
104296
- .panel-head { padding:14px 16px; border-bottom:1px solid var(--sb-border-default); color:var(--sb-text-secondary); font-size:12px; text-transform:uppercase; }
104297
- .candidate-head { display:flex; flex-direction:column; align-items:flex-start; justify-content:space-between; gap:10px; }
104298
- .filters { display:flex; align-items:center; gap:8px; text-transform:none; font-size:12px; }
104299
- .filter { display:inline-flex; align-items:center; gap:5px; cursor:pointer; color:var(--sb-text-secondary); }
104300
- .filter input { margin:0; accent-color:var(--sb-brand); }
104301
- #list { min-height:0; overflow:auto; }
104302
- .candidate-group { border-bottom:1px solid var(--sb-border-default); }
104303
- .candidate-group-title { width:100%; display:grid; grid-template-columns:minmax(0, 1fr) auto; align-items:start; gap:10px; padding:12px 12px 8px 16px; color:var(--sb-text-secondary); font-size:12px; font-weight:600; overflow-wrap:anywhere; cursor:pointer; user-select:none; }
104304
- .group-label { min-width:0; display:grid; grid-template-columns:auto minmax(0, 1fr); align-items:center; gap:7px; }
104305
- .group-key { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
104306
- .group-count { grid-column:2; justify-self:start; margin-top:4px; color:var(--sb-text-muted); font-weight:400; }
104307
- .group-actions { display:grid; justify-items:end; gap:5px; flex:0 0 auto; }
104308
- .group-btn { border:1px solid var(--sb-border-default); border-radius:999px; padding:2px 8px; background:transparent; color:var(--sb-text-secondary); cursor:pointer; font-size:12px; }
104309
- .group-btn:hover { border-color:var(--sb-brand); color:var(--sb-text-primary); }
104310
- .candidate { width:100%; display:block; text-align:left; padding:10px 16px 12px 30px; border:0; color:var(--sb-text-primary); background:transparent; cursor:pointer; }
104311
- .candidate + .candidate { border-top:1px solid var(--sb-border-default); }
104312
- .candidate:hover, .candidate.active { background:var(--sb-bg-hover); }
104313
- .candidate-title { min-width:0; display:flex; align-items:center; justify-content:space-between; gap:10px; }
104314
- .candidate-title-text { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
104315
- .candidate-tags { display:flex; align-items:center; gap:6px; flex:0 0 auto; }
104316
- .candidate-summary { display:-webkit-box; margin-top:5px; overflow:hidden; color:var(--sb-text-muted); font-size:12px; line-height:1.45; -webkit-box-orient:vertical; -webkit-line-clamp:2; }
104317
- .meta { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; color:var(--sb-text-muted); font-size:12px; }
104318
- .badge { border:1px solid var(--sb-border-strong); border-radius:999px; padding:1px 7px; color:var(--sb-text-secondary); background:transparent; }
104319
- .badge.pending { border-color:rgba(245,158,11,.55); color:var(--sb-warning); }
104320
- .badge.approved { border-color:rgba(62,207,142,.55); color:var(--sb-success); }
104321
- .badge.rejected { border-color:rgba(239,68,68,.55); color:var(--sb-error); }
104322
- .badge.warning { border-color:rgba(245,158,11,.55); color:var(--sb-warning); }
104323
- .notice { padding:10px 12px; border:1px solid var(--sb-border-strong); border-radius:8px; background:var(--sb-bg-elevated); color:var(--sb-text-secondary); }
104324
- .notice.warning { border-color:rgba(245,158,11,.55); color:var(--sb-warning); }
104325
- .section-list { display:grid; gap:12px; }
104326
- .section-card { display:grid; gap:10px; padding:12px; border:1px solid var(--sb-border-default); border-radius:8px; background:var(--sb-bg-elevated); }
104327
- .section-card.empty { color:var(--sb-text-secondary); }
104328
- .section-header { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; }
104329
- .section-title { display:flex; align-items:center; gap:8px; flex-wrap:wrap; min-width:0; }
104330
- .section-title code { overflow-wrap:anywhere; }
104331
- .section-summary { color:var(--sb-text-secondary); }
104332
- .section-source-refs { display:grid; gap:5px; margin-top:8px; color:var(--sb-text-muted); font-size:12px; }
104333
- .section-source-refs code { overflow-wrap:anywhere; }
104334
- .section-body { max-height:360px; overflow:auto; }
104335
- .evidence-details, .technical-details { border:1px solid var(--sb-border-default); border-radius:8px; background:var(--sb-bg-surface); }
104336
- .evidence-details > summary, .technical-details > summary { cursor:pointer; padding:8px 10px; color:var(--sb-text-secondary); font-size:12px; }
104337
- .technical-details > pre { border:0; border-top:1px solid var(--sb-border-default); border-radius:0 0 8px 8px; background:transparent; }
104338
- .page-location { min-width:0; color:var(--sb-text-muted); font-size:12px; overflow-wrap:anywhere; }
104339
- .repair-hint { margin:0; font-size:12px; color:var(--sb-text-muted); }
104340
- #modal-copy-state { min-height:1.5em; font-size:12px; }
104341
- .technical-content { display:grid; gap:10px; padding:0 10px 10px; border-top:1px solid var(--sb-border-default); }
104342
- .technical-content .meta { margin-top:10px; }
104343
- .technical-content code { overflow-wrap:anywhere; }
104344
- .detail { min-height:0; overflow:auto; padding:18px; display:grid; align-content:start; gap:16px; }
104345
- .detail-titlebar { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }
104346
- .detail-heading { min-width:0; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
104347
- .detail h2 { margin:0; font-size:22px; font-weight:500; letter-spacing:0; overflow-wrap:anywhere; }
104348
- code, pre, textarea { font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
104349
- pre { margin:0; white-space:pre-wrap; overflow:visible; padding:14px; border-radius:8px; border:1px solid var(--sb-border-default); background:var(--sb-bg-elevated); color:var(--sb-text-primary); }
104350
- .actions { display:flex; gap:10px; flex-wrap:wrap; }
104351
- .btn { border:1px solid var(--sb-border-strong); background:transparent; color:var(--sb-text-primary); padding:8px 12px; border-radius:6px; cursor:pointer; }
104352
- .btn:disabled { opacity:.55; cursor:not-allowed; }
104353
- .language-btn { width:76px; flex-shrink:0; }
104354
- .icon-btn { width:40px; height:40px; display:inline-grid; place-items:center; padding:0; font-size:18px; }
104355
- .btn:hover { border-color:var(--sb-brand); }
104356
- .btn.primary { background:var(--sb-brand); border-color:var(--sb-brand-border); color:var(--sb-brand-text); }
104357
- .btn.brand { background:var(--sb-brand); border-color:var(--sb-brand-border); color:var(--sb-brand-text); }
104358
- .btn.brand.ready { filter:brightness(1.04); }
104359
- .btn.reject.active { border-color:var(--sb-error); color:var(--sb-error); }
104360
- .btn.approve.active { border-color:var(--sb-success); color:var(--sb-success); }
104361
- textarea { width:100%; min-height:120px; resize:vertical; border:1px solid var(--sb-border-strong); border-radius:8px; padding:12px; background:var(--sb-bg-elevated); color:var(--sb-text-primary); }
104362
- .empty { padding:28px; color:var(--sb-text-secondary); border:1px dashed var(--sb-border-strong); border-radius:8px; }
104363
- .modal { position:fixed; inset:0; z-index:10; display:grid; place-items:center; padding:24px; background:rgba(0,0,0,.45); }
104364
- .modal.hidden { display:none; }
104365
- .modal-card { width:min(920px, 100%); max-height:min(720px, 92vh); display:grid; grid-template-rows:auto minmax(0, 1fr) auto; gap:14px; border:1px solid var(--sb-border-default); border-radius:8px; background:var(--sb-bg-surface); padding:18px; box-shadow:0 16px 44px rgba(0,0,0,.22); }
104366
- .modal-card h2 { margin:0; font-size:20px; font-weight:500; letter-spacing:0; }
104367
- .modal-body { min-height:0; overflow:auto; }
104368
- .modal-actions { display:flex; justify-content:flex-end; align-items:center; gap:10px; }
104369
- #search { margin:10px; min-width:0; padding:9px; border:1px solid var(--sb-border-default); border-radius:6px; background:var(--sb-bg-elevated); color:var(--sb-text-primary); }
104370
- .reader-navigation, .code-navigation { display:flex; justify-content:space-between; align-items:center; gap:8px; flex-wrap:wrap; }
104371
- .code-navigation { margin:12px 0; }
104372
- .code-navigation[hidden] { display:none; }
104373
- .reader-body { min-width:0; line-height:1.7; overflow-wrap:anywhere; }
104374
- .reader-body h1 { font-size:26px; }
104375
- .reader-body h2, .reader-body h3 { margin:24px 0 10px; }
104376
- .reader-body details { margin:12px 0; }
104377
- .reader-body summary { cursor:pointer; font-weight:500; }
104378
- .reader-body p { margin:10px 0; }
104379
- .reader-body a { color:var(--sb-brand); }
104380
- .reader-body blockquote { margin-left:0; padding-left:16px; border-left:3px solid var(--sb-border-strong); }
104381
- .reader-body pre { overflow:auto; white-space:pre; }
104382
- .reader-body code { font-size:.9em; }
104383
- .table-scroll { max-width:100%; overflow:auto; }
104384
- .reader-body table { border-collapse:collapse; width:100%; }
104385
- .reader-body td, .reader-body th { text-align:left; vertical-align:top; padding:8px 10px; border:1px solid var(--sb-border-default); }
104386
- .reader-body th { background:var(--sb-bg-hover); }
104387
- #decision-summary ul { max-height:160px; overflow:auto; }
104388
- @media (max-width: 820px) {
104389
- .header { grid-template-columns:minmax(0, 1fr); }
104390
- .toolbar { justify-content:flex-start; flex-wrap:wrap; }
104391
- body { overflow:auto; }
104392
- .shell { height:auto; min-height:100vh; overflow:visible; padding:18px; }
104393
- .layout { grid-template-columns:1fr; }
104394
- .candidate-panel { max-height:42vh; }
104395
- .detail-panel { display:block; overflow:visible; }
104396
- .detail { overflow:visible; }
104397
- .detail-titlebar { display:grid; }
104193
+ // src/project/reviewSiteModel.ts
104194
+ var hash3 = (s) => createHash24("sha256").update(s).digest("hex");
104195
+ var body = (s) => s.replace(/^---\r?\n[\s\S]*?\r?\n---\s*/u, "").replace(/<!--[^]*?-->/gu, "").trim();
104196
+ var title = (s, fallback) => {
104197
+ const front = /^---\r?\n([\s\S]*?)\r?\n---/u.exec(s);
104198
+ const value = front ? import_yaml40.parse(front[1])?.title : undefined;
104199
+ return typeof value === "string" ? value : /^#\s+(.+)$/mu.exec(s)?.[1] ?? fallback;
104200
+ };
104201
+ async function optional2(path3) {
104202
+ try {
104203
+ return await readFile68(path3, "utf8");
104204
+ } catch (e) {
104205
+ if (e.code === "ENOENT")
104206
+ return;
104207
+ throw e;
104208
+ }
104209
+ }
104210
+ async function reviewSiteBaselineHash(root2, reviewedPaths) {
104211
+ const files = await readApprovedMarkdownFiles(root2);
104212
+ return hash3(JSON.stringify([
104213
+ await optional2(join86(root2, "src/knowledge-map.yaml")) ?? null,
104214
+ files.map((f) => [f.relPath, reviewedPaths.includes(f.relPath) ? hash3(f.content) : title(f.content, f.relPath)]).sort((a, b) => a[0].localeCompare(b[0]))
104215
+ ]));
104216
+ }
104217
+ function reviewBodyDiff(previous3, next) {
104218
+ function blocks(markdown) {
104219
+ const text9 = body(markdown);
104220
+ const tree = unified().use(remarkParse).use(remarkGfm).parse(text9);
104221
+ const definitions = tree.children.filter((node3) => node3.type === "definition").map((node3) => text9.slice(node3.position?.start.offset, node3.position?.end.offset)).join(`
104222
+ `);
104223
+ return tree.children.filter((node3) => node3.type !== "definition").map((node3) => renderReviewMarkdown(text9.slice(node3.position?.start.offset, node3.position?.end.offset) + `
104224
+
104225
+ ` + definitions));
104226
+ }
104227
+ const before = blocks(previous3), after = blocks(next);
104228
+ const remaining = [...before];
104229
+ let omitted = false;
104230
+ const result = after.map((block) => {
104231
+ const exact = remaining.indexOf(block);
104232
+ if (exact >= 0) {
104233
+ remaining.splice(exact, 1);
104234
+ if (/^<h[1-6]>/u.test(block)) {
104235
+ omitted = false;
104236
+ return block;
104237
+ }
104238
+ if (omitted)
104239
+ return "";
104240
+ omitted = true;
104241
+ return '<div class="unchanged" data-label="unchanged">Unchanged content omitted.</div>';
104242
+ }
104243
+ omitted = false;
104244
+ return `<section class="changed"><span class="badge modify">Modify</span>${block}</section>`;
104245
+ });
104246
+ if (remaining.length)
104247
+ result.push(`<section class="changed"><span class="badge modify">Modify</span><details open><summary data-label="removed">Previous or removed content</summary>${remaining.join(`
104248
+ `)}</details></section>`);
104249
+ return result.join(`
104250
+ `);
104251
+ }
104252
+ async function collectReviewSiteModel(root2, candidates) {
104253
+ const pendingFeedback = new Map((await readPendingReviewFeedback(root2, candidates)).map((r) => [r.candidate_id, r.instruction]));
104254
+ const current2 = await readKnowledgeMap(root2);
104255
+ const metadata = await readApprovedKnowledgeMetadataIndex(root2);
104256
+ const articles = metadata.structure?.articles ?? [];
104257
+ const files = await readApprovedMarkdownFiles(root2);
104258
+ const byPath = new Map(files.map((f) => [f.relPath, f.content]));
104259
+ const pages = files.map((f) => ({
104260
+ id: articles.find((a) => a.path === f.relPath)?.article_id ?? f.relPath,
104261
+ path: f.relPath,
104262
+ title: title(f.content, f.relPath),
104263
+ change: "unchanged",
104264
+ html: "",
104265
+ sources: []
104266
+ }));
104267
+ for (const { record: r } of candidates) {
104268
+ const found = pages.find((p) => p.id === r.article_id || p.path === (r.approved_revision?.previous_path ?? r.path));
104269
+ const old = found && byPath.get(found.path);
104270
+ const next = r.indexer_candidate.sections.map((s) => s.markdown).join(`
104271
+
104272
+ `);
104273
+ const page = {
104274
+ id: r.article_id,
104275
+ candidate_id: r.candidate_id,
104276
+ title: r.review.title,
104277
+ path: r.path,
104278
+ ...pendingFeedback.has(r.candidate_id) ? { revisionInstruction: pendingFeedback.get(r.candidate_id) } : {},
104279
+ ...found && found.path !== r.path ? { previousPath: found.path } : {},
104280
+ change: found ? "modify" : "new",
104281
+ html: old === undefined ? renderReviewMarkdown(next.replace(/^# [^\n]+\n*/u, "")) : reviewBodyDiff(body(old).replace(/^# [^\n]+\n*/u, ""), next.replace(/^# [^\n]+\n*/u, "")),
104282
+ sources: [...r.source_refs, ...r.indexer_candidate.sections.flatMap((s) => s.references.map((ref2) => JSON.stringify(ref2)))]
104283
+ };
104284
+ if (found)
104285
+ pages.splice(pages.indexOf(found), 1, page);
104286
+ else
104287
+ pages.push(page);
104288
+ }
104289
+ let baseline = current2, navigationBaseline = files.length ? "current" : "empty";
104290
+ if (files.length) {
104291
+ try {
104292
+ const { stdout } = await promisify10(execFile10)("git", ["show", "HEAD:./src/knowledge-map.yaml"], { cwd: root2, timeout: 5000, maxBuffer: 4 * 1024 * 1024 });
104293
+ baseline = validateKnowledgeMap(import_yaml40.parse(stdout));
104294
+ navigationBaseline = "git-head";
104295
+ } catch {}
104296
+ }
104297
+ const nodes = (current2?.entries ?? []).map((n) => {
104298
+ const old = baseline?.entries.find((b) => b.key === n.key);
104299
+ const page = n.target && pages.find((p) => p.id === n.target?.artifact_ref);
104300
+ return {
104301
+ key: n.key,
104302
+ parent: n.parent ?? null,
104303
+ title: n.title,
104304
+ order: n.order ?? 0,
104305
+ ...page ? { page: page.id } : {},
104306
+ ...old && old.title !== n.title ? { oldTitle: old.title } : {},
104307
+ change: navigationBaseline === "empty" || !old ? "new" : JSON.stringify(old) !== JSON.stringify(n) ? "modify" : "unchanged"
104308
+ };
104309
+ });
104310
+ for (const n of baseline?.entries ?? [])
104311
+ if (!nodes.some((v) => v.key === n.key))
104312
+ nodes.push({
104313
+ key: n.key,
104314
+ parent: n.parent ?? null,
104315
+ title: n.title,
104316
+ order: n.order ?? 0,
104317
+ removed: true,
104318
+ change: "modify"
104319
+ });
104320
+ const unplaced = pages.filter((p) => p.candidate_id && !nodes.some((n) => n.page === p.id));
104321
+ if (unplaced.length) {
104322
+ nodes.push({ key: "review-unplaced", parent: null, title: "Unplaced articles", order: Number.MAX_SAFE_INTEGER, change: "new" });
104323
+ for (const [i2, p] of unplaced.entries())
104324
+ nodes.push({ key: `review-page-${p.id}`, parent: "review-unplaced", title: p.title, order: i2, page: p.id, change: p.change });
104325
+ }
104326
+ const pkgText = await optional2(join86(root2, "package.json"));
104327
+ const pkg = pkgText ? JSON.parse(pkgText) : {};
104328
+ const project = await optional2(join86(root2, "src/index.ts")) === undefined ? undefined : await loadContextProjectModule(root2);
104329
+ const siteTitle = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site?.title ? [p.site.title] : [])[0];
104330
+ return { 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 };
104331
+ }
104332
+ var reviewHtmlJson = (value) => JSON.stringify(value).replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
104333
+
104334
+ // src/project/reviewFeedbackCode.ts
104335
+ function createReviewFeedbackCodec() {
104336
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
104337
+ function checksum(text9) {
104338
+ let crc = 4294967295;
104339
+ for (let i2 = 0;i2 < text9.length; i2++) {
104340
+ crc ^= text9.charCodeAt(i2);
104341
+ for (let j = 0;j < 8; j++)
104342
+ crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
104343
+ }
104344
+ return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
104345
+ }
104346
+ function validate(value) {
104347
+ 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))
104348
+ throw new Error("Invalid review feedback scope or decisions");
104349
+ const seen = new Set;
104350
+ for (const repair of value.repairs) {
104351
+ 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)
104352
+ throw new Error("Invalid or conflicting revision instruction");
104353
+ seen.add(repair.index);
104354
+ }
104355
+ if (value.statuses.filter((s) => s === "revised").length !== seen.size)
104356
+ throw new Error("Missing revision instruction");
104357
+ return value;
104358
+ }
104359
+ function encode(value) {
104360
+ validate(value);
104361
+ const bytes = new TextEncoder().encode(JSON.stringify({ scope: value.scope, idsHash: value.idsHash, contentHash: value.contentHash, baselineHash: value.baselineHash, statuses: value.statuses }));
104362
+ let bits = 0, carry = 0, data2 = "";
104363
+ for (const byte of bytes) {
104364
+ carry = carry << 8 | byte;
104365
+ bits += 8;
104366
+ while (bits >= 6) {
104367
+ bits -= 6;
104368
+ data2 += alphabet[carry >>> bits & 63];
104369
+ }
104370
+ }
104371
+ if (bits)
104372
+ data2 += alphabet[carry << 6 - bits & 63];
104373
+ const body2 = `CR2.${data2}`;
104374
+ const repairs = value.repairs.map((r) => JSON.stringify([r.index, r.instruction])).join(`
104375
+ `);
104376
+ const trailer = repairs ? `
104377
+ ${repairs}` : "";
104378
+ const code3 = `${body2}.${checksum(body2 + trailer)}${trailer}`;
104379
+ if (code3.length > 250000)
104380
+ throw new Error("Review feedback is too large; shorten instructions or review a smaller scope");
104381
+ return code3;
104382
+ }
104383
+ function decode2(raw) {
104384
+ const code3 = raw.trim();
104385
+ if (code3.length > 250000)
104386
+ throw new Error("Review feedback exceeds supported size");
104387
+ const [header, ...lines] = code3.split(`
104388
+ `);
104389
+ const trailer = lines.length ? `
104390
+ ${lines.join(`
104391
+ `)}` : "";
104392
+ const match = /^CR2\.([A-Za-z0-9_-]+)\.([a-f0-9]{8})$/.exec(header);
104393
+ if (!match || checksum(`CR2.${match[1]}` + trailer) !== match[2])
104394
+ throw new Error("Review feedback is damaged or incomplete; copy the full code again");
104395
+ let bits = 0, carry = 0;
104396
+ const bytes = [];
104397
+ for (const char of match[1]) {
104398
+ carry = carry << 6 | alphabet.indexOf(char);
104399
+ bits += 6;
104400
+ if (bits >= 8) {
104401
+ bits -= 8;
104402
+ bytes.push(carry >>> bits & 255);
104403
+ }
104404
+ }
104405
+ const headerValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes)));
104406
+ const value = validate({ ...headerValue, repairs: lines.map((line) => {
104407
+ const row = JSON.parse(line);
104408
+ if (!Array.isArray(row) || row.length !== 2)
104409
+ throw new Error("Invalid revision instruction line");
104410
+ return { index: row[0], instruction: row[1] };
104411
+ }) });
104412
+ if (encode(value) !== code3)
104413
+ throw new Error("Noncanonical review feedback encoding");
104414
+ return value;
104398
104415
  }
104416
+ return { encode, decode: decode2 };
104417
+ }
104418
+
104419
+ // src/project/reviewSiteClient.ts
104420
+ var REVIEW_SITE_CLIENT = String.raw`
104421
+ const $=id=>document.getElementById(id);
104422
+ let language=(navigator.languages?.[0]||navigator.language||'en').startsWith('zh')?'zh':'en';
104423
+ const words={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 content'],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.']};
104424
+ const t=k=>words[k]?.[language==='zh'?0:1]||k;
104425
+ const escape=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
104426
+ const decisions=new Map(),notes=new Map(),expanded=new Set();let selected=null,active=null;
104427
+ const candidates=DATA.pages.filter(p=>p.candidate_id),ordered=[...candidates].sort((a,b)=>a.candidate_id<b.candidate_id?-1:1);
104428
+ for(const p of candidates)if(p.revisionInstruction){decisions.set(p.candidate_id,'revised');notes.set(p.candidate_id,p.revisionInstruction)}
104429
+ const badge=change=>change==='unchanged'?'':'<span class="badge '+change+'">'+(change==='new'?'New':change==='modify'?'Modify':t(change))+'</span>';
104430
+ const state=p=>decisions.get(p.candidate_id)||'pending';
104431
+ 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)])}
104432
+ function nodeChange(n){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 changes.includes('modify')?'modify':changes.includes('new')?'new':'unchanged'}
104433
+ function nodeTitle(n){return n.key==='review-unplaced'?t('unplaced'):n.title}
104434
+ function nodeLabel(n){return (n.removed?'<del>':'')+escape(nodeTitle(n))+(n.removed?'</del>':'')+(n.oldTitle?'<small class="old-title"> ← '+escape(n.oldTitle)+'</small>':'')}
104435
+ 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}
104436
+ 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>'}
104437
+ 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 '+change+(selected===n.page?' selected':'')+'" style="padding-left:'+(16+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>'}
104438
+ 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}
104439
+ 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}
104440
+ function showPage(id){selected=id;active=rootFor(id)||active;render()}
104441
+ function localizeBody(){document.querySelectorAll('[data-label]').forEach(el=>{el.textContent=t(el.dataset.label)})}
104442
+ 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>'}
104443
+ 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')+'</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('')+'<h2>'+t('files')+'</h2>'+workspaceTree()}
104444
+ 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'):''}}
104445
+ function render(){document.body.classList.toggle('home',selected===null);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:'<div class="unchanged">'+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()}
104446
+ 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()}
104447
+ function setAllDecision(value){for(const p of candidates)if(!decisions.has(p.candidate_id))decisions.set(p.candidate_id,value);render()}
104448
+ 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)}]:[])})}
104449
+ 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()}
104450
+ let guideTimer;function dismissGuide(){clearInterval(guideTimer);$('copy-guide').hidden=true}
104451
+ 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':'中文'}
104452
+ 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()}}});
104453
+ $('home').onclick=()=>{selected=null;active=null;render();window.scrollTo(0,0)};
104454
+ $('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'):'')};
104455
+ 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)};
104456
+ const addedRoots=DATA.nodes.filter(n=>!n.parent&&n.change==='new'&&!n.removed&&n.key!=='review-unplaced');
104457
+ let bulkTimer,bulkDeadline=0;
104458
+ 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}
104459
+ 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)}
104460
+ $('bulk-ack').onchange=updateBulkConfirmation;
104461
+ $('bulk-dialog').onclose=()=>clearInterval(bulkTimer);
104462
+ $('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');$('language').onclick=()=>{language=language==='zh'?'en':'zh';labels();render()};
104463
+ 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);
104464
+ `;
104465
+
104466
+ // src/project/reviewSiteStyles.ts
104467
+ var REVIEW_SITE_STYLES = String.raw`
104468
+ :root{--blue:#2563eb;--text:#161e2e;--muted:#646b7c;--line:#e7e9ef;--bg:#fff;--side:#f8f9fc;--red:#c63848;--amber:#a4660a}*{box-sizing:border-box}body{margin:0;color:var(--text);background:var(--bg);font:14px/1.75 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",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{box-shadow:inset 0 -2px var(--blue)}.new,nav .new{color:var(--red)}.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{background:#fff0f1}.badge.modify,.badge.revised{background:#fff4dd;color:var(--amber)}.badge.approved{background:#e9f7ef;color:#258451}.badge.rejected{background:#f4edf0;color:#9e4458}.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:white;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;min-height:32px;padding:6px 16px;font-size:13px;font-weight:450;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--muted)}.node.new{color:var(--red)}.node.modify{color:var(--amber)}.node.selected{background:#2563eb14}.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;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 #00bec8;background:#2563eb14;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)}.unchanged{border:1px dashed var(--line);border-radius:8px;padding:24px;text-align:center;color:var(--muted);font-size:13px}.changed{position:relative;border-left:3px solid #e8b051;background:#fffaf0;color:#8f5607;padding:24px 18px 12px;margin:20px 0}.changed>.badge{position:absolute;right:12px;top:6px}.changed details{color:var(--muted)}.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}.dark .changed{background:#302919;color:#f2cf85}@media(min-width:1600px){main,.home main{padding-left:52px}}@media(max-width:1250px){#home{width:190px}nav button{padding:0 8px}.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}}
104399
104469
  `;
104400
104470
 
104401
104471
  // src/project/reviewHtml.ts
104402
- var REVIEW_HTML_ROOT = join85(".tmp", "context-runtime", "review");
104472
+ var REVIEW_HTML_ROOT = join87(".tmp", "context-runtime", "review");
104403
104473
  async function collectReviewCandidates(projectRoot, collection) {
104404
104474
  const rows = await readCandidateRecords(projectRoot);
104405
104475
  const draftRows = rows.filter((row) => row.collection === collection && row.status === "draft");
@@ -104416,446 +104486,23 @@ async function collectAllReviewCandidates(projectRoot) {
104416
104486
  snapshot: await readReviewCandidateSnapshot(projectRoot, record4)
104417
104487
  })));
104418
104488
  }
104419
- function escapeHtml(value) {
104420
- return value.replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;").replace(/"/gu, "&quot;");
104421
- }
104422
- function jsonForScript(value) {
104423
- return JSON.stringify(value).replace(/</gu, "\\u003c").replace(/>/gu, "\\u003e").replace(/&/gu, "\\u0026").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
104424
- }
104425
- function renderReviewHtml(candidates, reviewScope) {
104426
- const candidateIds = candidates.map(({ record: record4 }) => record4.candidate_id);
104427
- const visibleCandidateIds = [...candidateIds].sort();
104489
+ function renderReviewHtml(candidates, reviewScope, model) {
104428
104490
  const scope2 = {
104429
- kind: reviewScope === "all" ? "all" : "collection",
104430
- ...reviewScope !== "all" ? { collection: reviewScope } : {},
104431
- count: visibleCandidateIds.length,
104432
- ids_sha256: candidateIdsHash(visibleCandidateIds),
104433
- candidates_sha256: candidateSetHash(candidates.map(({ record: record4 }) => record4))
104434
- };
104435
- const candidateData = candidates.map(({ record: record4, snapshot }) => {
104436
- return {
104437
- candidate_id: record4.candidate_id,
104438
- collection: record4.collection,
104439
- path: record4.path,
104440
- article_id: record4.article_id,
104441
- module: record4.module,
104442
- status: record4.status,
104443
- kind: record4.kind,
104444
- visibility: record4.visibility,
104445
- source_refs: record4.source_refs,
104446
- source_paths: record4.indexer_candidate === undefined ? [] : [...new Set(record4.indexer_candidate.sections.flatMap((section) => section.references).map((binding) => binding.locator.path))].sort(),
104447
- sections: record4.indexer_candidate.sections.map((section) => ({
104448
- id: section.section_key,
104449
- kind: record4.kind,
104450
- summary: section.section_key,
104451
- body: section.markdown,
104452
- source_refs: [...new Set(section.references.map((reference2) => `${reference2.source_ref}/${reference2.locator.path}#L${reference2.locator.start_line}-L${reference2.locator.end_line}`))].sort(),
104453
- content_mode: "authored"
104454
- })),
104455
- group_key: reviewScope === "all" ? `${record4.collection} / ${candidateGroupKey({ record: record4, snapshot })}` : candidateGroupKey({ record: record4, snapshot }),
104456
- group_label: reviewScope === "all" ? `${record4.collection} · ${candidateGroupLabel({ record: record4, snapshot })}` : candidateGroupLabel({ record: record4, snapshot }),
104457
- review: record4.review,
104458
- display_summary: record4.review.behavior_summary ?? record4.review.summary,
104459
- preview: candidatePreview({ record: record4, snapshot }),
104460
- rendered_markdown: renderReviewMarkdown(record4.indexer_candidate.sections.map((section) => section.markdown).join(`
104461
-
104462
- `), record4.review.title),
104463
- snapshot_ready: snapshot !== undefined
104464
- };
104465
- });
104466
- return `<!doctype html>
104467
- <html lang="en" data-theme="light">
104468
- <head>
104469
- <meta charset="utf-8">
104470
- <meta name="viewport" content="width=device-width, initial-scale=1">
104471
- <title>Context Review - ${escapeHtml(reviewScope)}</title>
104472
- <style>${REVIEW_HTML_STYLES}</style>
104473
- </head>
104474
- <body>
104475
- <main class="shell">
104476
- <header class="header">
104477
- <div class="titleline">
104478
- <h1 id="review-heading">Context Review</h1>
104479
- <div class="subtle" id="count-state">${candidates.length} draft candidate(s) in ${escapeHtml(reviewScope)} · ${candidates.length} pending 0 approved 0 omitted</div>
104480
- </div>
104481
- <div class="toolbar">
104482
- <span class="bulk-actions">
104483
- <button class="btn" id="all-approved">All approved</button>
104484
- <button class="btn" id="all-rejected">Omit all</button>
104485
- </span>
104486
- <button class="btn brand" id="payload-open">Copy review results</button>
104487
- <button class="btn language-btn" id="language" type="button" aria-label="Switch to Chinese">中文</button>
104488
- <button class="btn icon-btn" id="theme" title="Toggle theme" aria-label="Toggle theme">\uD83C\uDF19</button>
104489
- </div>
104490
- </header>
104491
- <section class="layout">
104492
- <aside class="panel candidate-panel">
104493
- <div class="panel-head candidate-head">
104494
- <span id="pages-label">Pages to review</span>
104495
- <div class="filters" id="filters" aria-label="candidate filters">
104496
- <label class="filter"><input id="filter-approved" type="checkbox" checked> <span id="label-approved">approved</span></label>
104497
- <label class="filter"><input id="filter-rejected" type="checkbox" checked> <span id="label-rejected">omitted</span></label>
104498
- <label class="filter"><input id="filter-pending" type="checkbox" checked> <span id="label-pending">pending</span></label>
104499
- </div>
104500
- </div>
104501
- <input id="search" type="search" placeholder="Search pages or modules" aria-label="Search pages or modules">
104502
- <div id="list"></div>
104503
- </aside>
104504
- <section class="panel detail-panel">
104505
- <div class="panel-head reader-navigation"><span id="content-label">Page content</span><div><button class="btn" id="previous-page">Previous</button> <button class="btn" id="next-page">Next</button> <button class="btn" id="next-pending">Next pending</button></div></div>
104506
- <div class="detail" id="detail"></div>
104507
- </section>
104508
- </section>
104509
- <div class="modal hidden" id="payload-modal" role="dialog" aria-modal="true" aria-labelledby="payload-title">
104510
- <section class="modal-card">
104511
- <div>
104512
- <h2 id="payload-title">Review results</h2>
104513
- <div class="subtle" id="code-help">These choices take effect only after you send the review code back to the conversation. Each segment is at most 980 characters. Send every segment before applying.</div>
104514
- </div>
104515
- <div class="modal-body">
104516
- <div id="decision-summary"></div>
104517
- <div class="code-navigation" id="code-navigation" hidden><button class="btn" id="code-previous">Previous segment</button><span id="code-length"></span><button class="btn" id="code-next">Next segment</button></div>
104518
- <textarea id="payload" aria-label="review code" readonly></textarea>
104519
- </div>
104520
- <div class="modal-actions">
104521
- <span class="subtle" id="modal-copy-state"></span>
104522
- <button class="btn" id="payload-close">Close</button>
104523
- <button class="btn primary" id="payload-copy">Copy</button>
104524
- </div>
104525
- </section>
104526
- </div>
104527
- </main>
104528
- <script>
104529
- const translations = ${jsonForScript(REVIEW_UI_ZH)};
104530
- let language = /^zh(?:-|$)/i.test(navigator.languages?.[0] || navigator.language || "en") ? "zh-CN" : "en";
104531
- function t(message, values = {}) {
104532
- const text = language === "zh-CN" ? translations[message] || message : message;
104533
- return text.replace(/\\{(\\w+)\\}/g, (_, key) => String(values[key] ?? ""));
104534
- }
104535
- const candidates = ${jsonForScript(candidateData)};
104536
- const payloadScope = ${jsonForScript(scope2)};
104537
- const reviewCode = (${createReviewCodeCodec.toString()})();
104538
- const payloadScopeLabel = ${jsonForScript(reviewScope)};
104539
- const decisions = new Map(candidates.map((item) => [item.candidate_id, "pending"]));
104540
- const list = document.getElementById("list");
104541
- const detail = document.getElementById("detail");
104542
- const countState = document.getElementById("count-state");
104543
- const filterApproved = document.getElementById("filter-approved");
104544
- const filterRejected = document.getElementById("filter-rejected");
104545
- const filterPending = document.getElementById("filter-pending");
104546
- const theme = document.getElementById("theme");
104547
- const allApproved = document.getElementById("all-approved");
104548
- const allRejected = document.getElementById("all-rejected");
104549
- const payloadOpen = document.getElementById("payload-open");
104550
- const payloadModal = document.getElementById("payload-modal");
104551
- const payloadClose = document.getElementById("payload-close");
104552
- const payloadCopy = document.getElementById("payload-copy");
104553
- const payloadBox = document.getElementById("payload");
104554
- const modalCopyState = document.getElementById("modal-copy-state");
104555
- const search = document.getElementById("search");
104556
- const decisionSummary = document.getElementById("decision-summary");
104557
- const codeLength = document.getElementById("code-length");
104558
- let codePart = 0;
104559
- let selected = candidates[0]?.candidate_id;
104560
- const collapsedGroups = new Set();
104561
-
104562
- function html(value) {
104563
- return String(value).replace(/[&<>"]/g, (char) => ({ "&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;" }[char]));
104564
- }
104565
- function decisionCounts() {
104566
- const counts = { pending: 0, approved: 0, rejected: 0 };
104567
- for (const status of decisions.values()) counts[status] += 1;
104568
- return counts;
104569
- }
104570
- function updateCountState() {
104571
- const counts = decisionCounts();
104572
- countState.textContent = t("{count} pages · {scope} · {pending} pending · {approved} approved · {rejected} omitted",
104573
- { count: candidates.length, scope: payloadScopeLabel === "all" ? t("All collections") : payloadScopeLabel, ...counts });
104574
- }
104575
- function visibleCandidates() {
104576
- const showApproved = filterApproved.checked;
104577
- const showRejected = filterRejected.checked;
104578
- const showPending = filterPending.checked;
104579
- const query = search.value.trim().toLowerCase();
104580
- return candidates.filter((item) => {
104581
- if (query && !(item.review.title + " " + item.module + " " + item.source_paths.join(" ")).toLowerCase().includes(query)) return false;
104582
- const status = decisions.get(item.candidate_id);
104583
- return (status === "pending" && showPending) ||
104584
- (status === "approved" && showApproved) ||
104585
- (status === "rejected" && showRejected);
104586
- });
104587
- }
104588
- function groupCandidates(items) {
104589
- const groups = [];
104590
- const byGroup = new Map();
104591
- for (const item of items) {
104592
- const key = item.group_key || item.module || "ungrouped";
104593
- let group = byGroup.get(key);
104594
- if (!group) {
104595
- group = { key, label: item.group_label || key, items: [] };
104596
- byGroup.set(key, group);
104597
- groups.push(group);
104598
- }
104599
- group.items.push(item);
104600
- }
104601
- return groups;
104602
- }
104603
- function statusBadge(status) {
104604
- const label = t(status === "rejected" ? "omitted" : status);
104605
- return '<span class="badge ' + html(status) + '">' + html(label) + '</span>';
104606
- }
104607
- function toggleGroup(groupKey) {
104608
- if (collapsedGroups.has(groupKey)) collapsedGroups.delete(groupKey);
104609
- else collapsedGroups.add(groupKey);
104610
- render();
104611
- }
104612
- function setGroupDecision(groupKey, status) {
104613
- const items = candidates.filter((item) => (item.group_key || item.module || "ungrouped") === groupKey);
104614
- if (items.length === 0) return;
104615
- const label = t(status === "rejected" ? "omitted" : status);
104616
- if (!window.confirm(t("Set all {count} pages in {group} to {status}?", { count: items.length, group: groupKey, status: label }))) return;
104617
- for (const item of items) {
104618
- if (status === "approved" && !item.snapshot_ready) continue;
104619
- decisions.set(item.candidate_id, status);
104620
- }
104621
- codePart = 0;
104622
- modalCopyState.textContent = t("Choices changed. Copy the updated code before applying.");
104623
- render();
104624
- updatePayloadBox();
104625
- }
104626
- function setAllDecision(status) {
104627
- if (candidates.length === 0) return;
104628
- const label = t(status === "rejected" ? "omitted" : status);
104629
- if (!window.confirm(t("Set all {count} pages to {status}?", { count: candidates.length, status: label }))) return;
104630
- for (const item of candidates) {
104631
- if (status === "approved" && !item.snapshot_ready) continue;
104632
- decisions.set(item.candidate_id, status);
104633
- }
104634
- codePart = 0;
104635
- modalCopyState.textContent = t("Choices changed. Copy the updated code before applying.");
104636
- render();
104637
- updatePayloadBox();
104638
- }
104639
- function payloadParts() {
104640
- if (decisionCounts().pending === candidates.length) return [];
104641
- const ordered = [...candidates].sort((a, b) => a.candidate_id < b.candidate_id ? -1 : a.candidate_id > b.candidate_id ? 1 : 0);
104642
- return reviewCode.encode(payloadScopeLabel, payloadScope.ids_sha256, payloadScope.candidates_sha256,
104643
- ordered.map((item) => decisions.get(item.candidate_id)));
104644
- }
104645
- function payloadText() { return payloadParts()[codePart] || t("Select at least one page decision; pending pages remain for later review."); }
104646
- function setDecision(id, status) {
104647
- const item = candidates.find((candidate) => candidate.candidate_id === id);
104648
- if (status === "approved" && item && !item.snapshot_ready) return;
104649
- decisions.set(id, status);
104650
- codePart = 0;
104651
- modalCopyState.textContent = t("Choices changed. Copy the updated code before applying.");
104652
- render();
104653
- updatePayloadBox();
104654
- }
104655
- function updatePayloadBox() {
104656
- const parts = payloadParts();
104657
- codePart = Math.min(codePart, Math.max(0, parts.length - 1));
104658
- payloadBox.value = payloadText();
104659
- document.getElementById("code-navigation").hidden = parts.length <= 1;
104660
- codeLength.textContent = parts.length ? t("Segment {part}/{total} · {length}/980 characters", { part: codePart + 1, total: parts.length, length: payloadBox.value.length }) : t("No review code yet");
104661
- document.getElementById("code-previous").disabled = codePart === 0;
104662
- document.getElementById("code-next").disabled = codePart + 1 >= parts.length;
104663
- const counts = decisionCounts();
104664
- const ready = counts.approved + counts.rejected > 0;
104665
- decisionSummary.innerHTML = '<p>' + html(t('{approved} approved · {rejected} not included · {pending} pending', counts)) + '</p>' +
104666
- (counts.rejected ? '<details><summary>' + html(t('Pages not included')) + '</summary><ul>' + candidates.filter((item) => decisions.get(item.candidate_id) === "rejected").map((item) => '<li>' + html(item.review.title) + '</li>').join('') + '</ul></details>' : '');
104667
- payloadCopy.disabled = !ready;
104668
- payloadOpen.classList.toggle("ready", ready);
104669
- payloadOpen.title = ready
104670
- ? t("Open review results")
104671
- : t("{count} pending pages remain", { count: counts.pending });
104672
- }
104673
- function openPayloadModal() {
104674
- updatePayloadBox();
104675
- payloadModal.classList.remove("hidden");
104676
- payloadBox.focus();
104677
- payloadBox.select();
104678
- modalCopyState.textContent = "";
104679
- }
104680
- function closePayloadModal() {
104681
- payloadModal.classList.add("hidden");
104682
- }
104683
- async function copyPayload() {
104684
- const counts = decisionCounts();
104685
- if (counts.approved + counts.rejected === 0) {
104686
- updatePayloadBox();
104687
- const message = t("Select at least one page decision; pending pages remain for later review.");
104688
- modalCopyState.textContent = message;
104689
- return;
104690
- }
104691
- const text = payloadText();
104692
- payloadBox.value = text;
104693
- try {
104694
- if (!navigator.clipboard) throw new Error("clipboard unavailable");
104695
- await navigator.clipboard.writeText(text);
104696
- modalCopyState.textContent = t("Copied");
104697
- } catch {
104698
- payloadBox.focus();
104699
- payloadBox.select();
104700
- modalCopyState.textContent = t("Copy manually from the textarea");
104701
- }
104702
- }
104703
- function render() {
104704
- updateCountState();
104705
- if (candidates.length === 0) {
104706
- list.innerHTML = '<div class="empty">' + html(t('No draft candidates.')) + '</div>';
104707
- detail.innerHTML = '<div class="empty">' + html(t('Nothing to review.')) + '</div>';
104708
- return;
104709
- }
104710
- const visible = visibleCandidates();
104711
- if (visible.length === 0) {
104712
- list.innerHTML = '<div class="empty">' + html(t('No candidates match the current filters.')) + '</div>';
104713
- detail.innerHTML = '<div class="empty">' + html(t('Adjust the candidate filters to continue reviewing.')) + '</div>';
104714
- return;
104715
- }
104716
- if (!visible.some((item) => item.candidate_id === selected)) selected = visible[0].candidate_id;
104717
- list.innerHTML = groupCandidates(visible).map((group) =>
104718
- {
104719
- const collapsed = collapsedGroups.has(group.key);
104720
- return '<section class="candidate-group">' +
104721
- '<div class="candidate-group-title" data-group-toggle="' + html(group.key) + '">' +
104722
- '<span class="group-label"><span>' + (collapsed ? "▸" : "▾") + '</span><span class="group-key">' + html(group.label) + '</span><span class="group-count">' + group.items.length + ' items</span></span>' +
104723
- '<span class="group-actions">' +
104724
- '<button class="group-btn" data-group-status="approved" data-group="' + html(group.key) + '">' + html(t('All approved')) + '</button>' +
104725
- '<button class="group-btn" data-group-status="rejected" data-group="' + html(group.key) + '">' + html(t('Omit all')) + '</button>' +
104726
- '</span>' +
104727
- '</div>' +
104728
- (collapsed ? "" : group.items.map((item) => {
104729
- const active = item.candidate_id === selected ? " active" : "";
104730
- const status = decisions.get(item.candidate_id);
104731
- return '<button class="candidate' + active + '" data-id="' + html(item.candidate_id) + '">' +
104732
- '<div class="candidate-title">' +
104733
- '<span class="candidate-title-text">' + html(item.review.title) + '</span>' +
104734
- '<span class="candidate-tags"><span class="badge">' + html(item.collection || "unknown") + '</span>' + (!item.snapshot_ready ? '<span class="badge warning">' + html(t('evidence unavailable')) + '</span>' : '') + statusBadge(status) + '</span>' +
104735
- '</div>' +
104736
- '<div class="candidate-summary">' + html(item.display_summary || item.review.summary) + '</div>' +
104737
- '</button>';
104738
- }).join("")) +
104739
- '</section>';
104740
- }
104741
- ).join("");
104742
- const item = visible.find((candidate) => candidate.candidate_id === selected) ?? visible[0];
104743
- selected = item.candidate_id;
104744
- const status = decisions.get(item.candidate_id);
104745
- const evidenceWarning = item.snapshot_ready ? "" :
104746
- '<div class="notice warning">' + html(t('Source snapshot unavailable. Restore it before approving this candidate, or omit the page.')) + '</div>';
104747
- const sectionDetails = '<article class="reader-body">' + item.rendered_markdown + '</article>';
104748
- const previewBlock = '<details class="technical-details"><summary>' + html(t('Source Markdown')) + '</summary><pre>' +
104749
- html(item.sections.map((section) => section.body).join("\\n\\n")) + '</pre></details>';
104750
- const displayedSources = [...new Set([...item.source_paths, ...item.source_refs, ...item.sections.flatMap((section) => section.source_refs)])];
104751
- const sourceLocationsBlock = displayedSources.length === 0 ? "" :
104752
- '<details class="technical-details">' +
104753
- '<summary>' + html(t('Source locations')) + '(' + displayedSources.length + ')</summary>' +
104754
- '<div class="technical-content"><div class="section-source-refs">' +
104755
- displayedSources.map((ref) => '<code>' + html(ref) + '</code>').join('') +
104756
- '</div></div>' +
104757
- '</details>';
104758
- detail.innerHTML = '<div class="detail-titlebar">' +
104759
- '<div class="page-location">' + html(item.path) + '</div>' +
104760
- '<div class="actions">' +
104761
- '<button class="btn approve ' + (status === "approved" ? "active" : "") + '" data-action="approved" ' + (!item.snapshot_ready ? "disabled" : "") + '>' + html(t('Approve')) + '</button>' +
104762
- '<button class="btn reject ' + (status === "rejected" ? "active" : "") + '" data-action="rejected">' + html(t('Omit')) + '</button>' +
104763
- '</div>' +
104764
- '</div>' +
104765
- evidenceWarning +
104766
- sectionDetails +
104767
- previewBlock +
104768
- '<p class="repair-hint">' + html(t('Need changes? Leave this page pending and ask the agent to repair it. Other reviewed pages can be approved.')) + '</p>' +
104769
- sourceLocationsBlock;
104770
- document.querySelectorAll("[data-id]").forEach((button) => button.addEventListener("click", () => { selected = button.dataset.id; render(); }));
104771
- document.querySelectorAll("[data-action]").forEach((button) => button.addEventListener("click", () => setDecision(item.candidate_id, button.dataset.action)));
104772
- document.querySelectorAll("[data-group-toggle]").forEach((header) => header.addEventListener("click", () => toggleGroup(header.dataset.groupToggle)));
104773
- document.querySelectorAll("[data-group-status]").forEach((button) => button.addEventListener("click", (event) => {
104774
- event.stopPropagation();
104775
- setGroupDecision(button.dataset.group, button.dataset.groupStatus);
104776
- }));
104777
- }
104778
- function navigatePage(direction, pendingOnly = false) {
104779
- const items = visibleCandidates();
104780
- const current = items.findIndex((item) => item.candidate_id === selected);
104781
- for (let step = 1; step <= items.length; step++) {
104782
- const item = items[(current + direction * step + items.length * 2) % items.length];
104783
- if (!pendingOnly || decisions.get(item.candidate_id) === "pending") { selected = item.candidate_id; render(); detail.scrollTop = 0; return; }
104784
- }
104785
- }
104786
- search.addEventListener("input", render);
104787
- document.getElementById("previous-page").addEventListener("click", () => navigatePage(-1));
104788
- document.getElementById("next-page").addEventListener("click", () => navigatePage(1));
104789
- document.getElementById("next-pending").addEventListener("click", () => navigatePage(1, true));
104790
- document.getElementById("code-previous").addEventListener("click", () => { codePart = Math.max(0, codePart - 1); updatePayloadBox(); });
104791
- document.getElementById("code-next").addEventListener("click", () => { codePart++; updatePayloadBox(); });
104792
- function applyLanguage() {
104793
- document.documentElement.lang = language;
104794
- document.title = t("Context Review") + " - " + payloadScopeLabel;
104795
- const labels = {
104796
- "review-heading": "Context Review", "all-approved": "All approved", "all-rejected": "Omit all",
104797
- "payload-open": "Copy review results", "pages-label": "Pages to review", "label-approved": "approved",
104798
- "label-rejected": "omitted", "label-pending": "pending", "content-label": "Page content",
104799
- "previous-page": "Previous", "next-page": "Next", "next-pending": "Next pending",
104800
- "payload-title": "Review results", "code-previous": "Previous segment", "code-next": "Next segment",
104801
- "payload-close": "Close", "payload-copy": "Copy",
104802
- "code-help": "These choices take effect only after you send the review code back to the conversation. Each segment is at most 980 characters. Send every segment before applying.",
104803
- };
104804
- for (const [id, message] of Object.entries(labels)) document.getElementById(id).textContent = t(message);
104805
- search.placeholder = t("Search pages or modules");
104806
- search.setAttribute("aria-label", t("Search pages or modules"));
104807
- document.getElementById("filters").setAttribute("aria-label", t("candidate filters"));
104808
- payloadBox.setAttribute("aria-label", t("review code"));
104809
- theme.title = t("Toggle theme");
104810
- theme.setAttribute("aria-label", t("Toggle theme"));
104811
- const button = document.getElementById("language");
104812
- button.textContent = language === "zh-CN" ? "English" : "中文";
104813
- button.setAttribute("aria-label", language === "zh-CN" ? "Switch to English" : "切换为中文");
104814
- modalCopyState.textContent = "";
104815
- const scrollTop = detail.scrollTop;
104816
- render();
104817
- detail.scrollTop = scrollTop;
104818
- updatePayloadBox();
104819
- }
104820
- function toggleLanguage() {
104821
- language = language === "zh-CN" ? "en" : "zh-CN";
104822
- applyLanguage();
104823
- }
104824
- document.getElementById("language").addEventListener("click", toggleLanguage);
104825
- function effectiveTheme() {
104826
- return document.documentElement.dataset.theme || "light";
104827
- }
104828
- function updateThemeIcon() {
104829
- theme.textContent = effectiveTheme() === "dark" ? "☀️" : "\uD83C\uDF19";
104830
- }
104831
- theme.addEventListener("click", () => {
104832
- document.documentElement.dataset.theme = effectiveTheme() === "dark" ? "light" : "dark";
104833
- updateThemeIcon();
104834
- });
104835
- updateThemeIcon();
104836
- allApproved.addEventListener("click", () => setAllDecision("approved"));
104837
- allRejected.addEventListener("click", () => setAllDecision("rejected"));
104838
- filterApproved.addEventListener("change", render);
104839
- filterRejected.addEventListener("change", render);
104840
- filterPending.addEventListener("change", render);
104841
- payloadOpen.addEventListener("click", openPayloadModal);
104842
- payloadClose.addEventListener("click", closePayloadModal);
104843
- payloadCopy.addEventListener("click", copyPayload);
104844
- payloadModal.addEventListener("click", (event) => {
104845
- if (event.target === payloadModal) closePayloadModal();
104846
- });
104847
- document.addEventListener("keydown", (event) => {
104848
- if (event.key === "Escape" && !payloadModal.classList.contains("hidden")) closePayloadModal();
104849
- });
104850
- applyLanguage();
104851
- </script>
104852
- </body>
104853
- </html>
104854
- `;
104491
+ label: reviewScope,
104492
+ ids_sha256: candidateIdsHash(candidates.map((c) => c.record.candidate_id).sort()),
104493
+ candidates_sha256: candidateSetHash(candidates.map((c) => c.record))
104494
+ };
104495
+ 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}</style></head><body>
104496
+ <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>
104497
+ <div class="layout"><aside id="tree"></aside><main><article id="article"></article></main></div>
104498
+ <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>
104499
+ <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>
104500
+ <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>
104501
+ <script>const DATA=${reviewHtmlJson(model)};const SCOPE=${reviewHtmlJson(scope2)};const feedbackCodec=(${createReviewFeedbackCodec.toString()})();${REVIEW_SITE_CLIENT}</script></body></html>`;
104855
104502
  }
104856
104503
  function resolveOutputPath(projectRoot, outPath, reviewScope) {
104857
104504
  if (outPath === undefined)
104858
- return join85(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
104505
+ return join87(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
104859
104506
  return isAbsolute15(outPath) ? outPath : resolve28(projectRoot, outPath);
104860
104507
  }
104861
104508
  async function writeReviewHtml(input) {
@@ -104865,8 +104512,8 @@ async function writeReviewHtml(input) {
104865
104512
  }
104866
104513
  const candidates = reviewScope === "all" ? await collectAllReviewCandidates(input.projectRoot) : await collectReviewCandidates(input.projectRoot, reviewScope);
104867
104514
  const outPath = resolveOutputPath(input.projectRoot, input.out, reviewScope);
104868
- await mkdir30(dirname38(outPath), { recursive: true });
104869
- await writeFile24(outPath, renderReviewHtml(candidates, reviewScope), "utf8");
104515
+ await mkdir30(dirname37(outPath), { recursive: true });
104516
+ await writeFile24(outPath, renderReviewHtml(candidates, reviewScope, await collectReviewSiteModel(input.projectRoot, candidates)), "utf8");
104870
104517
  return {
104871
104518
  path: outPath,
104872
104519
  candidates: candidates.length,
@@ -104879,8 +104526,8 @@ async function writeReviewHtml(input) {
104879
104526
  init_productionRequirements();
104880
104527
  init_dist();
104881
104528
  init_atomicWrite();
104882
- import { mkdir as mkdir31, readFile as readFile67 } from "node:fs/promises";
104883
- import { join as join86 } from "node:path";
104529
+ import { mkdir as mkdir31, readFile as readFile69 } from "node:fs/promises";
104530
+ import { join as join88 } from "node:path";
104884
104531
  var REVIEW_BATCH_MAX_CANDIDATES = 6;
104885
104532
  var REVIEW_BATCH_MAX_BYTES = 512 * 1024;
104886
104533
  async function readerPurposes(projectRoot, sources) {
@@ -104956,15 +104603,16 @@ function buildCurrentReviewBatchDocuments(candidates) {
104956
104603
  });
104957
104604
  }
104958
104605
  async function materializeCurrentReviewBatchSet(input) {
104606
+ const feedback = await readPendingReviewFeedback(input.projectRoot, input.candidates);
104959
104607
  const batches = buildCurrentReviewBatchDocuments(input.candidates);
104960
104608
  const setDigest = digestText(batches.map((batch) => `${batch.task_key}:${batch.digest}`).join(`
104961
104609
  `));
104962
- const root2 = join86(input.projectRoot, ".tmp", "context-runtime", "review", `current-${setDigest.slice("sha256:".length)}`);
104610
+ const root2 = join88(input.projectRoot, ".tmp", "context-runtime", "review", `current-${setDigest.slice("sha256:".length)}`);
104963
104611
  await mkdir31(root2, { recursive: true });
104964
104612
  const entries2 = [];
104965
104613
  for (const batch of batches) {
104966
- const path4 = join86(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
104967
- const existing = await readFile67(path4, "utf8").catch((error) => {
104614
+ const path4 = join88(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
104615
+ const existing = await readFile69(path4, "utf8").catch((error) => {
104968
104616
  if (error.code === "ENOENT")
104969
104617
  return;
104970
104618
  throw error;
@@ -104976,6 +104624,7 @@ async function materializeCurrentReviewBatchSet(input) {
104976
104624
  }
104977
104625
  const content3 = [
104978
104626
  "# Current knowledge Review",
104627
+ ...feedback.length ? ["", "## Pending user revision instructions", ...feedback.map((item) => JSON.stringify(item)), "Apply these through their repair commands, then review the new candidates. Do not approve unchanged drafts to bypass feedback."] : [],
104979
104628
  "",
104980
104629
  "## Reader purposes",
104981
104630
  "",
@@ -105016,7 +104665,7 @@ async function materializeCurrentReviewBatchSet(input) {
105016
104665
  ].join(`
105017
104666
  `);
105018
104667
  const digest6 = digestText(content3);
105019
- const path3 = join86(root2, "index.md");
104668
+ const path3 = join88(root2, "index.md");
105020
104669
  await atomicWriteFile(path3, `${content3}
105021
104670
  `);
105022
104671
  return {
@@ -105049,11 +104698,11 @@ function shellQuote6(value) {
105049
104698
  }
105050
104699
  function receiptSetPath(receipts) {
105051
104700
  const token = digestText(JSON.stringify(receipts)).slice("sha256:".length);
105052
- return join87(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
104701
+ return join89(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
105053
104702
  }
105054
104703
  async function writeReceiptContinuation(input) {
105055
104704
  const path3 = receiptSetPath(input.receipts);
105056
- const absolutePath = join87(input.projectRoot, path3);
104705
+ const absolutePath = join89(input.projectRoot, path3);
105057
104706
  await writeJsonAtomic(absolutePath, input.receipts);
105058
104707
  const contextCommand = input.managed ? [
105059
104708
  "context",
@@ -105148,7 +104797,7 @@ async function materializeContextWorkflowResource(input) {
105148
104797
  const resourceId = workflowResourceId(input.resourceId);
105149
104798
  const content3 = renderContextWorkflowResource(resourceId, status);
105150
104799
  const location = await materializeResource(await loadContextWorkflowProvider(), resourceId, {
105151
- cache: join87(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
104800
+ cache: join89(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
105152
104801
  workspace: found.projectRoot,
105153
104802
  revision: input.revision,
105154
104803
  input: {
@@ -105180,7 +104829,7 @@ async function materializeContextWorkflowResource(input) {
105180
104829
  receipts: afterReadReceipts
105181
104830
  });
105182
104831
  const directResources = (status.workflow.current?.resources.required ?? []).filter((resource) => resource.read_state === "read-required" && resource.path !== undefined && resource.digest !== undefined);
105183
- const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${join87(found.projectRoot, continuation.path)}`)} --format json`;
104832
+ const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${join89(found.projectRoot, continuation.path)}`)} --format json`;
105184
104833
  return {
105185
104834
  protocol: "context.workflow.resource.v1",
105186
104835
  id: resourceId,
@@ -105241,14 +104890,14 @@ async function acknowledgeCurrentWorkflowResources(input) {
105241
104890
  const reevaluated = await reevaluateProjectStatusWorkflow({
105242
104891
  snapshot,
105243
104892
  resourceReceipts: normalizedReceipts,
105244
- resourceReceiptsReference: `@${join87(found.projectRoot, continuation.path)}`
104893
+ resourceReceiptsReference: `@${join89(found.projectRoot, continuation.path)}`
105245
104894
  });
105246
104895
  return {
105247
104896
  ...reevaluated,
105248
104897
  resourceAcknowledgement: {
105249
104898
  protocol: "context.workflow.resource-receipts.v1",
105250
104899
  acknowledged: directResources.length,
105251
- receiptReference: `@${join87(found.projectRoot, continuation.path)}`
104900
+ receiptReference: `@${join89(found.projectRoot, continuation.path)}`
105252
104901
  }
105253
104902
  };
105254
104903
  }
@@ -105294,9 +104943,9 @@ init_cliFeedback();
105294
104943
  init_errors3();
105295
104944
  init_exitCode();
105296
104945
  init_workspace();
105297
- import { readFile as readFile68 } from "node:fs/promises";
105298
- import { isAbsolute as isAbsolute16, join as join88, sep as sep6, resolve as resolve29 } from "node:path";
105299
- var RECEIPT_DIRECTORY = join88(".tmp", "context-runtime", "workflow", "read-receipts");
104946
+ import { readFile as readFile70 } from "node:fs/promises";
104947
+ import { isAbsolute as isAbsolute16, join as join90, sep as sep6, resolve as resolve29 } from "node:path";
104948
+ var RECEIPT_DIRECTORY = join90(".tmp", "context-runtime", "workflow", "read-receipts");
105300
104949
  function workflowResourceReceiptCwd(value, cwd) {
105301
104950
  if (value === undefined || !value.startsWith("@"))
105302
104951
  return cwd;
@@ -105314,7 +104963,7 @@ async function receiptDocument(value, cwd) {
105314
104963
  let source2 = value;
105315
104964
  if (value.startsWith("@")) {
105316
104965
  try {
105317
- source2 = await readFile68(resolve29(cwd, value.slice(1)), "utf8");
104966
+ source2 = await readFile70(resolve29(cwd, value.slice(1)), "utf8");
105318
104967
  } catch (error) {
105319
104968
  const ioCode = error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : undefined;
105320
104969
  throw new ContextError(ExitCode.UserError, "resource read receipt file is unavailable", {
@@ -105475,33 +105124,33 @@ function runSuccessBaseBody(input) {
105475
105124
  `log: ${input.logPath}`
105476
105125
  ];
105477
105126
  }
105478
- function appendCaptureFileRunBody(body, result) {
105127
+ function appendCaptureFileRunBody(body2, result) {
105479
105128
  const nextAction = nextActionCommand(result.next_action);
105480
- body.push(`source: file:${result.source.name}`, `include: ${result.source.include.join(", ")}`, `documents: ${result.documents.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105129
+ body2.push(`source: file:${result.source.name}`, `include: ${result.source.include.join(", ")}`, `documents: ${result.documents.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105481
105130
  if (nextAction !== undefined)
105482
- body.push(`next action: ${nextAction}`);
105131
+ body2.push(`next action: ${nextAction}`);
105483
105132
  for (const document4 of result.documents.slice(0, 8)) {
105484
- body.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105133
+ body2.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105485
105134
  }
105486
105135
  }
105487
- function appendCaptureLarkRunBody(body, result) {
105136
+ function appendCaptureLarkRunBody(body2, result) {
105488
105137
  const nextAction = nextActionCommand(result.next_action);
105489
- body.push(`source: lark:${result.source.name}`, `identity: ${result.source.identity}`, `documents: ${result.documents.length}`, `assets: ${result.assets.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105138
+ body2.push(`source: lark:${result.source.name}`, `identity: ${result.source.identity}`, `documents: ${result.documents.length}`, `assets: ${result.assets.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105490
105139
  if (nextAction !== undefined)
105491
- body.push(`next action: ${nextAction}`);
105140
+ body2.push(`next action: ${nextAction}`);
105492
105141
  for (const document4 of result.documents.slice(0, 8)) {
105493
- body.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105142
+ body2.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105494
105143
  }
105495
105144
  }
105496
- function appendRunResultBody(body, result) {
105145
+ function appendRunResultBody(body2, result) {
105497
105146
  if (isCaptureFileRunResult(result))
105498
- appendCaptureFileRunBody(body, result);
105147
+ appendCaptureFileRunBody(body2, result);
105499
105148
  if (isCaptureLarkRunResult(result))
105500
- appendCaptureLarkRunBody(body, result);
105149
+ appendCaptureLarkRunBody(body2, result);
105501
105150
  if (result !== null && typeof result === "object" && !Array.isArray(result) && "kind" in result && (result.kind === "semantic.rules.view.result" || result.kind === "diagnostics.view.result") && "next_action" in result && result.next_action !== null && typeof result.next_action === "object" && !Array.isArray(result.next_action)) {
105502
105151
  const nextCommand = nextActionCommand(result.next_action);
105503
105152
  if (nextCommand !== undefined)
105504
- body.push(`next action: ${nextCommand}`);
105153
+ body2.push(`next action: ${nextCommand}`);
105505
105154
  }
105506
105155
  }
105507
105156
  function writeRunSuccess(input) {
@@ -105522,14 +105171,14 @@ function writeRunSuccess(input) {
105522
105171
  `);
105523
105172
  return;
105524
105173
  }
105525
- const body = runSuccessBaseBody(input);
105526
- appendRunResultBody(body, input.result);
105174
+ const body2 = runSuccessBaseBody(input);
105175
+ appendRunResultBody(body2, input.result);
105527
105176
  process.stdout.write(formatFeedback({
105528
105177
  symbol: "✓",
105529
105178
  action: "ran",
105530
105179
  subject: input.plan.phase.id,
105531
105180
  headline: input.plan.phase.kind,
105532
- body
105181
+ body: body2
105533
105182
  }));
105534
105183
  }
105535
105184
  function compactDiagnostics(record4) {
@@ -105561,15 +105210,15 @@ function compactJsonResult(result, verbose) {
105561
105210
  // src/project/runLog.ts
105562
105211
  import { randomUUID as randomUUID5 } from "node:crypto";
105563
105212
  import { mkdir as mkdir32, writeFile as writeFile25 } from "node:fs/promises";
105564
- import { dirname as dirname39, join as join91 } from "node:path";
105213
+ import { dirname as dirname38, join as join93 } from "node:path";
105565
105214
  var createPhaseRunId = () => {
105566
105215
  const timestamp = new Date().toISOString().replace(/[-:.TZ]/gu, "");
105567
105216
  return `run_${timestamp}_${randomUUID5().slice(0, 8)}`;
105568
105217
  };
105569
105218
  async function writePhaseRunLog(input) {
105570
- const relPath = join91(".tmp", "context-runtime", "runs", `${input.runId}.json`);
105571
- const absPath = join91(input.projectRoot, relPath);
105572
- await mkdir32(dirname39(absPath), { recursive: true });
105219
+ const relPath = join93(".tmp", "context-runtime", "runs", `${input.runId}.json`);
105220
+ const absPath = join93(input.projectRoot, relPath);
105221
+ await mkdir32(dirname38(absPath), { recursive: true });
105573
105222
  await writeFile25(absPath, `${JSON.stringify({
105574
105223
  run_id: input.runId,
105575
105224
  phase_id: input.phase.id,
@@ -105774,7 +105423,7 @@ function writeRunPlan(plan, format2, preview, previewError) {
105774
105423
  `);
105775
105424
  return;
105776
105425
  }
105777
- const body = [
105426
+ const body2 = [
105778
105427
  `dry-run: ${plan.dryRun ? "yes" : "no"}`,
105779
105428
  `reads: ${plan.phase.reads.length > 0 ? plan.phase.reads.join(", ") : "none"}`,
105780
105429
  `writes: ${plan.phase.writes.length > 0 ? plan.phase.writes.join(", ") : "none"}`,
@@ -105786,7 +105435,7 @@ function writeRunPlan(plan, format2, preview, previewError) {
105786
105435
  action: "planned",
105787
105436
  subject: plan.phase.id,
105788
105437
  headline: plan.phase.kind,
105789
- body
105438
+ body: body2
105790
105439
  }));
105791
105440
  }
105792
105441
  function customPhaseContext(input) {
@@ -106308,7 +105957,7 @@ init_debugTrace();
106308
105957
  // src/project/workflow/workflowExecutionRuntime.ts
106309
105958
  init_src();
106310
105959
  init_debugTrace();
106311
- import { createHash as createHash27 } from "node:crypto";
105960
+ import { createHash as createHash28 } from "node:crypto";
106312
105961
  import { spawn as spawn5 } from "node:child_process";
106313
105962
  function digestText2(value, includeTail) {
106314
105963
  const filtered = redactIndexerOutputText({
@@ -106318,7 +105967,7 @@ function digestText2(value, includeTail) {
106318
105967
  const bytes = Buffer.byteLength(filtered);
106319
105968
  return {
106320
105969
  bytes,
106321
- sha256: createHash27("sha256").update(filtered).digest("hex"),
105970
+ sha256: createHash28("sha256").update(filtered).digest("hex"),
106322
105971
  ...includeTail && filtered.length > 0 ? { tail: filtered.slice(-8192) } : {}
106323
105972
  };
106324
105973
  }
@@ -106511,11 +106160,150 @@ execution scope cleanup failed`, true)
106511
106160
  // src/project/workflow/workflowInProcessActions.ts
106512
106161
  init_workflowFacts();
106513
106162
 
106163
+ // src/project/reviewCode.ts
106164
+ function createReviewCodeCodec() {
106165
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
106166
+ function checksum(text9) {
106167
+ let crc = 4294967295;
106168
+ for (let i2 = 0;i2 < text9.length; i2++) {
106169
+ crc ^= text9.charCodeAt(i2);
106170
+ for (let bit = 0;bit < 8; bit++)
106171
+ crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
106172
+ }
106173
+ return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
106174
+ }
106175
+ function pack(bytes) {
106176
+ let value = 0, bits = 0, result = "";
106177
+ for (const byte of bytes) {
106178
+ value = value << 8 | byte;
106179
+ bits += 8;
106180
+ while (bits >= 6) {
106181
+ bits -= 6;
106182
+ result += alphabet[value >>> bits & 63];
106183
+ }
106184
+ }
106185
+ if (bits)
106186
+ result += alphabet[value << 6 - bits & 63];
106187
+ return result;
106188
+ }
106189
+ function unpack(text9) {
106190
+ if (!/^[A-Za-z0-9_-]*$/.test(text9))
106191
+ throw new Error("Invalid review code encoding");
106192
+ let value = 0, bits = 0;
106193
+ const bytes = [];
106194
+ for (const char of text9) {
106195
+ value = value << 6 | alphabet.indexOf(char);
106196
+ bits += 6;
106197
+ if (bits >= 8) {
106198
+ bits -= 8;
106199
+ bytes.push(value >>> bits & 255);
106200
+ }
106201
+ }
106202
+ if (pack(bytes) !== text9)
106203
+ throw new Error("Noncanonical review code encoding");
106204
+ return bytes;
106205
+ }
106206
+ function hash4(text9) {
106207
+ if (!/^[a-f0-9]{64}$/.test(text9))
106208
+ throw new Error("Review requires a complete candidate digest");
106209
+ return pack(Array.from({ length: 32 }, (_, i2) => Number.parseInt(text9.slice(i2 * 2, i2 * 2 + 2), 16)));
106210
+ }
106211
+ function unhash(text9) {
106212
+ const bytes = unpack(text9);
106213
+ if (bytes.length !== 32)
106214
+ throw new Error("Invalid candidate digest");
106215
+ return bytes.map((byte) => byte.toString(16).padStart(2, "0")).join("");
106216
+ }
106217
+ function encode(scope2, idsHash, contentHash2, statuses) {
106218
+ if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !statuses.length || statuses.length > 1e6 || statuses.some((status) => status !== "approved" && status !== "rejected" && status !== "pending")) {
106219
+ throw new Error("Select at least one review decision and keep undecided pages pending");
106220
+ }
106221
+ if (statuses.every((s) => s === "pending"))
106222
+ throw new Error("Select at least one review decision");
106223
+ const mode = statuses.every((s) => s === "approved") ? "a" : statuses.every((s) => s === "rejected") ? "r" : statuses.includes("pending") ? "p" : "b";
106224
+ const bytes = Array(Math.ceil(statuses.length / (mode === "p" ? 4 : 8))).fill(0);
106225
+ if (mode === "p")
106226
+ statuses.forEach((s, i2) => {
106227
+ bytes[i2 >> 2] |= (s === "approved" ? 1 : s === "rejected" ? 2 : 0) << i2 % 4 * 2;
106228
+ });
106229
+ if (mode === "b")
106230
+ statuses.forEach((s, i2) => {
106231
+ if (s === "rejected")
106232
+ bytes[i2 >> 3] |= 1 << i2 % 8;
106233
+ });
106234
+ const body2 = ["CR1", scope2, statuses.length, hash4(idsHash), hash4(contentHash2), mode, mode === "b" || mode === "p" ? pack(bytes) : ""].join(".");
106235
+ const code3 = `${body2}.${checksum(body2)}`;
106236
+ if (code3.length <= 980)
106237
+ return [code3];
106238
+ const total = Math.ceil(code3.length / 900);
106239
+ if (total > 200)
106240
+ throw new Error("Review decisions exceed 200 segments; use a smaller collection scope");
106241
+ const identity = checksum(code3);
106242
+ return Array.from({ length: total }, (_, i2) => `CRP1.${identity}.${i2 + 1}.${total}.${code3.slice(i2 * 900, (i2 + 1) * 900)}`);
106243
+ }
106244
+ function decode2(input) {
106245
+ if (input.length > 250000)
106246
+ throw new Error("Review code exceeds the supported size");
106247
+ const lines = input.trim().split(/\s+/);
106248
+ let code3 = lines[0];
106249
+ if (code3.startsWith("CRP1.")) {
106250
+ const parts = new Map;
106251
+ let identity = "", total = 0;
106252
+ for (const line of lines) {
106253
+ const match = /^CRP1\.([a-f0-9]{8})\.([1-9][0-9]*)\.([1-9][0-9]*)\.(.+)$/.exec(line);
106254
+ if (!match || line.length > 980)
106255
+ throw new Error("Invalid review code segment");
106256
+ const index2 = Number(match[2]), count2 = Number(match[3]);
106257
+ if (count2 > 200 || index2 > count2 || parts.has(index2) || total && (total !== count2 || identity !== match[1])) {
106258
+ throw new Error("Duplicate or mixed review code segments");
106259
+ }
106260
+ identity = match[1];
106261
+ total = count2;
106262
+ parts.set(index2, match[4]);
106263
+ }
106264
+ if (parts.size !== total)
106265
+ throw new Error(`Missing review code segments: received ${parts.size} of ${total}; collect all segments before applying`);
106266
+ code3 = Array.from({ length: total }, (_, i2) => parts.get(i2 + 1)).join("");
106267
+ if (checksum(code3) !== identity)
106268
+ throw new Error("Review code segment checksum mismatch");
106269
+ } else if (lines.length !== 1 || code3.length > 980)
106270
+ throw new Error("Copy each complete review code segment unchanged");
106271
+ const fields = code3.split(".");
106272
+ if (fields.length !== 8 || fields[0] !== "CR1" || checksum(fields.slice(0, 7).join(".")) !== fields[7]) {
106273
+ throw new Error("Review code is damaged or unsupported; copy it again from the report");
106274
+ }
106275
+ const [, scope2, countText, ids, content3, mode, data2] = fields;
106276
+ if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !/^[1-9][0-9]*$/.test(countText))
106277
+ throw new Error("Invalid review scope");
106278
+ const count = Number(countText);
106279
+ if (count > 1e6 || !["a", "r", "b", "p"].includes(mode))
106280
+ throw new Error("Invalid review decisions");
106281
+ const bytes = unpack(data2);
106282
+ const perByte = mode === "p" ? 4 : 8;
106283
+ if (mode === "b" || mode === "p" ? bytes.length !== Math.ceil(count / perByte) || count % perByte !== 0 && bytes.at(-1) >>> count % perByte * (mode === "p" ? 2 : 1) !== 0 : data2 !== "") {
106284
+ throw new Error("Invalid review decision bitmap");
106285
+ }
106286
+ const statuses = Array.from({ length: count }, (_, i2) => {
106287
+ if (mode === "p") {
106288
+ const value = bytes[i2 >> 2] >>> i2 % 4 * 2 & 3;
106289
+ if (value === 3)
106290
+ throw new Error("Invalid pending review bitmap");
106291
+ return value === 1 ? "approved" : value === 2 ? "rejected" : "pending";
106292
+ }
106293
+ return mode === "r" || mode === "b" && bytes[i2 >> 3] & 1 << i2 % 8 ? "rejected" : "approved";
106294
+ });
106295
+ if (statuses.every((s) => s === "pending"))
106296
+ throw new Error("Review contains no decisions");
106297
+ return { scope: scope2, count, idsHash: unhash(ids), contentHash: unhash(content3), statuses };
106298
+ }
106299
+ return { encode, decode: decode2 };
106300
+ }
106301
+
106514
106302
  // src/project/review.ts
106515
106303
  init_cliFeedback();
106516
106304
  init_errors3();
106517
106305
  init_exitCode();
106518
- import { readFile as readFile73 } from "node:fs/promises";
106306
+ import { readFile as readFile75 } from "node:fs/promises";
106519
106307
  import { isAbsolute as isAbsolute18, resolve as resolve30 } from "node:path";
106520
106308
 
106521
106309
  // src/project/reviewApply.ts
@@ -106528,8 +106316,8 @@ init_writeLock();
106528
106316
  init_reviewApplyIndexer();
106529
106317
  init_approvedKnowledgeSnapshots();
106530
106318
  import { existsSync as existsSync25 } from "node:fs";
106531
- import { readFile as readFile71 } from "node:fs/promises";
106532
- import { join as join92 } from "node:path";
106319
+ import { readFile as readFile73 } from "node:fs/promises";
106320
+ import { join as join94 } from "node:path";
106533
106321
 
106534
106322
  // src/project/reviewCandidateAuthority.ts
106535
106323
  init_src2();
@@ -106626,7 +106414,7 @@ async function prepareApprovedPage(input) {
106626
106414
  next: "Refresh the current production or article revision, then reopen Review before approval."
106627
106415
  });
106628
106416
  }
106629
- const relPath = join92("knowledge", input.record.path);
106417
+ const relPath = join94("knowledge", input.record.path);
106630
106418
  const existingView = findApprovedPageForArticleId(input.record.indexer_candidate.artifact_ref, input.approvedPageIndex);
106631
106419
  const previousPath = input.record.approved_revision?.previous_path;
106632
106420
  if (previousPath !== undefined && (!isSafeKnowledgeTargetPath(previousPath.split("/")[0], previousPath) || previousPath.includes("\\")))
@@ -106641,13 +106429,13 @@ async function prepareApprovedPage(input) {
106641
106429
  next: "Resolve the approved page path migration explicitly before approving this candidate."
106642
106430
  });
106643
106431
  }
106644
- const absPath = join92(input.projectRoot, relPath);
106645
- const existing = existsSync25(absPath) ? await readFile71(absPath, "utf8") : undefined;
106432
+ const absPath = join94(input.projectRoot, relPath);
106433
+ const existing = existsSync25(absPath) ? await readFile73(absPath, "utf8") : undefined;
106646
106434
  let previous3;
106647
106435
  if (previousPath !== undefined) {
106648
106436
  if (existing !== undefined || existingView?.relPath !== `knowledge/${previousPath}`)
106649
106437
  throw new TypeError("Page move destination or original identity changed; refresh its revision.");
106650
- previous3 = { path: `knowledge/${previousPath}`, content: await readFile71(join92(input.projectRoot, "knowledge", previousPath), "utf8") };
106438
+ previous3 = { path: `knowledge/${previousPath}`, content: await readFile73(join94(input.projectRoot, "knowledge", previousPath), "utf8") };
106651
106439
  }
106652
106440
  if (input.record.approved_revision !== undefined) {
106653
106441
  const base = previous3?.content ?? existing;
@@ -106694,7 +106482,7 @@ async function prepareApprovedPage(input) {
106694
106482
  }
106695
106483
  async function readProjectFileMaybe(projectRoot, relPath) {
106696
106484
  try {
106697
- return await readFile71(join92(projectRoot, relPath), "utf8");
106485
+ return await readFile73(join94(projectRoot, relPath), "utf8");
106698
106486
  } catch (error) {
106699
106487
  if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") {
106700
106488
  return;
@@ -106824,6 +106612,36 @@ async function applyReviewDecisions(input) {
106824
106612
  const rows = await readCandidateRecords(input.projectRoot);
106825
106613
  const nextRows = [...rows];
106826
106614
  const decisions = expandReviewPayload(input.payload, rows);
106615
+ 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);
106616
+ if (input.payload.baseline_hash && input.payload.baseline_hash !== await reviewSiteBaselineHash(input.projectRoot, scoped.map((r) => r.approved_revision?.previous_path ?? r.path))) {
106617
+ throw new ContextError(ExitCode.WorkspaceStateError, "Review navigation or approved baseline changed; generate a fresh report", {
106618
+ category: ErrorCategory.WorkspaceStateInvalid,
106619
+ code: "review-baseline-stale",
106620
+ next: "context review html --all --format json"
106621
+ });
106622
+ }
106623
+ const repairIds = new Set;
106624
+ const repairs = (input.payload.feedback_repairs ?? []).map((repair) => {
106625
+ const row = scoped[repair.index];
106626
+ if (!Number.isInteger(repair.index) || !row || repairIds.has(repair.index) || !repair.instruction.trim() || input.payload.encoded_statuses?.[repair.index] !== "pending")
106627
+ throw new Error("Invalid review revision request");
106628
+ repairIds.add(repair.index);
106629
+ const quote = (value) => "'" + value.replace(/'/gu, "'\\''") + "'";
106630
+ return {
106631
+ candidate_id: row.candidate_id,
106632
+ path: row.path,
106633
+ fingerprint: row.fingerprint,
106634
+ instruction: repair.instruction,
106635
+ command: `context revise ${quote(row.candidate_id)} --instruction ${quote(repair.instruction)} --format json`
106636
+ };
106637
+ });
106638
+ const feedbackPath = repairs.length ? `.tmp/context-runtime/review-feedback/${indexerProtocolDigest(input.payload).replace("sha256:", "")}.json` : undefined;
106639
+ const feedbackTarget = feedbackPath === undefined ? undefined : reviewFileTarget({
106640
+ path: feedbackPath,
106641
+ baseContent: await readProjectFileMaybe(input.projectRoot, feedbackPath),
106642
+ targetContent: JSON.stringify({ created_at: now, repairs }, null, 2) + `
106643
+ `
106644
+ });
106827
106645
  const approvesAnyCandidate = decisions.some((decision) => decision.status === "approved");
106828
106646
  const candidateAuthority = approvesAnyCandidate ? await loadReviewCandidateAuthority(input.projectRoot) : undefined;
106829
106647
  const approvedPageIndex = approvesAnyCandidate ? await buildApprovedArticleIndex(input.projectRoot) : {
@@ -106887,7 +106705,7 @@ async function applyReviewDecisions(input) {
106887
106705
  });
106888
106706
  }
106889
106707
  seenApprovedIds.set(approvedRef, row.candidate_id);
106890
- const approvedPath = join92("knowledge", row.path);
106708
+ const approvedPath = join94("knowledge", row.path);
106891
106709
  const previousPathCandidate = seenApprovedPaths.get(knowledgeTargetPathKey(approvedPath));
106892
106710
  if (previousPathCandidate !== undefined) {
106893
106711
  throw new ContextError(ExitCode.UserError, `multiple approved review decisions target the same knowledge path: ${approvedPath}`, {
@@ -106937,13 +106755,14 @@ async function applyReviewDecisions(input) {
106937
106755
  for (const path3 of approvedPageIndex.byRelPath.keys()) {
106938
106756
  if (pagesToWrite.some((page) => page.relPath === path3 || page.previous?.path === path3))
106939
106757
  continue;
106940
- const before = await readFile71(join92(input.projectRoot, path3), "utf8");
106758
+ const before = await readFile73(join94(input.projectRoot, path3), "utf8");
106941
106759
  const local = path3.replace(/^knowledge\//u, "");
106942
106760
  const after = moveKnowledgeLinkTargets2(before, local, local, moved);
106943
106761
  navigationTargets.push(reviewFileTarget({ path: path3, baseContent: before, targetContent: after }));
106944
106762
  }
106945
106763
  }
106946
106764
  const targets = [
106765
+ feedbackTarget,
106947
106766
  await prepareApprovedKnowledgeSnapshotTarget({ projectRoot: input.projectRoot, pages: pagesToWrite, candidates: rows }),
106948
106767
  ...navigationTargets,
106949
106768
  ...pagesToWrite.flatMap((page) => page.previous === undefined ? [] : [reviewFileTarget({
@@ -106979,6 +106798,7 @@ async function applyReviewDecisions(input) {
106979
106798
  }
106980
106799
  return {
106981
106800
  applied: decisions.length,
106801
+ ...feedbackPath ? { repairs, feedback_path: feedbackPath } : {},
106982
106802
  approved,
106983
106803
  rejected,
106984
106804
  unchanged,
@@ -106991,13 +106811,13 @@ async function applyReviewDecisions(input) {
106991
106811
  }
106992
106812
 
106993
106813
  // src/project/reviewMaintenance.ts
106994
- import { readFile as readFile72, writeFile as writeFile26 } from "node:fs/promises";
106814
+ import { readFile as readFile74, writeFile as writeFile26 } from "node:fs/promises";
106995
106815
  init_writeLock();
106996
106816
  init_verifyFrontmatter();
106997
106817
  function deprecateApprovedPage(input) {
106998
106818
  return withProjectWriteLock(input.projectRoot, "deprecate-article", async () => {
106999
106819
  const page = await approvedPageForArticleId(input.projectRoot, input.viewRef);
107000
- const original = await readFile72(page.path, "utf8");
106820
+ const original = await readFile74(page.path, "utf8");
107001
106821
  const content3 = parseFrontmatterLoose(original).deprecated === true ? original : updateFrontmatter(original, (metadata) => ({ ...metadata, deprecated: true, timestamp: new Date().toISOString() }));
107002
106822
  const changed = content3 !== original;
107003
106823
  if (changed)
@@ -107016,13 +106836,13 @@ function deprecateApprovedPage(input) {
107016
106836
  init_candidateLedger();
107017
106837
 
107018
106838
  // src/project/localHtmlReport.ts
107019
- import { execFile as execFile10 } from "node:child_process";
107020
- import { isAbsolute as isAbsolute17, join as join93 } from "node:path";
106839
+ import { execFile as execFile11 } from "node:child_process";
106840
+ import { isAbsolute as isAbsolute17, join as join95 } from "node:path";
107021
106841
  import { pathToFileURL as pathToFileURL2 } from "node:url";
107022
- import { promisify as promisify10 } from "node:util";
107023
- var execFileAsync5 = promisify10(execFile10);
106842
+ import { promisify as promisify11 } from "node:util";
106843
+ var execFileAsync5 = promisify11(execFile11);
107024
106844
  function htmlReportReference(input) {
107025
- const absolutePath = isAbsolute17(input.path) ? input.path : join93(input.projectRoot, input.path);
106845
+ const absolutePath = isAbsolute17(input.path) ? input.path : join95(input.projectRoot, input.path);
107026
106846
  return {
107027
106847
  format: "html",
107028
106848
  path: input.path,
@@ -107194,7 +107014,7 @@ function parseReviewPayloadText(raw) {
107194
107014
  async function readReviewPayloadFile(filePath2) {
107195
107015
  let raw;
107196
107016
  try {
107197
- raw = await readFile73(filePath2, "utf8");
107017
+ raw = await readFile75(filePath2, "utf8");
107198
107018
  } catch (error) {
107199
107019
  const message = error instanceof Error ? error.message : String(error);
107200
107020
  throw new ContextError(ExitCode.UserError, `review payload file cannot be read: ${filePath2}`, {
@@ -107206,6 +107026,24 @@ async function readReviewPayloadFile(filePath2) {
107206
107026
  }
107207
107027
  if (raw.trim().startsWith("CR")) {
107208
107028
  try {
107029
+ if (raw.trim().startsWith("CR2.")) {
107030
+ const feedback = createReviewFeedbackCodec().decode(raw);
107031
+ const collection2 = feedback.scope === "all" ? undefined : assertCollection(feedback.scope);
107032
+ return {
107033
+ decisions: [],
107034
+ encoded_statuses: feedback.statuses.map((s) => s === "revised" ? "pending" : s),
107035
+ feedback_repairs: feedback.repairs,
107036
+ baseline_hash: feedback.baselineHash,
107037
+ ...collection2 === undefined ? {} : { collection: collection2 },
107038
+ scope: {
107039
+ kind: collection2 === undefined ? "all" : "collection",
107040
+ ...collection2 === undefined ? {} : { collection: collection2 },
107041
+ count: feedback.statuses.length,
107042
+ ids_sha256: feedback.idsHash,
107043
+ candidates_sha256: feedback.contentHash
107044
+ }
107045
+ };
107046
+ }
107209
107047
  const decoded = createReviewCodeCodec().decode(raw);
107210
107048
  const collection = decoded.scope === "all" ? undefined : assertCollection(decoded.scope);
107211
107049
  return {
@@ -107223,7 +107061,7 @@ async function readReviewPayloadFile(filePath2) {
107223
107061
  } catch (error) {
107224
107062
  throw new ContextError(ExitCode.UserError, error instanceof Error ? error.message : String(error), {
107225
107063
  category: ErrorCategory.UserInputInvalid,
107226
- next: "Copy all review code segments unchanged from the current HTML report into one input file, one per line; apply only after all segments are present."
107064
+ 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."
107227
107065
  });
107228
107066
  }
107229
107067
  }
@@ -107243,6 +107081,7 @@ function formatApplyResult(result, format2) {
107243
107081
  `rejected: ${result.rejected}`,
107244
107082
  `materialized: ${result.materialized}`,
107245
107083
  `removed: ${result.removed}`,
107084
+ ...(result.repairs ?? []).map((r) => `repair ${r.path}: ${r.command}`),
107246
107085
  `unchanged: ${result.unchanged}`,
107247
107086
  `candidate file: ${result.candidateFileUpdated ? "updated" : "unchanged"}`,
107248
107087
  ...result.pages.map((page) => `page: ${page}`),
@@ -107489,6 +107328,7 @@ async function runReviewApproveAllCommand(input) {
107489
107328
  `materialized: ${result.materialized}`,
107490
107329
  `unchanged: ${result.unchanged}`,
107491
107330
  `removed: ${result.removed}`,
107331
+ ...(result.repairs ?? []).map((r) => `repair ${r.path}: ${r.command}`),
107492
107332
  ...continuation === undefined ? [] : [`workflow: ${continuation.state}`, continuation.stop.message]
107493
107333
  ]
107494
107334
  }));
@@ -108025,8 +107865,8 @@ init_exitCode();
108025
107865
  init_maintenanceStorage();
108026
107866
  init_productionFeedback();
108027
107867
  import { randomUUID as randomUUID6 } from "node:crypto";
108028
- import { readFile as readFile74 } from "node:fs/promises";
108029
- import { join as join94 } from "node:path";
107868
+ import { readFile as readFile76 } from "node:fs/promises";
107869
+ import { join as join96 } from "node:path";
108030
107870
  async function beginProductionRevision(input) {
108031
107871
  return withProductionFeedback({ operation: "revision" }, () => withProjectWriteLock(input.projectRoot, "production-revision", async () => {
108032
107872
  await recoverDurableMultiFileTransactions(input.projectRoot);
@@ -108065,7 +107905,7 @@ async function beginProductionRevision(input) {
108065
107905
  const formal = approved.byPath.get(path3);
108066
107906
  if (!prior && !formal)
108067
107907
  throw invalid2("Write the current task first; there is no article draft to revise yet.");
108068
- const markdown = prior?.body ?? await readFile74(await safeProjectTarget(input.projectRoot, join94("knowledge", path3)), "utf8");
107908
+ const markdown = prior?.body ?? await readFile76(await safeProjectTarget(input.projectRoot, join96("knowledge", path3)), "utf8");
108069
107909
  const sections = prior?.indexer_candidate.sections.map((section) => ({ id: section.section_key, references: section.references })) ?? formal?.sections;
108070
107910
  const sources = [];
108071
107911
  for (const source2 of owner.sources) {
@@ -108241,8 +108081,8 @@ init_actionInputWorkspace();
108241
108081
  init_cliFeedback();
108242
108082
  init_errors3();
108243
108083
  init_exitCode();
108244
- var import_yaml40 = __toESM(require_dist(), 1);
108245
- import { readFile as readFile75 } from "node:fs/promises";
108084
+ var import_yaml41 = __toESM(require_dist(), 1);
108085
+ import { readFile as readFile77 } from "node:fs/promises";
108246
108086
  function userInputError2(message, detail = {}) {
108247
108087
  return new ContextError(ExitCode.UserError, message, {
108248
108088
  category: ErrorCategory.UserInputInvalid,
@@ -108253,7 +108093,7 @@ function parsePayloadText(raw) {
108253
108093
  const trimmed = raw.trimStart();
108254
108094
  if (trimmed.startsWith("{") || trimmed.startsWith("["))
108255
108095
  return JSON.parse(raw);
108256
- return import_yaml40.default.parse(raw);
108096
+ return import_yaml41.default.parse(raw);
108257
108097
  }
108258
108098
  function isCompleteJsonLine(raw) {
108259
108099
  if (!raw.endsWith(`
@@ -108311,7 +108151,7 @@ async function readPayloadTextFromStdin(stdin) {
108311
108151
  }
108312
108152
  async function readPayloadText(path3) {
108313
108153
  if (path3 !== "-")
108314
- return readFile75(path3, "utf8");
108154
+ return readFile77(path3, "utf8");
108315
108155
  return readPayloadTextFromStdin(process.stdin);
108316
108156
  }
108317
108157
  async function readYamlOrJsonInput(input) {
@@ -108673,7 +108513,7 @@ function registerRuntimeEventLogCommands(program2) {
108673
108513
  if (result.status === "pending") {
108674
108514
  const reason = result.last_result?.reason;
108675
108515
  const requiresNetworkAccess = isNetworkFailure(reason);
108676
- throw new ContextError(ExitCode.ExternalToolError, requiresNetworkAccess ? "runtime event delivery could not reach the configured sink" : "runtime event delivery was rejected by the configured sink", {
108516
+ throw new ContextError(ExitCode.ExternalToolError, requiresNetworkAccess ? "runtime event delivery could not reach the configured sink" : reason === "invalid_batch" ? "local telemetry bridge rejected the batch before network delivery; update the sink CLI for protocol compatibility" : "runtime event delivery was rejected by the configured sink", {
108677
108517
  category: ErrorCategory.ExternalToolFailed,
108678
108518
  reason_code: requiresNetworkAccess ? "runtime-events-network-unavailable" : "runtime-events-delivery-failed",
108679
108519
  pending_count: result.pending_count,
@@ -108702,10 +108542,10 @@ init_actionInputWorkspace();
108702
108542
 
108703
108543
  // src/project/actionCompletionOutput.ts
108704
108544
  init_atomicWrite();
108705
- var import_yaml43 = __toESM(require_dist(), 1);
108545
+ var import_yaml44 = __toESM(require_dist(), 1);
108706
108546
  import { Buffer as Buffer4 } from "node:buffer";
108707
- import { createHash as createHash29 } from "node:crypto";
108708
- import { join as join98 } from "node:path";
108547
+ import { createHash as createHash30 } from "node:crypto";
108548
+ import { join as join100 } from "node:path";
108709
108549
  var INLINE_LIMIT = 16 * 1024;
108710
108550
  function record4(value) {
108711
108551
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
@@ -108718,7 +108558,7 @@ function shortText(value, limit = 400) {
108718
108558
  }
108719
108559
  function serializeActionCompletion(value, format2) {
108720
108560
  return format2 === "json" ? `${JSON.stringify(value, null, 2)}
108721
- ` : import_yaml43.default.stringify(value);
108561
+ ` : import_yaml44.default.stringify(value);
108722
108562
  }
108723
108563
  async function prepareActionCompletionOutput(input) {
108724
108564
  if (input.verbose)
@@ -108730,9 +108570,9 @@ async function prepareActionCompletionOutput(input) {
108730
108570
  const production = typeof result.stage_state === "string";
108731
108571
  if (production && Buffer4.byteLength(full) <= INLINE_LIMIT)
108732
108572
  return input.result;
108733
- const digest6 = createHash29("sha256").update(full).digest("hex");
108734
- const root2 = join98(input.projectRoot, ".tmp/context-runtime/action-results");
108735
- const resultFile = join98(root2, `${digest6}.json`);
108573
+ const digest6 = createHash30("sha256").update(full).digest("hex");
108574
+ const root2 = join100(input.projectRoot, ".tmp/context-runtime/action-results");
108575
+ const resultFile = join100(root2, `${digest6}.json`);
108736
108576
  await atomicWriteFile(resultFile, full);
108737
108577
  if (production)
108738
108578
  return {
@@ -108743,7 +108583,7 @@ async function prepareActionCompletionOutput(input) {
108743
108583
  ...pick(result, ["next", "next_preparation"])
108744
108584
  };
108745
108585
  const next2 = record4(result.next) ?? record4(record4(result.workflow)?.current) ?? record4(record4(result.continuation)?.next);
108746
- const nextFile = next2 === undefined ? undefined : join98(root2, `${digest6}.next.json`);
108586
+ const nextFile = next2 === undefined ? undefined : join100(root2, `${digest6}.next.json`);
108747
108587
  if (nextFile !== undefined)
108748
108588
  await atomicWriteFile(nextFile, serializeActionCompletion(next2, "json"));
108749
108589
  const outcomes = (Array.isArray(result.outcomes) ? result.outcomes : []).map(record4).filter((item) => item !== undefined);
@@ -108784,7 +108624,7 @@ async function prepareActionCompletionOutput(input) {
108784
108624
  ]),
108785
108625
  next_route: next2 === undefined ? null : {
108786
108626
  file: nextFile,
108787
- digest: `sha256:${createHash29("sha256").update(serializeActionCompletion(next2, "json")).digest("hex")}`,
108627
+ digest: `sha256:${createHash30("sha256").update(serializeActionCompletion(next2, "json")).digest("hex")}`,
108788
108628
  ...pick(next2, ["revision", "node", "availability"]),
108789
108629
  commands: next2.commands,
108790
108630
  gate: next2.gate === undefined ? undefined : pick(record4(next2.gate), ["id", "resolution", "delegatable"])
@@ -108831,7 +108671,7 @@ init_errors3();
108831
108671
  init_cliFeedback();
108832
108672
  init_exitCode();
108833
108673
  init_productionFeedback();
108834
- var import_yaml44 = __toESM(require_dist(), 1);
108674
+ var import_yaml45 = __toESM(require_dist(), 1);
108835
108675
  import { relative as relative24, resolve as resolve32 } from "node:path";
108836
108676
  async function completeCurrentProductionAction(input) {
108837
108677
  return withProductionFeedback({ operation: "action", file: input.submissionPath }, () => withProjectWriteLock(input.projectRoot, "production-action", async () => {
@@ -108850,7 +108690,7 @@ async function completeCurrentProductionAction(input) {
108850
108690
  throw invalid3("Production requires a stage-local manifest file, not stdin.");
108851
108691
  const path3 = relative24(resolve32(input.projectRoot, productionAgentDirectory(stage.id)), resolve32(input.cwd, input.submissionPath));
108852
108692
  const manifest = await readProductionFile({ projectRoot: input.projectRoot, stage: stage.id, path: path3 });
108853
- const value = import_yaml44.default.parse(manifest.text, { uniqueKeys: true });
108693
+ const value = import_yaml45.default.parse(manifest.text, { uniqueKeys: true });
108854
108694
  if (!value || typeof value !== "object" || !("stage" in value) || value.stage !== stage.id) {
108855
108695
  throw invalid3("The manifest must identify the current production stage. No tasks were saved.");
108856
108696
  }
@@ -108997,7 +108837,7 @@ init_errors3();
108997
108837
  init_cliFeedback();
108998
108838
  init_exitCode();
108999
108839
  init_productionFeedback();
109000
- var import_yaml45 = __toESM(require_dist(), 1);
108840
+ var import_yaml46 = __toESM(require_dist(), 1);
109001
108841
  import { relative as relative25, resolve as resolve33 } from "node:path";
109002
108842
  var productionKnownTasksSchema = productionPlanInputSchema.omit({ stage: true });
109003
108843
  async function prepareKnownProductionTasks(input) {
@@ -109012,7 +108852,7 @@ async function prepareKnownProductionTasks(input) {
109012
108852
  input_schema: zodToJsonSchema(productionKnownTasksSchema, { $refStrategy: "none" }),
109013
108853
  next_action: { command: "context status --format json" }
109014
108854
  });
109015
- const plan = productionKnownTasksSchema.parse(import_yaml45.default.parse(fixed2.text, { uniqueKeys: true }));
108855
+ const plan = productionKnownTasksSchema.parse(import_yaml46.default.parse(fixed2.text, { uniqueKeys: true }));
109016
108856
  const request = await productionPlanningRequest(input.projectRoot);
109017
108857
  let stage = await readProductionStage(input.projectRoot);
109018
108858
  if (!request || stage?.report_approved || (stage ? ![stage.id, request.revision].includes(input.revision) : request.revision !== input.revision)) {
@@ -109022,7 +108862,7 @@ async function prepareKnownProductionTasks(input) {
109022
108862
  await prepareCurrentProductionStage({ projectRoot: input.projectRoot, revision: input.revision });
109023
108863
  stage = await readProductionStage(input.projectRoot);
109024
108864
  }
109025
- const text10 = import_yaml45.default.stringify({ ...plan, stage: stage.id });
108865
+ const text10 = import_yaml46.default.stringify({ ...plan, stage: stage.id });
109026
108866
  const result = await submitProductionPlan({
109027
108867
  projectRoot: input.projectRoot,
109028
108868
  stage: stage.id,
@@ -109144,9 +108984,9 @@ function registerProjectActionCommands(program2) {
109144
108984
  // src/commands/cleanClaudePluginCache.ts
109145
108985
  init_cliFeedback();
109146
108986
  import { existsSync as existsSync27 } from "node:fs";
109147
- import { readdir as readdir25, rm as rm20 } from "node:fs/promises";
108987
+ import { readdir as readdir26, rm as rm20 } from "node:fs/promises";
109148
108988
  import { homedir } from "node:os";
109149
- import { join as join99 } from "node:path";
108989
+ import { join as join101 } from "node:path";
109150
108990
  var ORPHAN_MARKER = ".orphaned_at";
109151
108991
  var CLAUDE_PLUGIN_CACHE_ROOT_ENV = "C4A_CLAUDE_PLUGIN_CACHE_ROOT";
109152
108992
  var CLAUDE_PLUGIN_CACHE_HOME_ENV = "C4A_CLAUDE_PLUGIN_CACHE_HOME";
@@ -109159,23 +108999,23 @@ async function cleanClaudePluginCache(opts = {}) {
109159
108999
  lines.push("· claude plugin cache: missing — nothing to clean");
109160
109000
  return { lines, removed, scanned };
109161
109001
  }
109162
- const marketplaces = await readdir25(cacheRoot, { withFileTypes: true });
109002
+ const marketplaces = await readdir26(cacheRoot, { withFileTypes: true });
109163
109003
  for (const mp of marketplaces) {
109164
109004
  if (!mp.isDirectory())
109165
109005
  continue;
109166
- const mpDir = join99(cacheRoot, mp.name);
109167
- const plugins = await readdir25(mpDir, { withFileTypes: true });
109006
+ const mpDir = join101(cacheRoot, mp.name);
109007
+ const plugins = await readdir26(mpDir, { withFileTypes: true });
109168
109008
  for (const pl of plugins) {
109169
109009
  if (!pl.isDirectory())
109170
109010
  continue;
109171
- const plDir = join99(mpDir, pl.name);
109172
- const versions = await readdir25(plDir, { withFileTypes: true });
109011
+ const plDir = join101(mpDir, pl.name);
109012
+ const versions = await readdir26(plDir, { withFileTypes: true });
109173
109013
  for (const ver of versions) {
109174
109014
  if (!ver.isDirectory())
109175
109015
  continue;
109176
109016
  scanned += 1;
109177
- const verDir = join99(plDir, ver.name);
109178
- const markerPath = join99(verDir, ORPHAN_MARKER);
109017
+ const verDir = join101(plDir, ver.name);
109018
+ const markerPath = join101(verDir, ORPHAN_MARKER);
109179
109019
  if (!existsSync27(markerPath))
109180
109020
  continue;
109181
109021
  const label2 = `${mp.name}/${pl.name}/${ver.name}`;
@@ -109207,11 +109047,11 @@ function resolveClaudePluginCacheRoot(opts) {
109207
109047
  if (explicitRoot)
109208
109048
  return explicitRoot;
109209
109049
  const home = opts.home ?? process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV] ?? homedir();
109210
- return join99(home, ".claude", "plugins", "cache");
109050
+ return join101(home, ".claude", "plugins", "cache");
109211
109051
  }
109212
109052
  async function isEmptyDir(dir) {
109213
109053
  try {
109214
- const entries2 = await readdir25(dir);
109054
+ const entries2 = await readdir26(dir);
109215
109055
  return entries2.length === 0;
109216
109056
  } catch {
109217
109057
  return false;
@@ -109239,13 +109079,13 @@ init_exitCode();
109239
109079
 
109240
109080
  // src/lib/packageVersion.ts
109241
109081
  import { existsSync as existsSync28, readFileSync as readFileSync9 } from "node:fs";
109242
- import { dirname as dirname42, join as join100 } from "node:path";
109082
+ import { dirname as dirname41, join as join102 } from "node:path";
109243
109083
  import { fileURLToPath as fileURLToPath7 } from "node:url";
109244
109084
  function readPackageVersion() {
109245
109085
  try {
109246
- let dir = dirname42(fileURLToPath7(import.meta.url));
109086
+ let dir = dirname41(fileURLToPath7(import.meta.url));
109247
109087
  for (let depth = 0;depth < 8; depth += 1) {
109248
- const packagePath = join100(dir, "package.json");
109088
+ const packagePath = join102(dir, "package.json");
109249
109089
  if (existsSync28(packagePath)) {
109250
109090
  const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
109251
109091
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -109253,7 +109093,7 @@ function readPackageVersion() {
109253
109093
  }
109254
109094
  break;
109255
109095
  }
109256
- const parent = dirname42(dir);
109096
+ const parent = dirname41(dir);
109257
109097
  if (parent === dir)
109258
109098
  break;
109259
109099
  dir = parent;
@@ -109268,24 +109108,24 @@ function readPackageVersion() {
109268
109108
 
109269
109109
  // src/project/sourceCommands.ts
109270
109110
  init_src2();
109271
- import { readFile as readFile88 } from "node:fs/promises";
109111
+ import { readFile as readFile90 } from "node:fs/promises";
109272
109112
  import { isAbsolute as isAbsolute22, resolve as resolve37 } from "node:path";
109273
109113
  init_cliFeedback();
109274
109114
  init_errors3();
109275
109115
  init_exitCode();
109276
- var import_yaml49 = __toESM(require_dist(), 1);
109116
+ var import_yaml50 = __toESM(require_dist(), 1);
109277
109117
 
109278
109118
  // src/project/repoSourceRecovery.ts
109279
109119
  init_cliFeedback();
109280
109120
  init_errors3();
109281
109121
  init_exitCode();
109282
- import { execFile as execFile11 } from "node:child_process";
109122
+ import { execFile as execFile12 } from "node:child_process";
109283
109123
  import { existsSync as existsSync29 } from "node:fs";
109284
109124
  import { lstat as lstat12, mkdir as mkdir34, readlink as readlink2, realpath as realpath10, rm as rm21, symlink as symlink4 } from "node:fs/promises";
109285
- import { basename as basename11, dirname as dirname43, isAbsolute as isAbsolute19, relative as relative26, resolve as resolve34 } from "node:path";
109286
- import { promisify as promisify11 } from "node:util";
109125
+ import { basename as basename11, dirname as dirname42, isAbsolute as isAbsolute19, relative as relative26, resolve as resolve34 } from "node:path";
109126
+ import { promisify as promisify12 } from "node:util";
109287
109127
  init_writeLock();
109288
- var execFileAsync6 = promisify11(execFile11);
109128
+ var execFileAsync6 = promisify12(execFile12);
109289
109129
  var RECOVERY_SCHEMA = "context.repository-source-recovery.v1";
109290
109130
  function userInputError3(message, detail = {}) {
109291
109131
  return new ContextError(ExitCode.UserError, message, {
@@ -109467,7 +109307,7 @@ async function cloneCheckout(input) {
109467
109307
  next: `Use local mode with path ${JSON.stringify(target)} after inspecting the existing checkout.`
109468
109308
  });
109469
109309
  }
109470
- await mkdir34(dirname43(target), { recursive: true });
109310
+ await mkdir34(dirname42(target), { recursive: true });
109471
109311
  const cloneArgs = ["clone", "--no-checkout", "--depth=1", "--filter=blob:none", input.remote, target];
109472
109312
  try {
109473
109313
  await execFileAsync6("git", cloneArgs, { encoding: "utf8", maxBuffer: 4 * 1024 * 1024 });
@@ -109516,7 +109356,7 @@ async function bindLocalAlias(input) {
109516
109356
  const stats = await lstat12(alias).catch(() => null);
109517
109357
  if (stats !== null) {
109518
109358
  if (stats.isSymbolicLink()) {
109519
- const actual = resolve34(dirname43(alias), await readlink2(alias));
109359
+ const actual = resolve34(dirname42(alias), await readlink2(alias));
109520
109360
  const actualReal = await realpath10(actual).catch(() => null);
109521
109361
  if (actualReal !== null && actualReal === await realpath10(input.checkout))
109522
109362
  return;
@@ -109532,8 +109372,8 @@ async function bindLocalAlias(input) {
109532
109372
  });
109533
109373
  }
109534
109374
  }
109535
- await mkdir34(dirname43(alias), { recursive: true });
109536
- await symlink4(relative26(dirname43(alias), input.checkout) || ".", alias);
109375
+ await mkdir34(dirname42(alias), { recursive: true });
109376
+ await symlink4(relative26(dirname42(alias), input.checkout) || ".", alias);
109537
109377
  }
109538
109378
  function selectPhysicalGroup(sources, selector) {
109539
109379
  const direct = selectRepoSources(sources, selector);
@@ -109627,11 +109467,11 @@ async function restoreRepositorySources(input) {
109627
109467
  // src/project/sourceDocumentStatus.ts
109628
109468
  init_src2();
109629
109469
  import { existsSync as existsSync30 } from "node:fs";
109630
- import { readFile as readFile81 } from "node:fs/promises";
109631
- import { join as join102 } from "node:path";
109470
+ import { readFile as readFile83 } from "node:fs/promises";
109471
+ import { join as join104 } from "node:path";
109632
109472
  // src/project/sourceCommandViews.ts
109633
- import { readFile as readFile80 } from "node:fs/promises";
109634
- import { join as join101 } from "node:path";
109473
+ import { readFile as readFile82 } from "node:fs/promises";
109474
+ import { join as join103 } from "node:path";
109635
109475
  init_workspace();
109636
109476
  init_documentBatchManifest();
109637
109477
  function repoSourceAgentView(source2) {
@@ -109694,7 +109534,7 @@ async function fileSourceAgentViewWithNextAction(input) {
109694
109534
  };
109695
109535
  }
109696
109536
  function documentSourceManifestPath(source2) {
109697
- return source2.snapshot?.manifest ?? join101(source2.materializedAt, "manifest.json");
109537
+ return source2.snapshot?.manifest ?? join103(source2.materializedAt, "manifest.json");
109698
109538
  }
109699
109539
  async function fileSourceDocumentSiteHint(input) {
109700
109540
  const detection = await detectDocumentSiteFiles({
@@ -109704,7 +109544,7 @@ async function fileSourceDocumentSiteHint(input) {
109704
109544
  let snapshotConfigured = false;
109705
109545
  const manifest = documentSourceManifestPath(input.source);
109706
109546
  try {
109707
- const parsed = parseDocumentSnapshotForSource(JSON.parse(await readFile80(join101(input.projectRoot, manifest), "utf8")), input.source.name);
109547
+ const parsed = parseDocumentSnapshotForSource(JSON.parse(await readFile82(join103(input.projectRoot, manifest), "utf8")), input.source.name);
109708
109548
  snapshotConfigured = manifestUsesMdxJsonDocs(parsed);
109709
109549
  } catch {
109710
109550
  snapshotConfigured = false;
@@ -109736,11 +109576,11 @@ async function larkSourceAgentViewWithNextAction(input) {
109736
109576
  // src/project/sourceDocumentStatus.ts
109737
109577
  init_documentBatchManifest();
109738
109578
  function documentSourceManifestPath2(source2) {
109739
- return source2.snapshot?.manifest ?? join102(source2.materializedAt, "manifest.json");
109579
+ return source2.snapshot?.manifest ?? join104(source2.materializedAt, "manifest.json");
109740
109580
  }
109741
109581
  async function documentSnapshotState(input) {
109742
109582
  const manifest = documentSourceManifestPath2(input.source);
109743
- const manifestPath = join102(input.projectRoot, manifest);
109583
+ const manifestPath = join104(input.projectRoot, manifest);
109744
109584
  if (!existsSync30(manifestPath)) {
109745
109585
  return {
109746
109586
  snapshotReady: false,
@@ -109751,7 +109591,7 @@ async function documentSnapshotState(input) {
109751
109591
  };
109752
109592
  }
109753
109593
  try {
109754
- const parsed = findDocumentSnapshotForSource(JSON.parse(await readFile81(manifestPath, "utf8")), input.source.name);
109594
+ const parsed = findDocumentSnapshotForSource(JSON.parse(await readFile83(manifestPath, "utf8")), input.source.name);
109755
109595
  if (parsed === null) {
109756
109596
  return {
109757
109597
  snapshotReady: false,
@@ -109819,7 +109659,7 @@ async function documentSnapshotState(input) {
109819
109659
  const missing = [
109820
109660
  ...parsed.files.map((file) => file.path),
109821
109661
  ...(parsed.assets ?? []).filter((asset) => asset.content_hash !== undefined).map((asset) => asset.path)
109822
- ].find((path3) => !existsSync30(join102(input.projectRoot, input.source.materializedAt, path3)));
109662
+ ].find((path3) => !existsSync30(join104(input.projectRoot, input.source.materializedAt, path3)));
109823
109663
  if (missing !== undefined) {
109824
109664
  return {
109825
109665
  snapshotReady: false,
@@ -109918,10 +109758,10 @@ init_atomicWrite();
109918
109758
  init_cliFeedback();
109919
109759
  init_errors3();
109920
109760
  init_exitCode();
109921
- var import_yaml46 = __toESM(require_dist(), 1);
109922
- import { createHash as createHash30 } from "node:crypto";
109923
- import { readFile as readFile82, realpath as realpath11 } from "node:fs/promises";
109924
- import { basename as basename12, extname as extname15, isAbsolute as isAbsolute20, join as join103, relative as relative27, resolve as resolve35 } from "node:path";
109761
+ var import_yaml47 = __toESM(require_dist(), 1);
109762
+ import { createHash as createHash31 } from "node:crypto";
109763
+ import { readFile as readFile84, realpath as realpath11 } from "node:fs/promises";
109764
+ import { basename as basename12, extname as extname15, isAbsolute as isAbsolute20, join as join105, relative as relative27, resolve as resolve35 } from "node:path";
109925
109765
  init_writeLock();
109926
109766
  var SOURCE_NAME_PATTERN3 = /^[a-z0-9][a-z0-9._-]*$/u;
109927
109767
  function isDateSourceNamespace(value) {
@@ -109950,7 +109790,7 @@ function defaultLarkModule(input) {
109950
109790
  if (titleSlug.length > 0)
109951
109791
  return titleSlug;
109952
109792
  }
109953
- const opaqueSlug = (kind, identity) => `${kind}-${createHash30("sha256").update(identity).digest("hex").slice(0, 12)}`;
109793
+ const opaqueSlug = (kind, identity) => `${kind}-${createHash31("sha256").update(identity).digest("hex").slice(0, 12)}`;
109954
109794
  if (input.url !== undefined) {
109955
109795
  try {
109956
109796
  const parsed = new URL(input.url);
@@ -110017,8 +109857,8 @@ function assertSafeFileInclude(value) {
110017
109857
  }
110018
109858
  async function readRegistryDocument(projectRoot, registryPath2) {
110019
109859
  try {
110020
- const content3 = await readFile82(join103(projectRoot, registryPath2), "utf8");
110021
- return content3.trim().length === 0 ? { sources: [] } : import_yaml46.default.parse(content3);
109860
+ const content3 = await readFile84(join105(projectRoot, registryPath2), "utf8");
109861
+ return content3.trim().length === 0 ? { sources: [] } : import_yaml47.default.parse(content3);
110022
109862
  } catch (error) {
110023
109863
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
110024
109864
  return { sources: [] };
@@ -110135,7 +109975,7 @@ async function addFileSourceUnlocked(input) {
110135
109975
  const record6 = entry2;
110136
109976
  return record6.name !== input.name && record6.id !== input.name;
110137
109977
  }), nextEntry];
110138
- await atomicWriteFile(join103(input.projectRoot, DEFAULT_FILE_SOURCES_REGISTRY_PATH), import_yaml46.default.stringify({ sources: nextSources }));
109978
+ await atomicWriteFile(join105(input.projectRoot, DEFAULT_FILE_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
110139
109979
  const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
110140
109980
  const entry = updated.files.find((source2) => source2.name === input.name || source2.id === input.name);
110141
109981
  if (entry === undefined) {
@@ -110191,7 +110031,7 @@ async function addLarkSourceUnlocked(input) {
110191
110031
  const record6 = entry2;
110192
110032
  return record6.name !== input.name && record6.id !== input.name;
110193
110033
  }), nextEntry];
110194
- await atomicWriteFile(join103(input.projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH), import_yaml46.default.stringify({ sources: nextSources }));
110034
+ await atomicWriteFile(join105(input.projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
110195
110035
  const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
110196
110036
  const entry = updated.larks.find((source2) => source2.name === input.name || source2.id === input.name);
110197
110037
  if (entry === undefined) {
@@ -110297,12 +110137,12 @@ function parseBatchItem(value, index2) {
110297
110137
  const url = optionalString(record6, "url", path3);
110298
110138
  const docToken = optionalString(record6, "docToken", path3);
110299
110139
  const wikiToken = optionalString(record6, "wikiToken", path3);
110300
- const title = optionalString(record6, "title", path3);
110140
+ const title2 = optionalString(record6, "title", path3);
110301
110141
  const module = optionalString(record6, "module", path3) ?? defaultLarkModule({
110302
110142
  ...url !== undefined ? { url } : {},
110303
110143
  ...docToken !== undefined ? { docToken } : {},
110304
110144
  ...wikiToken !== undefined ? { wikiToken } : {},
110305
- ...title !== undefined ? { title } : {}
110145
+ ...title2 !== undefined ? { title: title2 } : {}
110306
110146
  });
110307
110147
  return {
110308
110148
  type,
@@ -110310,7 +110150,7 @@ function parseBatchItem(value, index2) {
110310
110150
  ...url !== undefined ? { url } : {},
110311
110151
  ...docToken !== undefined ? { docToken } : {},
110312
110152
  ...wikiToken !== undefined ? { wikiToken } : {},
110313
- ...title !== undefined ? { title } : {}
110153
+ ...title2 !== undefined ? { title: title2 } : {}
110314
110154
  };
110315
110155
  }
110316
110156
  function parseBatchPayload(payload) {
@@ -110391,11 +110231,11 @@ init_candidateLedger();
110391
110231
  init_documentBatchManifest();
110392
110232
  init_workspace();
110393
110233
  init_writeLock();
110394
- var import_yaml47 = __toESM(require_dist(), 1);
110234
+ var import_yaml48 = __toESM(require_dist(), 1);
110395
110235
  import { existsSync as existsSync31 } from "node:fs";
110396
- import { createHash as createHash31 } from "node:crypto";
110397
- import { readFile as readFile83, readdir as readdir26, rm as rm22 } from "node:fs/promises";
110398
- import { isAbsolute as isAbsolute21, join as join104, relative as relative28, resolve as resolve36, sep as sep8 } from "node:path";
110236
+ import { createHash as createHash32 } from "node:crypto";
110237
+ import { readFile as readFile85, readdir as readdir27, rm as rm22 } from "node:fs/promises";
110238
+ import { isAbsolute as isAbsolute21, join as join106, relative as relative28, resolve as resolve36, sep as sep8 } from "node:path";
110399
110239
  function sourceIdentity(source2) {
110400
110240
  if (source2.kind === "source.collection")
110401
110241
  return;
@@ -110427,10 +110267,10 @@ function collectStrings(value, output) {
110427
110267
  }
110428
110268
  }
110429
110269
  async function yamlReferences(input) {
110430
- const absolutePath = join104(input.projectRoot, input.path);
110270
+ const absolutePath = join106(input.projectRoot, input.path);
110431
110271
  if (!existsSync31(absolutePath))
110432
110272
  return false;
110433
- const parsed = import_yaml47.default.parse(await readFile83(absolutePath, "utf8"));
110273
+ const parsed = import_yaml48.default.parse(await readFile85(absolutePath, "utf8"));
110434
110274
  const strings = [];
110435
110275
  collectStrings(parsed, strings);
110436
110276
  return strings.some((value) => stringReferencesSource(value, input.source));
@@ -110556,11 +110396,11 @@ async function registryRemovalWrite(projectRoot, source2) {
110556
110396
  const path3 = registryPath2(source2.type);
110557
110397
  if (path3 === null)
110558
110398
  return;
110559
- const absolutePath = join104(projectRoot, path3);
110560
- const document4 = existsSync31(absolutePath) ? import_yaml47.default.parse(await readFile83(absolutePath, "utf8")) : { sources: [] };
110399
+ const absolutePath = join106(projectRoot, path3);
110400
+ const document4 = existsSync31(absolutePath) ? import_yaml48.default.parse(await readFile85(absolutePath, "utf8")) : { sources: [] };
110561
110401
  return {
110562
110402
  path: absolutePath,
110563
- bytes: import_yaml47.default.stringify(removeDocumentEntry(document4, source2))
110403
+ bytes: import_yaml48.default.stringify(removeDocumentEntry(document4, source2))
110564
110404
  };
110565
110405
  }
110566
110406
  function safeManagedMaterializedPath(projectRoot, source2) {
@@ -110575,7 +110415,7 @@ function safeManagedMaterializedPath(projectRoot, source2) {
110575
110415
  return absolute;
110576
110416
  }
110577
110417
  function safeManagedManifestPath(projectRoot, source2) {
110578
- const manifest = source2.manifest ?? join104(source2.materializedAt, "manifest.json");
110418
+ const manifest = source2.manifest ?? join106(source2.materializedAt, "manifest.json");
110579
110419
  if (isAbsolute21(manifest))
110580
110420
  throw unsafeOwnership(source2, manifest);
110581
110421
  const absolute = resolve36(projectRoot, manifest);
@@ -110610,7 +110450,7 @@ function projectRelative(projectRoot, path3) {
110610
110450
  return relative28(projectRoot, path3).split(sep8).join("/");
110611
110451
  }
110612
110452
  function digest6(value) {
110613
- return `sha256:${createHash31("sha256").update(JSON.stringify(value)).digest("hex")}`;
110453
+ return `sha256:${createHash32("sha256").update(JSON.stringify(value)).digest("hex")}`;
110614
110454
  }
110615
110455
  async function sharedMaterializedOwners(projectRoot, source2) {
110616
110456
  const target = safeManagedMaterializedPath(projectRoot, source2);
@@ -110704,7 +110544,7 @@ async function createRemovalPlan(projectRoot, selector) {
110704
110544
  source: source2,
110705
110545
  registry: registryPath2(source2.type),
110706
110546
  registryBytes: registryWrite?.bytes ?? null,
110707
- managedBytes: source2.type === "note" || source2.type === "sessions" ? await readFile83(absoluteRemovals[0], "utf8") : null,
110547
+ managedBytes: source2.type === "note" || source2.type === "sessions" ? await readFile85(absoluteRemovals[0], "utf8") : null,
110708
110548
  references,
110709
110549
  cleanup,
110710
110550
  manifestBytes: manifestWrite?.bytes ?? null
@@ -110737,10 +110577,10 @@ function publicRemovalResult(plan, action) {
110737
110577
  };
110738
110578
  }
110739
110579
  async function pruneExtractRuntime(projectRoot, source2) {
110740
- const fingerprintPath = join104(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
110580
+ const fingerprintPath = join106(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
110741
110581
  const removedPhaseIds = new Set;
110742
110582
  if (existsSync31(fingerprintPath)) {
110743
- const parsed = JSON.parse(await readFile83(fingerprintPath, "utf8"));
110583
+ const parsed = JSON.parse(await readFile85(fingerprintPath, "utf8"));
110744
110584
  const phases = parsed.phases ?? {};
110745
110585
  const next2 = Object.fromEntries(Object.entries(phases).filter(([phaseId, raw]) => {
110746
110586
  if (raw === null || typeof raw !== "object" || Array.isArray(raw))
@@ -110754,27 +110594,27 @@ async function pruneExtractRuntime(projectRoot, source2) {
110754
110594
  await atomicWriteFile(fingerprintPath, `${JSON.stringify({ ...parsed, phases: next2 }, null, 2)}
110755
110595
  `);
110756
110596
  }
110757
- const phaseOwnershipPath = join104(projectRoot, ".tmp/context-runtime/extract/custom-phase-candidates.json");
110597
+ const phaseOwnershipPath = join106(projectRoot, ".tmp/context-runtime/extract/custom-phase-candidates.json");
110758
110598
  if (existsSync31(phaseOwnershipPath) && removedPhaseIds.size > 0) {
110759
- const parsed = JSON.parse(await readFile83(phaseOwnershipPath, "utf8"));
110599
+ const parsed = JSON.parse(await readFile85(phaseOwnershipPath, "utf8"));
110760
110600
  const phases = Object.fromEntries(Object.entries(parsed.phases ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
110761
110601
  await atomicWriteFile(phaseOwnershipPath, `${JSON.stringify({ ...parsed, phases }, null, 2)}
110762
110602
  `);
110763
110603
  }
110764
- const symbolPath = join104(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
110604
+ const symbolPath = join106(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
110765
110605
  if (existsSync31(symbolPath)) {
110766
- const parsed = JSON.parse(await readFile83(symbolPath, "utf8"));
110606
+ const parsed = JSON.parse(await readFile85(symbolPath, "utf8"));
110767
110607
  const symbols = Array.isArray(parsed.symbols) ? parsed.symbols.filter((entry) => entry === null || typeof entry !== "object" || Array.isArray(entry) || entry.source !== source2.name) : [];
110768
110608
  const phaseFingerprints = Object.fromEntries(Object.entries(parsed.phaseFingerprints ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
110769
110609
  await atomicWriteFile(symbolPath, `${JSON.stringify({ ...parsed, phaseFingerprints, symbols }, null, 2)}
110770
110610
  `);
110771
110611
  }
110772
- const snapshotRoot = join104(projectRoot, ".tmp/context-runtime/extract/candidates");
110612
+ const snapshotRoot = join106(projectRoot, ".tmp/context-runtime/extract/candidates");
110773
110613
  const visit4 = async (directory) => {
110774
110614
  if (!existsSync31(directory))
110775
110615
  return;
110776
- for (const entry of await readdir26(directory, { withFileTypes: true })) {
110777
- const path3 = join104(directory, entry.name);
110616
+ for (const entry of await readdir27(directory, { withFileTypes: true })) {
110617
+ const path3 = join106(directory, entry.name);
110778
110618
  if (entry.isDirectory()) {
110779
110619
  await visit4(path3);
110780
110620
  continue;
@@ -110782,7 +110622,7 @@ async function pruneExtractRuntime(projectRoot, source2) {
110782
110622
  if (!entry.isFile() || !entry.name.endsWith(".json"))
110783
110623
  continue;
110784
110624
  try {
110785
- const parsed = JSON.parse(await readFile83(path3, "utf8"));
110625
+ const parsed = JSON.parse(await readFile85(path3, "utf8"));
110786
110626
  const refs = Array.isArray(parsed.source_refs) ? parsed.source_refs : [];
110787
110627
  if (parsed.source === source2.name || refs.some((ref2) => typeof ref2 === "string" && stringReferencesSource(ref2, source2))) {
110788
110628
  await rm22(path3, { force: true });
@@ -110824,7 +110664,7 @@ async function removeProjectSource(input) {
110824
110664
  });
110825
110665
  }
110826
110666
  await applyAtomicFileBatch({
110827
- transactionRoot: join104(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
110667
+ transactionRoot: join106(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
110828
110668
  writes: [...plan.registryWrite === undefined ? [] : [plan.registryWrite], ...plan.manifestWrite !== undefined ? [plan.manifestWrite] : []],
110829
110669
  removals: plan.absoluteRemovals
110830
110670
  });
@@ -110840,7 +110680,7 @@ init_workspace();
110840
110680
  init_writeLock();
110841
110681
  init_durableMultiFileTransaction();
110842
110682
  init_durableSingleFileTransaction();
110843
- import { readFile as readFile84 } from "node:fs/promises";
110683
+ import { readFile as readFile86 } from "node:fs/promises";
110844
110684
  import ts from "typescript";
110845
110685
  function generateSourceConfiguration(text10, selected) {
110846
110686
  const file = ts.createSourceFile("index.ts", text10, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
@@ -110980,7 +110820,7 @@ function generateSourceConfiguration(text10, selected) {
110980
110820
  async function configureRegisteredSources(projectRoot, selected) {
110981
110821
  return withProjectWriteLock(projectRoot, "source-project-configuration", async () => {
110982
110822
  const path3 = "src/index.ts";
110983
- const config = JSON.parse(await readFile84(await safeProjectTarget(projectRoot, "package.json"), "utf8"));
110823
+ const config = JSON.parse(await readFile86(await safeProjectTarget(projectRoot, "package.json"), "utf8"));
110984
110824
  if (config.context?.entry !== path3)
110985
110825
  return {
110986
110826
  status: "manual",
@@ -110989,7 +110829,7 @@ async function configureRegisteredSources(projectRoot, selected) {
110989
110829
  sources: selected
110990
110830
  };
110991
110831
  const target = await safeProjectTarget(projectRoot, path3);
110992
- const text10 = await readFile84(target, "utf8");
110832
+ const text10 = await readFile86(target, "utf8");
110993
110833
  const updated = generateSourceConfiguration(text10, selected);
110994
110834
  if (updated === undefined)
110995
110835
  return {
@@ -111069,7 +110909,7 @@ async function readIncludeList(projectRoot, path3) {
111069
110909
  }
111070
110910
  let content3;
111071
110911
  try {
111072
- content3 = await readFile88(isAbsolute22(trimmed) ? resolve37(trimmed) : resolve37(projectRoot, trimmed), "utf8");
110912
+ content3 = await readFile90(isAbsolute22(trimmed) ? resolve37(trimmed) : resolve37(projectRoot, trimmed), "utf8");
111073
110913
  } catch (error) {
111074
110914
  throw new ContextError(ExitCode.UserError, `cannot read include list: ${trimmed}`, {
111075
110915
  category: ErrorCategory.UserInputInvalid,
@@ -111137,7 +110977,7 @@ function writeFormatted(value, format2) {
111137
110977
  return;
111138
110978
  }
111139
110979
  if (format2 === "yaml") {
111140
- process.stdout.write(import_yaml49.default.stringify(value));
110980
+ process.stdout.write(import_yaml50.default.stringify(value));
111141
110981
  return;
111142
110982
  }
111143
110983
  process.stdout.write(renderTable2(value));
@@ -111327,14 +111167,14 @@ report shown above. A direct maintenance call outside that Route may omit it.
111327
111167
  const url = optionalString2(options.url);
111328
111168
  const docToken = optionalString2(options.docToken);
111329
111169
  const wikiToken = optionalString2(options.wikiToken);
111330
- const title = optionalString2(options.title);
111170
+ const title2 = optionalString2(options.title);
111331
111171
  const requestedModule = optionalString2(options.module);
111332
111172
  const batchMode = sourceNamespace.generated || isDateSourceNamespace(sourceNamespace.name) || requestedModule !== undefined;
111333
111173
  const module = batchMode ? requestedModule ?? defaultLarkModule({
111334
111174
  ...url !== undefined ? { url } : {},
111335
111175
  ...docToken !== undefined ? { docToken } : {},
111336
111176
  ...wikiToken !== undefined ? { wikiToken } : {},
111337
- ...title !== undefined ? { title } : {}
111177
+ ...title2 !== undefined ? { title: title2 } : {}
111338
111178
  }) : undefined;
111339
111179
  const sourceName = module === undefined ? sourceNamespace.name : `${sourceNamespace.name}/${module}`;
111340
111180
  const result = await addLarkSource({
@@ -111344,7 +111184,7 @@ report shown above. A direct maintenance call outside that Route may omit it.
111344
111184
  ...url !== undefined ? { url } : {},
111345
111185
  ...docToken !== undefined ? { docToken } : {},
111346
111186
  ...wikiToken !== undefined ? { wikiToken } : {},
111347
- ...title !== undefined ? { title } : {}
111187
+ ...title2 !== undefined ? { title: title2 } : {}
111348
111188
  });
111349
111189
  writeFormatted(options.configure ? { ...result, configuration: await configureRegisteredSources(projectRoot, [{ type: "lark", name: sourceName }]) } : result, format2);
111350
111190
  });
@@ -111458,19 +111298,19 @@ report shown above. A direct maintenance call outside that Route may omit it.
111458
111298
  init_cliFeedback();
111459
111299
  init_errors3();
111460
111300
  init_exitCode();
111461
- var import_yaml51 = __toESM(require_dist(), 1);
111301
+ var import_yaml52 = __toESM(require_dist(), 1);
111462
111302
 
111463
111303
  // src/project/productionSkillCatalog.ts
111464
111304
  init_errors3();
111465
111305
  init_cliFeedback();
111466
111306
  init_exitCode();
111467
- var import_yaml50 = __toESM(require_dist(), 1);
111307
+ var import_yaml51 = __toESM(require_dist(), 1);
111468
111308
  import { constants as constants6, existsSync as existsSync32 } from "node:fs";
111469
- import { lstat as lstat13, open as open5, readdir as readdir27 } from "node:fs/promises";
111470
- import { dirname as dirname44, join as join106, resolve as resolve38 } from "node:path";
111309
+ import { lstat as lstat13, open as open5, readdir as readdir28 } from "node:fs/promises";
111310
+ import { dirname as dirname43, join as join108, resolve as resolve38 } from "node:path";
111471
111311
  import { fileURLToPath as fileURLToPath8 } from "node:url";
111472
111312
  function bundledSkillRoot() {
111473
- const directory = dirname44(fileURLToPath8(import.meta.url));
111313
+ const directory = dirname43(fileURLToPath8(import.meta.url));
111474
111314
  const root2 = [resolve38(directory, "indexers/bundles"), resolve38(directory, "../../dist/indexers/bundles")].find((path3) => existsSync32(path3));
111475
111315
  if (!root2)
111476
111316
  throw new TypeError("Bundled skill files are unavailable; build or reinstall the Context CLI.");
@@ -111494,10 +111334,10 @@ async function listProductionSkills(root2) {
111494
111334
  }
111495
111335
  }
111496
111336
  async function readProductionSkills(root2) {
111497
- const entries2 = (await readdir27(root2, { withFileTypes: true })).filter((entry) => entry.isDirectory()).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
111337
+ const entries2 = (await readdir28(root2, { withFileTypes: true })).filter((entry) => entry.isDirectory()).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
111498
111338
  const skills = [];
111499
111339
  for (const directory of entries2) {
111500
- const entry = join106(root2, directory.name, "SKILL.md");
111340
+ const entry = join108(root2, directory.name, "SKILL.md");
111501
111341
  if (!(await lstat13(entry)).isFile())
111502
111342
  throw new TypeError(`Skill entry must be a regular file: ${entry}`);
111503
111343
  const handle2 = await open5(entry, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
@@ -111514,7 +111354,7 @@ async function readProductionSkills(root2) {
111514
111354
  const frontmatter2 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(header);
111515
111355
  if (!frontmatter2)
111516
111356
  throw new TypeError(`Skill entry needs complete frontmatter within 64 KiB: ${entry}`);
111517
- const value = import_yaml50.default.parse(frontmatter2[1], { uniqueKeys: true });
111357
+ const value = import_yaml51.default.parse(frontmatter2[1], { uniqueKeys: true });
111518
111358
  if (!value || typeof value !== "object" || !("name" in value) || typeof value.name !== "string" || !value.name.trim() || !("description" in value) || typeof value.description !== "string" || !value.description.trim()) {
111519
111359
  throw new TypeError(`Skill entry needs a name and description: ${entry}`);
111520
111360
  }
@@ -111577,7 +111417,7 @@ function outputFormat2(options) {
111577
111417
  }
111578
111418
  function writeOutput(value, format2) {
111579
111419
  process.stdout.write(format2 === "json" ? `${JSON.stringify(value, null, 2)}
111580
- ` : import_yaml51.default.stringify(value));
111420
+ ` : import_yaml52.default.stringify(value));
111581
111421
  }
111582
111422
  function registerProjectIndexerCommands(program2) {
111583
111423
  const indexer = program2.command("indexer").description("Discover bundled skills or inspect a benchmark result");
@@ -111632,7 +111472,7 @@ init_exitCode();
111632
111472
  init_workflowProvider();
111633
111473
  init_workspace();
111634
111474
  import { existsSync as existsSync33 } from "node:fs";
111635
- import { dirname as dirname45, resolve as resolve39 } from "node:path";
111475
+ import { dirname as dirname44, resolve as resolve39 } from "node:path";
111636
111476
  function shellQuote7(value) {
111637
111477
  return /^[A-Za-z0-9._/=-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
111638
111478
  }
@@ -111665,10 +111505,10 @@ function readyResult(input, projectRoot, relocation) {
111665
111505
  return {
111666
111506
  schema: "context.entry.v1",
111667
111507
  guidance: {
111668
- knowledge_updates: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/knowledge-updates.md") },
111669
- workspace_prepare: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/workspace-prepare.md") },
111670
- workspace_commit: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/workspace-commit.md") },
111671
- workspace_restore: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/workspace-restore.md") }
111508
+ knowledge_updates: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/knowledge-updates.md") },
111509
+ workspace_prepare: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/workspace-prepare.md") },
111510
+ workspace_commit: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/workspace-commit.md") },
111511
+ workspace_restore: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/workspace-restore.md") }
111672
111512
  },
111673
111513
  state: relocation ? "workspace-relocation-required" : "workspace-ready",
111674
111514
  cwd: resolve39(input.cwd),
@@ -111905,20 +111745,20 @@ init_cliFeedback();
111905
111745
  init_errors3();
111906
111746
  init_exitCode();
111907
111747
  import { existsSync as existsSync35 } from "node:fs";
111908
- import { dirname as dirname47, join as join108, resolve as resolve40 } from "node:path";
111748
+ import { dirname as dirname46, join as join110, resolve as resolve40 } from "node:path";
111909
111749
  import { fileURLToPath as fileURLToPath9 } from "node:url";
111910
111750
 
111911
111751
  // src/project/pluginInstallTargets.ts
111912
111752
  init_cliFeedback();
111913
111753
  init_errors3();
111914
111754
  init_exitCode();
111915
- import { execFile as execFile12 } from "node:child_process";
111755
+ import { execFile as execFile13 } from "node:child_process";
111916
111756
  import { existsSync as existsSync34 } from "node:fs";
111917
- import { cp as cp2, mkdir as mkdir35, readdir as readdir28, readFile as readFile89, rename as rename9, rm as rm23, writeFile as writeFile27 } from "node:fs/promises";
111757
+ import { cp as cp2, mkdir as mkdir35, readdir as readdir29, readFile as readFile91, rename as rename9, rm as rm23, writeFile as writeFile27 } from "node:fs/promises";
111918
111758
  import { homedir as homedir2 } from "node:os";
111919
- import { dirname as dirname46, join as join107 } from "node:path";
111920
- import { promisify as promisify12 } from "node:util";
111921
- var execFileAsync7 = promisify12(execFile12);
111759
+ import { dirname as dirname45, join as join109 } from "node:path";
111760
+ import { promisify as promisify13 } from "node:util";
111761
+ var execFileAsync7 = promisify13(execFile13);
111922
111762
  var MARKETPLACE_NAME = "c4a";
111923
111763
  var PLUGIN_ID = "c4a@c4a";
111924
111764
  var PLUGIN_NAME = "c4a";
@@ -111970,23 +111810,23 @@ async function claudePluginInstalled(pluginId) {
111970
111810
  }
111971
111811
  }
111972
111812
  function codexHome() {
111973
- return process.env.CODEX_HOME?.trim() || join107(homedir2(), ".codex");
111813
+ return process.env.CODEX_HOME?.trim() || join109(homedir2(), ".codex");
111974
111814
  }
111975
111815
  function claudePluginCacheRoot() {
111976
111816
  const explicitRoot = process.env[CLAUDE_PLUGIN_CACHE_ROOT_ENV2]?.trim();
111977
111817
  if (explicitRoot)
111978
111818
  return explicitRoot;
111979
111819
  const home = process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV2]?.trim() || homedir2();
111980
- return join107(home, ".claude", "plugins", "cache");
111820
+ return join109(home, ".claude", "plugins", "cache");
111981
111821
  }
111982
111822
  function sharedSkillsRoot() {
111983
- return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join107(homedir2(), ".agents", "skills");
111823
+ return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join109(homedir2(), ".agents", "skills");
111984
111824
  }
111985
111825
  function claudeSkillsRoot() {
111986
- return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join107(homedir2(), ".claude", "skills");
111826
+ return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join109(homedir2(), ".claude", "skills");
111987
111827
  }
111988
111828
  function cursorPluginRoot() {
111989
- return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join107(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
111829
+ return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join109(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
111990
111830
  }
111991
111831
  function blockHeader(line) {
111992
111832
  const match = line.match(/^\s*\[([^\]]+)\]\s*$/u);
@@ -112037,8 +111877,8 @@ function pruneLegacyCodexConfigContent(content3) {
112037
111877
  `), removed };
112038
111878
  }
112039
111879
  async function pruneLegacyCodexConfig(dryRun, steps) {
112040
- const configPath = join107(codexHome(), "config.toml");
112041
- const current2 = await readFile89(configPath, "utf8").catch(() => "");
111880
+ const configPath = join109(codexHome(), "config.toml");
111881
+ const current2 = await readFile91(configPath, "utf8").catch(() => "");
112042
111882
  if (!current2)
112043
111883
  return;
112044
111884
  const next2 = pruneLegacyCodexConfigContent(current2);
@@ -112054,10 +111894,10 @@ async function pruneLegacyCodexConfig(dryRun, steps) {
112054
111894
  }
112055
111895
  }
112056
111896
  async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, steps) {
112057
- const cacheRoot = join107(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
111897
+ const cacheRoot = join109(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
112058
111898
  if (!existsSync34(cacheRoot))
112059
111899
  return;
112060
- const versions = (await readdir28(cacheRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== keepVersion).map((entry) => entry.name).sort();
111900
+ const versions = (await readdir29(cacheRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== keepVersion).map((entry) => entry.name).sort();
112061
111901
  if (versions.length === 0)
112062
111902
  return;
112063
111903
  steps.push({
@@ -112066,7 +111906,7 @@ async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, ste
112066
111906
  status: dryRun ? "planned" : "ran"
112067
111907
  });
112068
111908
  if (!dryRun)
112069
- await Promise.all(versions.map((version3) => rm23(join107(cacheRoot, version3), { recursive: true, force: true })));
111909
+ await Promise.all(versions.map((version3) => rm23(join109(cacheRoot, version3), { recursive: true, force: true })));
112070
111910
  }
112071
111911
  async function pruneCodexPluginCache(currentVersion, dryRun, steps) {
112072
111912
  await pruneCodexPluginCacheForName(PLUGIN_NAME, currentVersion, dryRun, steps);
@@ -112076,7 +111916,7 @@ async function pruneCodexPluginCache(currentVersion, dryRun, steps) {
112076
111916
  }
112077
111917
  async function isEmptyDir2(dir) {
112078
111918
  try {
112079
- return (await readdir28(dir)).length === 0;
111919
+ return (await readdir29(dir)).length === 0;
112080
111920
  } catch {
112081
111921
  return false;
112082
111922
  }
@@ -112086,19 +111926,19 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
112086
111926
  if (!existsSync34(cacheRoot))
112087
111927
  return;
112088
111928
  const removed = [];
112089
- const marketplaces = await readdir28(cacheRoot, { withFileTypes: true }).catch(() => []);
111929
+ const marketplaces = await readdir29(cacheRoot, { withFileTypes: true }).catch(() => []);
112090
111930
  for (const marketplace of marketplaces) {
112091
111931
  if (!marketplace.isDirectory())
112092
111932
  continue;
112093
- const pluginDir = join107(cacheRoot, marketplace.name, PLUGIN_NAME);
111933
+ const pluginDir = join109(cacheRoot, marketplace.name, PLUGIN_NAME);
112094
111934
  if (!existsSync34(pluginDir))
112095
111935
  continue;
112096
- const versions = await readdir28(pluginDir, { withFileTypes: true }).catch(() => []);
111936
+ const versions = await readdir29(pluginDir, { withFileTypes: true }).catch(() => []);
112097
111937
  for (const version3 of versions) {
112098
111938
  if (!version3.isDirectory())
112099
111939
  continue;
112100
- const versionDir = join107(pluginDir, version3.name);
112101
- if (!existsSync34(join107(versionDir, ORPHAN_MARKER2)))
111940
+ const versionDir = join109(pluginDir, version3.name);
111941
+ if (!existsSync34(join109(versionDir, ORPHAN_MARKER2)))
112102
111942
  continue;
112103
111943
  removed.push(`${marketplace.name}/${PLUGIN_NAME}/${version3.name}`);
112104
111944
  if (!dryRun) {
@@ -112108,7 +111948,7 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
112108
111948
  if (!dryRun && await isEmptyDir2(pluginDir)) {
112109
111949
  await rm23(pluginDir, { recursive: true, force: true });
112110
111950
  }
112111
- const marketplaceDir = join107(cacheRoot, marketplace.name);
111951
+ const marketplaceDir = join109(cacheRoot, marketplace.name);
112112
111952
  if (!dryRun && await isEmptyDir2(marketplaceDir)) {
112113
111953
  await rm23(marketplaceDir, { recursive: true, force: true });
112114
111954
  }
@@ -112129,7 +111969,7 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
112129
111969
  return;
112130
111970
  const removed = [];
112131
111971
  for (const pluginName of LEGACY_PLUGIN_NAMES) {
112132
- const pluginDir = join107(cacheRoot, MARKETPLACE_NAME, pluginName);
111972
+ const pluginDir = join109(cacheRoot, MARKETPLACE_NAME, pluginName);
112133
111973
  if (!existsSync34(pluginDir))
112134
111974
  continue;
112135
111975
  removed.push(`${MARKETPLACE_NAME}/${pluginName}`);
@@ -112146,12 +111986,12 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
112146
111986
  }
112147
111987
  }
112148
111988
  async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
112149
- const manifest = await readFile89(join107(root2, "claude", ".claude-plugin", "plugin.json"), "utf8").then((content3) => JSON.parse(content3)).catch(() => null);
111989
+ const manifest = await readFile91(join109(root2, "claude", ".claude-plugin", "plugin.json"), "utf8").then((content3) => JSON.parse(content3)).catch(() => null);
112150
111990
  const currentVersion = typeof manifest?.version === "string" ? manifest.version : null;
112151
111991
  if (!currentVersion)
112152
111992
  return;
112153
- const pluginDir = join107(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
112154
- const staleVersions = (await readdir28(pluginDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== currentVersion).map((entry) => entry.name);
111993
+ const pluginDir = join109(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
111994
+ const staleVersions = (await readdir29(pluginDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== currentVersion).map((entry) => entry.name);
112155
111995
  if (staleVersions.length === 0)
112156
111996
  return;
112157
111997
  steps.push({
@@ -112160,7 +112000,7 @@ async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
112160
112000
  status: dryRun ? "planned" : "ran"
112161
112001
  });
112162
112002
  if (!dryRun) {
112163
- await Promise.all(staleVersions.map((version3) => rm23(join107(pluginDir, version3), { recursive: true, force: true })));
112003
+ await Promise.all(staleVersions.map((version3) => rm23(join109(pluginDir, version3), { recursive: true, force: true })));
112164
112004
  }
112165
112005
  }
112166
112006
  function enableCodexPluginConfig(content3) {
@@ -112224,36 +112064,36 @@ source = ${JSON.stringify(root2)}
112224
112064
  `;
112225
112065
  }
112226
112066
  async function ensureCodexPluginEnabled() {
112227
- const configPath = join107(codexHome(), "config.toml");
112228
- await mkdir35(dirname46(configPath), { recursive: true });
112229
- const current2 = await readFile89(configPath, "utf8").catch(() => "");
112067
+ const configPath = join109(codexHome(), "config.toml");
112068
+ await mkdir35(dirname45(configPath), { recursive: true });
112069
+ const current2 = await readFile91(configPath, "utf8").catch(() => "");
112230
112070
  const next2 = enableCodexPluginConfig(current2);
112231
112071
  if (next2 !== current2) {
112232
112072
  await writeFile27(configPath, next2, "utf8");
112233
112073
  }
112234
112074
  }
112235
112075
  async function ensureCodexLocalMarketplace(root2) {
112236
- const configPath = join107(codexHome(), "config.toml");
112237
- await mkdir35(dirname46(configPath), { recursive: true });
112238
- const current2 = await readFile89(configPath, "utf8").catch(() => "");
112076
+ const configPath = join109(codexHome(), "config.toml");
112077
+ await mkdir35(dirname45(configPath), { recursive: true });
112078
+ const current2 = await readFile91(configPath, "utf8").catch(() => "");
112239
112079
  const next2 = upsertCodexLocalMarketplaceConfig(current2, root2);
112240
112080
  if (next2 !== current2) {
112241
112081
  await writeFile27(configPath, next2, "utf8");
112242
112082
  }
112243
112083
  }
112244
112084
  async function codexPluginVersion(root2) {
112245
- const manifestPath = join107(root2, "codex", ".codex-plugin", "plugin.json");
112246
- const manifest = JSON.parse(await readFile89(manifestPath, "utf8"));
112085
+ const manifestPath = join109(root2, "codex", ".codex-plugin", "plugin.json");
112086
+ const manifest = JSON.parse(await readFile91(manifestPath, "utf8"));
112247
112087
  if (typeof manifest.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(manifest.version)) {
112248
112088
  throw new Error(`Codex plugin manifest has an invalid version: ${manifestPath}`);
112249
112089
  }
112250
112090
  return manifest.version;
112251
112091
  }
112252
112092
  function codexPluginCacheDir(version3) {
112253
- return join107(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
112093
+ return join109(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
112254
112094
  }
112255
112095
  async function replaceDirectoryFromSource(source2, target) {
112256
- await mkdir35(dirname46(target), { recursive: true });
112096
+ await mkdir35(dirname45(target), { recursive: true });
112257
112097
  const temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
112258
112098
  const previous3 = `${target}.previous-${process.pid}-${Date.now()}`;
112259
112099
  await rm23(temporary, { recursive: true, force: true });
@@ -112273,7 +112113,7 @@ async function replaceDirectoryFromSource(source2, target) {
112273
112113
  }
112274
112114
  }
112275
112115
  async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
112276
- const source2 = join107(root2, "codex");
112116
+ const source2 = join109(root2, "codex");
112277
112117
  const target = codexPluginCacheDir(version3);
112278
112118
  steps.push({
112279
112119
  agent: "codex",
@@ -112285,16 +112125,16 @@ async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
112285
112125
  await replaceDirectoryFromSource(source2, target);
112286
112126
  }
112287
112127
  async function bundledProviderSkillNames(root2) {
112288
- const skillsRoot = join107(root2, "skills");
112289
- const entries2 = await readdir28(skillsRoot, { withFileTypes: true });
112128
+ const skillsRoot = join109(root2, "skills");
112129
+ const entries2 = await readdir29(skillsRoot, { withFileTypes: true });
112290
112130
  const names = [];
112291
112131
  for (const entry of entries2) {
112292
112132
  if (!entry.isDirectory() || entry.name === "context")
112293
112133
  continue;
112294
- const skillPath = join107(skillsRoot, entry.name, "SKILL.md");
112134
+ const skillPath = join109(skillsRoot, entry.name, "SKILL.md");
112295
112135
  if (!existsSync34(skillPath))
112296
112136
  continue;
112297
- const skill = await readFile89(skillPath, "utf8");
112137
+ const skill = await readFile91(skillPath, "utf8");
112298
112138
  if (!/^\s*context-role:\s*["']?indexer-provider["']?\s*$/mu.test(skill))
112299
112139
  continue;
112300
112140
  names.push(entry.name);
@@ -112306,10 +112146,10 @@ async function bundledProviderSkillNames(root2) {
112306
112146
  return names;
112307
112147
  }
112308
112148
  async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps) {
112309
- const sourceRoot2 = join107(root2, "skills");
112149
+ const sourceRoot2 = join109(root2, "skills");
112310
112150
  for (const name3 of await bundledProviderSkillNames(root2)) {
112311
- const source2 = join107(sourceRoot2, name3);
112312
- const target = join107(targetRoot, name3);
112151
+ const source2 = join109(sourceRoot2, name3);
112152
+ const target = join109(targetRoot, name3);
112313
112153
  steps.push({
112314
112154
  agent,
112315
112155
  command: `materialize lifecycle Provider skill: ${shellQuote8(source2)} -> ${shellQuote8(target)}`,
@@ -112320,7 +112160,7 @@ async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps
112320
112160
  }
112321
112161
  }
112322
112162
  async function installCursor(root2, dryRun, steps) {
112323
- const source2 = join107(root2, "cursor");
112163
+ const source2 = join109(root2, "cursor");
112324
112164
  const target = cursorPluginRoot();
112325
112165
  steps.push({
112326
112166
  agent: "cursor",
@@ -112375,12 +112215,12 @@ async function installCodex(root2, dryRun, steps) {
112375
112215
  steps.push({ agent: "codex", command: commandLine("codex", addArgs), status: dryRun ? "planned" : "ran" });
112376
112216
  steps.push({
112377
112217
  agent: "codex",
112378
- command: `ensure ${shellQuote8(join107(codexHome(), "config.toml"))} registers local marketplace ${shellQuote8(MARKETPLACE_NAME)}`,
112218
+ command: `ensure ${shellQuote8(join109(codexHome(), "config.toml"))} registers local marketplace ${shellQuote8(MARKETPLACE_NAME)}`,
112379
112219
  status: dryRun ? "planned" : "ran"
112380
112220
  });
112381
112221
  steps.push({
112382
112222
  agent: "codex",
112383
- command: `ensure ${shellQuote8(join107(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
112223
+ command: `ensure ${shellQuote8(join109(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
112384
112224
  status: dryRun ? "planned" : "ran"
112385
112225
  });
112386
112226
  if (dryRun) {
@@ -112419,10 +112259,10 @@ function pluginAgentOption(value) {
112419
112259
  }
112420
112260
  function packageCandidateDirs() {
112421
112261
  const dirs = [];
112422
- let dir = dirname47(fileURLToPath9(import.meta.url));
112262
+ let dir = dirname46(fileURLToPath9(import.meta.url));
112423
112263
  for (let index2 = 0;index2 < 8; index2++) {
112424
112264
  dirs.push(dir);
112425
- const parent = dirname47(dir);
112265
+ const parent = dirname46(dir);
112426
112266
  if (parent === dir)
112427
112267
  break;
112428
112268
  dir = parent;
@@ -112435,13 +112275,13 @@ function pluginRootCandidates() {
112435
112275
  return [resolve40(envRoot)];
112436
112276
  const candidates = [];
112437
112277
  for (const dir of packageCandidateDirs()) {
112438
- candidates.push(join108(dir, "plugins"));
112439
- candidates.push(join108(dir, "dist", "plugins"));
112278
+ candidates.push(join110(dir, "plugins"));
112279
+ candidates.push(join110(dir, "dist", "plugins"));
112440
112280
  }
112441
112281
  return [...new Set(candidates)];
112442
112282
  }
112443
112283
  function isInstallablePluginRoot(root2) {
112444
- return existsSync35(join108(root2, ".claude-plugin", "marketplace.json")) && existsSync35(join108(root2, ".agents", "plugins", "marketplace.json")) && existsSync35(join108(root2, "claude", ".claude-plugin", "plugin.json")) && existsSync35(join108(root2, "codex", ".codex-plugin", "plugin.json")) && existsSync35(join108(root2, "cursor", ".cursor-plugin", "plugin.json")) && existsSync35(join108(root2, "skills"));
112284
+ return existsSync35(join110(root2, ".claude-plugin", "marketplace.json")) && existsSync35(join110(root2, ".agents", "plugins", "marketplace.json")) && existsSync35(join110(root2, "claude", ".claude-plugin", "plugin.json")) && existsSync35(join110(root2, "codex", ".codex-plugin", "plugin.json")) && existsSync35(join110(root2, "cursor", ".cursor-plugin", "plugin.json")) && existsSync35(join110(root2, "skills"));
112445
112285
  }
112446
112286
  function resolveBundledPluginsRoot() {
112447
112287
  const candidates = pluginRootCandidates();
@@ -112552,32 +112392,32 @@ function formatPluginStatusResult(result) {
112552
112392
  function formatPluginInstallResult(result) {
112553
112393
  const degraded = result.results.some((item) => item.status === "skipped" || item.status === "failed");
112554
112394
  const ready = result.results.filter((item) => item.status === "installed" || item.status === "planned").map((item) => item.agent);
112555
- const body = [
112395
+ const body2 = [
112556
112396
  `marketplace: ${dim(result.pluginsRoot)}`,
112557
112397
  "manual install: use the marketplace path above as the plugin marketplace root."
112558
112398
  ];
112559
112399
  for (const item of result.results) {
112560
112400
  if (item.status === "installed" || item.status === "planned") {
112561
- body.push(`✅ ${item.agent}: ${item.status}`);
112401
+ body2.push(`✅ ${item.agent}: ${item.status}`);
112562
112402
  continue;
112563
112403
  }
112564
112404
  const icon = item.status === "failed" ? "✗" : "⚠";
112565
112405
  const detail = item.message ? ` — ${item.message}` : "";
112566
- body.push(yellow(`${icon} ${item.agent}: ${item.status}${detail}`));
112406
+ body2.push(yellow(`${icon} ${item.agent}: ${item.status}${detail}`));
112567
112407
  if (item.next)
112568
- body.push(yellow(` next: ${item.next}`));
112408
+ body2.push(yellow(` next: ${item.next}`));
112569
112409
  }
112570
112410
  const detailSteps = result.steps.map((step) => ` ${step.agent}: ${step.status} ${step.command}`);
112571
112411
  if (detailSteps.length > 0) {
112572
- body.push("details:");
112573
- body.push(...detailSteps.map(dim));
112412
+ body2.push("details:");
112413
+ body2.push(...detailSteps.map(dim));
112574
112414
  }
112575
112415
  return formatFeedback({
112576
112416
  symbol: result.dryRun ? "·" : degraded ? "⚠" : "✓",
112577
112417
  action: result.dryRun ? "planned" : "installed",
112578
112418
  subject: "context plugin",
112579
112419
  headline: `${ready.length}/${result.agents.length} target(s) ready`,
112580
- body
112420
+ body: body2
112581
112421
  });
112582
112422
  }
112583
112423
 
@@ -112677,29 +112517,29 @@ function inferErrorCategory(message) {
112677
112517
  }
112678
112518
  function readQuickstartPath() {
112679
112519
  try {
112680
- let dir = dirname48(fileURLToPath10(import.meta.url));
112520
+ let dir = dirname47(fileURLToPath10(import.meta.url));
112681
112521
  for (let i2 = 0;i2 < 8; i2++) {
112682
- const candidate = join109(dir, "docs", "quickstart.md");
112522
+ const candidate = join111(dir, "docs", "quickstart.md");
112683
112523
  if (existsSync36(candidate))
112684
112524
  return candidate;
112685
- const pkg = join109(dir, "package.json");
112525
+ const pkg = join111(dir, "package.json");
112686
112526
  if (existsSync36(pkg))
112687
112527
  return candidate;
112688
- const parent = dirname48(dir);
112528
+ const parent = dirname47(dir);
112689
112529
  if (parent === dir)
112690
112530
  break;
112691
112531
  dir = parent;
112692
112532
  }
112693
112533
  } catch {}
112694
- return join109(dirname48(fileURLToPath10(import.meta.url)), "docs", "quickstart.md");
112534
+ return join111(dirname47(fileURLToPath10(import.meta.url)), "docs", "quickstart.md");
112695
112535
  }
112696
112536
  var GREEN = "\x1B[32m";
112697
112537
  var RESET = "\x1B[0m";
112698
112538
  function greenBox(lines) {
112699
112539
  const width = Math.max(...lines.map((line) => line.length));
112700
112540
  const border = `+${"-".repeat(width + 2)}+`;
112701
- const body = lines.map((line) => `| ${line.padEnd(width)} |`);
112702
- return `${GREEN}${[border, ...body, border].join(`
112541
+ const body2 = lines.map((line) => `| ${line.padEnd(width)} |`);
112542
+ return `${GREEN}${[border, ...body2, border].join(`
112703
112543
  `)}${RESET}`;
112704
112544
  }
112705
112545
  function headerHelpText() {