@c4a/context-cli 0.5.29-beta.21 → 0.5.29-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -19189,6 +19189,65 @@ function parseOptionalCommentBlock(input) {
19189
19189
  after: trimBlankEdges(input.lines.slice(end + 1))
19190
19190
  };
19191
19191
  }
19192
+ function stripSummaryBlockquote(value) {
19193
+ const lines = value.split(/\r?\n/u);
19194
+ const allQuoted = lines.every((line) => {
19195
+ const trimmed = line.trimStart();
19196
+ return trimmed.length === 0 || trimmed.startsWith(">");
19197
+ });
19198
+ if (!allQuoted)
19199
+ return value;
19200
+ return lines.map((line) => {
19201
+ const trimmed = line.trimStart();
19202
+ if (!trimmed.startsWith(">"))
19203
+ return "";
19204
+ return trimmed.slice(1).replace(/^\s/u, "");
19205
+ }).join(`
19206
+ `).trim();
19207
+ }
19208
+ function unescapeRawComment(value) {
19209
+ return value.replace(/--\\>/gu, "-->");
19210
+ }
19211
+ function parseOptionalRawBlock(lines, sectionId) {
19212
+ const hiddenStart = lines.findIndex((line) => RAW_OPEN_RE.test(line.trim()));
19213
+ const hiddenEnd = lines.findIndex((line) => RAW_CLOSE_RE.test(line.trim()));
19214
+ const legacyStart = lines.findIndex((line) => LEGACY_RAW_OPEN_RE.test(line.trim()));
19215
+ const legacyEnd = lines.findIndex((line) => LEGACY_RAW_CLOSE_RE.test(line.trim()));
19216
+ if (hiddenStart >= 0 && legacyStart >= 0) {
19217
+ throw new Error(`section ${sectionId} must contain at most one c4a:raw block`);
19218
+ }
19219
+ if (hiddenStart < 0 && hiddenEnd >= 0) {
19220
+ throw new Error(`section ${sectionId} has c4a:raw close without open`);
19221
+ }
19222
+ if (legacyStart < 0 && legacyEnd >= 0) {
19223
+ throw new Error(`section ${sectionId} has raw close without open`);
19224
+ }
19225
+ if (hiddenStart >= 0 && hiddenEnd < 0) {
19226
+ throw new Error(`section ${sectionId} is missing /c4a:raw`);
19227
+ }
19228
+ if (hiddenStart >= 0 && hiddenEnd < hiddenStart) {
19229
+ throw new Error(`section ${sectionId} has /c4a:raw before c4a:raw`);
19230
+ }
19231
+ if (hiddenStart >= 0) {
19232
+ const valueLines = trimBlankEdges(lines.slice(hiddenStart + 1, hiddenEnd));
19233
+ if (valueLines.length === 0) {
19234
+ throw new Error(`section ${sectionId} has empty c4a:raw`);
19235
+ }
19236
+ return {
19237
+ value: unescapeRawComment(valueLines.join(`
19238
+ `).trim()),
19239
+ before: trimBlankEdges(lines.slice(0, hiddenStart)),
19240
+ after: trimBlankEdges(lines.slice(hiddenEnd + 1))
19241
+ };
19242
+ }
19243
+ return parseOptionalCommentBlock({
19244
+ lines,
19245
+ sectionId,
19246
+ name: "raw",
19247
+ open: LEGACY_RAW_OPEN_RE,
19248
+ close: LEGACY_RAW_CLOSE_RE
19249
+ });
19250
+ }
19192
19251
  function parseSectionBody(lines, sectionId) {
19193
19252
  const summary = parseOptionalCommentBlock({
19194
19253
  lines,
@@ -19201,13 +19260,7 @@ function parseSectionBody(lines, sectionId) {
19201
19260
  throw new Error(`section ${sectionId} has content before c4a:summary`);
19202
19261
  }
19203
19262
  const afterSummary = summary.value !== undefined ? summary.after : summary.before;
19204
- const raw = parseOptionalCommentBlock({
19205
- lines: afterSummary,
19206
- sectionId,
19207
- name: "raw",
19208
- open: RAW_OPEN_RE,
19209
- close: RAW_CLOSE_RE
19210
- });
19263
+ const raw = parseOptionalRawBlock(afterSummary, sectionId);
19211
19264
  if (raw.after.length > 0) {
19212
19265
  throw new Error(`section ${sectionId} has content after /c4a:raw`);
19213
19266
  }
@@ -19216,7 +19269,7 @@ function parseSectionBody(lines, sectionId) {
19216
19269
  throw new Error(`section ${sectionId} is missing content`);
19217
19270
  }
19218
19271
  return {
19219
- ...summary.value !== undefined ? { summary: summary.value } : {},
19272
+ ...summary.value !== undefined ? { summary: stripSummaryBlockquote(summary.value) } : {},
19220
19273
  content: contentLines.join(`
19221
19274
  `).trim(),
19222
19275
  ...raw.value !== undefined ? { raw: raw.value } : {}
@@ -19287,7 +19340,7 @@ function parseSectionBlock(lines, startIndex, anchorSlug) {
19287
19340
  }
19288
19341
  return { section, nextIndex: index };
19289
19342
  }
19290
- var SECTION_OPEN_RE, SECTION_CLOSE_RE, LEGACY_SECTION_OPEN_RE, SUMMARY_OPEN_RE, SUMMARY_CLOSE_RE, RAW_OPEN_RE, RAW_CLOSE_RE, RELATION_RE, ATTR_RE;
19343
+ var SECTION_OPEN_RE, SECTION_CLOSE_RE, LEGACY_SECTION_OPEN_RE, SUMMARY_OPEN_RE, SUMMARY_CLOSE_RE, RAW_OPEN_RE, RAW_CLOSE_RE, LEGACY_RAW_OPEN_RE, LEGACY_RAW_CLOSE_RE, RELATION_RE, ATTR_RE;
19291
19344
  var init_nodeParserSections = __esm(() => {
19292
19345
  init_knowledge();
19293
19346
  SECTION_OPEN_RE = /^<!--\s*c4a:section\b([\s\S]*?)\s*-->$/u;
@@ -19295,8 +19348,10 @@ var init_nodeParserSections = __esm(() => {
19295
19348
  LEGACY_SECTION_OPEN_RE = /^<!--\s*section-\d+\s+([A-Za-z_][\w-]*)\b[\s\S]*-->$/u;
19296
19349
  SUMMARY_OPEN_RE = /^<!--\s*c4a:summary\s*-->$/u;
19297
19350
  SUMMARY_CLOSE_RE = /^<!--\s*\/c4a:summary\s*-->$/u;
19298
- RAW_OPEN_RE = /^<!--\s*c4a:raw\s*-->$/u;
19299
- RAW_CLOSE_RE = /^<!--\s*\/c4a:raw\s*-->$/u;
19351
+ RAW_OPEN_RE = /^<!--\s*c4a:raw\s*$/u;
19352
+ RAW_CLOSE_RE = /^\/c4a:raw\s*-->$/u;
19353
+ LEGACY_RAW_OPEN_RE = /^<!--\s*c4a:raw\s*-->$/u;
19354
+ LEGACY_RAW_CLOSE_RE = /^<!--\s*\/c4a:raw\s*-->$/u;
19300
19355
  RELATION_RE = /^<!--\s*c4a:relation\b([\s\S]*?)\s*-->$/u;
19301
19356
  ATTR_RE = /([A-Za-z_][\w:-]*)="([^"]*)"/yu;
19302
19357
  });
@@ -19847,12 +19902,15 @@ function renderSectionComment(section) {
19847
19902
  return [head, ...relationLines].join(`
19848
19903
  `);
19849
19904
  }
19905
+ function renderSummaryText(value) {
19906
+ return value.trim().split(/\r?\n/u).map((line) => line.trim().length > 0 ? `> ${line}` : ">").join(`
19907
+ `);
19908
+ }
19850
19909
  function renderSectionBody(section) {
19851
19910
  return [
19852
19911
  renderSectionComment(section),
19853
- ...section.summary !== undefined ? ["<!-- c4a:summary -->", section.summary.trim(), "<!-- /c4a:summary -->", ""] : [],
19912
+ ...section.summary !== undefined ? ["<!-- c4a:summary -->", renderSummaryText(section.summary), "<!-- /c4a:summary -->", ""] : [],
19854
19913
  section.content.trim(),
19855
- ...section.raw !== undefined ? ["", "<!-- c4a:raw -->", section.raw.trim(), "<!-- /c4a:raw -->"] : [],
19856
19914
  "<!-- /c4a:section -->"
19857
19915
  ].join(`
19858
19916
  `);
@@ -27004,6 +27062,20 @@ async function readCurrentSourceOwnershipRecord(ctxDir) {
27004
27062
  async function readCurrentSourceOwnership(ctxDir) {
27005
27063
  return (await readCurrentSourceOwnershipRecord(ctxDir))?.ownership ?? null;
27006
27064
  }
27065
+ async function readFreshCurrentSourceOwnership(ctxDir) {
27066
+ const ownership = await readCurrentSourceOwnership(ctxDir);
27067
+ if (ownership === null)
27068
+ return null;
27069
+ const currentHashes = await latestSnapshotHashes(ctxDir);
27070
+ const stale = staleOwnershipSourceIds(ownership, currentHashes);
27071
+ if (stale.length === 0)
27072
+ return ownership;
27073
+ const refreshed = await refreshStaleSourceOwnership(ctxDir, ownership);
27074
+ if (refreshed === null)
27075
+ throw sourceOwnershipStaleError(stale);
27076
+ await writeCurrentSourceOwnership(ctxDir, refreshed);
27077
+ return refreshed;
27078
+ }
27007
27079
  function publishedSourceOwnershipSummary(record) {
27008
27080
  return {
27009
27081
  source: "finalized-ownership",
@@ -27155,18 +27227,9 @@ function sourceOwnershipStaleError(sourceIds) {
27155
27227
  });
27156
27228
  }
27157
27229
  async function assertNoUnownedSourceOwnership(ctxDir) {
27158
- let ownership = await readCurrentSourceOwnership(ctxDir);
27230
+ const ownership = await readFreshCurrentSourceOwnership(ctxDir);
27159
27231
  if (ownership === null)
27160
27232
  return;
27161
- const currentHashes = await latestSnapshotHashes(ctxDir);
27162
- const stale = staleOwnershipSourceIds(ownership, currentHashes);
27163
- if (stale.length > 0) {
27164
- const refreshed = await refreshStaleSourceOwnership(ctxDir, ownership);
27165
- if (refreshed === null)
27166
- throw sourceOwnershipStaleError(stale);
27167
- ownership = refreshed;
27168
- await writeCurrentSourceOwnership(ctxDir, refreshed);
27169
- }
27170
27233
  const unowned = ownership.sources.flatMap((source2) => source2.blocks.filter((block) => block.status === "unowned" && block.reason !== "no-accepted-heading-owner-after-refresh").map((block) => `${source2.source_id}:${block.file}:L${block.line_start}-L${block.line_end}`));
27171
27234
  if (unowned.length === 0)
27172
27235
  return;
@@ -28550,6 +28613,29 @@ function pushNodeSourceStateIssues(node2, sourceStatuses, alignedPendingCompileS
28550
28613
  });
28551
28614
  }
28552
28615
  }
28616
+ function staleSourceSnapshot(input) {
28617
+ const current = parseVersionedSourceId(input.sourceEntry).snapshotHash;
28618
+ if (current === undefined || input.source === undefined)
28619
+ return null;
28620
+ const latest = selectLatestSnapshot(input.source.snapshots);
28621
+ if (latest === null)
28622
+ return null;
28623
+ const latestHash = shortSnapshotHash(latest.content_hash);
28624
+ return current === latestHash ? null : {
28625
+ current_snapshot_hash: current,
28626
+ latest_snapshot_hash: latestHash
28627
+ };
28628
+ }
28629
+ function pushStaleSourceSnapshotIssue(input) {
28630
+ input.issues.push({
28631
+ severity: "error",
28632
+ code: "stale-node-source-snapshot",
28633
+ message: `section source_ref "${input.section.source_ref}" cannot resolve because node.sources[] points to stale snapshot "${input.sourceEntry}" (latest @${input.stale.latest_snapshot_hash})`,
28634
+ path: input.node.relativePath,
28635
+ slug: input.node.parsed.node.id,
28636
+ sectionId: input.section.id
28637
+ });
28638
+ }
28553
28639
  async function pushSectionReferenceIssues(input) {
28554
28640
  const { node: node2, section, issues } = input;
28555
28641
  for (const target of section.refers_to_nodes ?? []) {
@@ -28616,6 +28702,11 @@ async function pushSectionReferenceIssues(input) {
28616
28702
  const source2 = input.sourcesById.get(sourceId);
28617
28703
  if (source2?.status !== "active")
28618
28704
  return;
28705
+ const stale = staleSourceSnapshot({ sourceEntry, source: source2 });
28706
+ if (stale !== null) {
28707
+ pushStaleSourceSnapshotIssue({ issues, node: node2, section, sourceEntry, stale });
28708
+ return;
28709
+ }
28619
28710
  const canonicalSourceRef = await canonicalizeHashedSourceRef({
28620
28711
  ctxDir: input.ctxDir,
28621
28712
  sourceRef: section.source_ref,
@@ -28979,8 +29070,7 @@ function hasUrlReferenceLabel(text) {
28979
29070
  return regexMatches(EN_URL_REFERENCE_LABEL_RE, text) || regexMatches(CJK_URL_REFERENCE_LABEL_RE, text);
28980
29071
  }
28981
29072
  function diagnoseUrlReferencePreservation(input) {
28982
- const proposedText = `${input.summary ?? ""}
28983
- ${input.content}
29073
+ const proposedText = `${input.content}
28984
29074
  ${input.detail ?? ""}`;
28985
29075
  const citedUrls = evidenceUrlsIn(input.citedText);
28986
29076
  const proposedUrls = evidenceUrlsIn(proposedText);
@@ -29042,14 +29132,17 @@ function supportLexicalOverlap(content, citedText) {
29042
29132
  };
29043
29133
  }
29044
29134
  function hardTermPresent(term, citedText, citedHardTerms) {
29045
- if (citedHardTerms.has(term) || normalizedToken(citedText).includes(term))
29135
+ const variants = uniqueSorted([term, term.replace(TRAILING_HARD_TERM_PUNCTUATION_RE, "")]);
29136
+ if (variants.some((variant) => citedHardTerms.has(variant) || normalizedToken(citedText).includes(variant))) {
29046
29137
  return true;
29138
+ }
29047
29139
  const normalizedCited = normalizedToken(citedText);
29048
- const slashParts = term.split("/").map(normalizedToken).filter((part) => part.length > 0);
29140
+ const normalizedTerm = variants[0] ?? term;
29141
+ const slashParts = normalizedTerm.split("/").map(normalizedToken).filter((part) => part.length > 0);
29049
29142
  if (slashParts.length > 1) {
29050
29143
  return slashParts.every((part) => citedHardTerms.has(part) || normalizedCited.includes(part));
29051
29144
  }
29052
- const hyphenParts = term.includes("-") && !/[0-9._/:#]/u.test(term) ? term.split("-").map(normalizedToken).filter((part) => part.length > 1) : [];
29145
+ const hyphenParts = normalizedTerm.includes("-") && !/[0-9._/:#]/u.test(normalizedTerm) ? normalizedTerm.split("-").map(normalizedToken).filter((part) => part.length > 1) : [];
29053
29146
  return hyphenParts.length > 1 && hyphenParts.every((part) => citedHardTerms.has(part) || normalizedCited.includes(part));
29054
29147
  }
29055
29148
  function thresholdsForKind(kind) {
@@ -29127,12 +29220,13 @@ function sourceTextSupportDiagnostic(content, citedText, thresholds = DEFAULT_TH
29127
29220
  };
29128
29221
  }
29129
29222
  function sourceSectionSupportDiagnostic(input) {
29130
- const summary = typeof input.summary === "string" ? input.summary.trim() : "";
29131
29223
  const detail = typeof input.detail === "string" ? input.detail.trim() : "";
29132
- const content = summary.length > 0 ? `${summary}
29133
- ${input.content}` : input.content;
29224
+ const content = input.content;
29134
29225
  if (detail.length === 0) {
29135
- return sourceTextSupportDiagnostic(content, input.citedText, thresholdsForKind(input.kind));
29226
+ return {
29227
+ ...sourceTextSupportDiagnostic(content, input.citedText, thresholdsForKind(input.kind)),
29228
+ checkedFields: ["content"]
29229
+ };
29136
29230
  }
29137
29231
  const thresholds = thresholdsForKind(input.kind);
29138
29232
  const contentDiagnostic = sourceTextSupportDiagnostic(content, input.citedText, thresholds);
@@ -29141,6 +29235,7 @@ ${input.content}` : input.content;
29141
29235
  ${detail}`, input.citedText, thresholds);
29142
29236
  const detailAware = {
29143
29237
  ...combined,
29238
+ checkedFields: ["content", "detail"],
29144
29239
  detailAware: true,
29145
29240
  detailVerdict: detailDiagnostic.verdict
29146
29241
  };
@@ -29178,11 +29273,13 @@ function sourceTextSupportsContent(content, citedText) {
29178
29273
  return sourceTextSupportDiagnostic(content, citedText).verdict !== "unsupported";
29179
29274
  }
29180
29275
  function formatSupportDiagnostic(diagnostic) {
29276
+ const basis = diagnostic.checkedFields !== undefined && diagnostic.checkedFields.length > 0 ? diagnostic.checkedFields.join("+") : diagnostic.detailAware === true ? "content+detail" : "content";
29181
29277
  const parts = [
29182
- `support=${diagnostic.verdict}; lexical overlap ${diagnostic.matchedTermCount}/${diagnostic.contentTermCount} terms; requires ${diagnostic.requiredTermCount}/${diagnostic.contentTermCount}`
29278
+ `support=${diagnostic.verdict}; lexical overlap ${diagnostic.matchedTermCount}/${diagnostic.contentTermCount} terms; requires ${diagnostic.requiredTermCount}/${diagnostic.contentTermCount}`,
29279
+ `basis=${basis}`
29183
29280
  ];
29184
29281
  if (diagnostic.detailAware === true) {
29185
- parts.push(`basis=content+detail; detail=${diagnostic.detailVerdict ?? "unknown"}`);
29282
+ parts.push(`detail=${diagnostic.detailVerdict ?? "unknown"}`);
29186
29283
  }
29187
29284
  if (diagnostic.missingContentTerms.length > 0) {
29188
29285
  parts.push(`content terms not found in cited raw text: ${diagnostic.missingContentTerms.join(", ")}`);
@@ -29224,12 +29321,16 @@ async function findAutoNarrowedSourceRefs(input) {
29224
29321
  });
29225
29322
  if (resolved === null)
29226
29323
  continue;
29324
+ const { supportText } = supportTextWithHeadingContext({
29325
+ citedText: resolved.cited_text,
29326
+ blocks: resolved.blocks
29327
+ });
29227
29328
  const diagnostic = sourceSectionSupportDiagnostic({
29228
29329
  kind: input.section.kind,
29229
29330
  summary: input.section.summary,
29230
29331
  content: input.section.content,
29231
29332
  detail: input.section.detail,
29232
- citedText: resolved.cited_text
29333
+ citedText: supportText
29233
29334
  });
29234
29335
  if (!isStrictlySupported(diagnostic))
29235
29336
  continue;
@@ -29267,6 +29368,21 @@ function evidenceBlocksLineRange(blocks) {
29267
29368
  function evidenceBlocksLocatorId(blocks) {
29268
29369
  return blocks.map((block) => block.block_locator_id).filter((value) => typeof value === "string" && value.length > 0).join(" + ");
29269
29370
  }
29371
+ function evidenceHeadingContext(blocks) {
29372
+ const headings = blocks.flatMap((block) => block.heading_path ?? []).map((heading) => heading.trim()).filter((heading) => heading.length > 0 && heading !== "document");
29373
+ return [...new Set(headings)];
29374
+ }
29375
+ function supportTextWithHeadingContext(input) {
29376
+ const headingContext = evidenceHeadingContext(input.blocks);
29377
+ if (headingContext.length === 0)
29378
+ return { supportText: input.citedText };
29379
+ return {
29380
+ supportText: `${headingContext.join(`
29381
+ `)}
29382
+ ${input.citedText}`,
29383
+ headingContext
29384
+ };
29385
+ }
29270
29386
  async function sourceRefForBlock(input) {
29271
29387
  return sourceRefForRange({
29272
29388
  ctxDir: input.ctxDir,
@@ -29277,12 +29393,16 @@ async function sourceRefForBlock(input) {
29277
29393
  async function rankEvidenceBlockCandidates(input) {
29278
29394
  const ranked = (await Promise.all(input.blocks.map(async (block) => {
29279
29395
  const citedText = sourceRefRangeText(input.raw, block.line_start, block.line_end);
29396
+ const { supportText } = supportTextWithHeadingContext({
29397
+ citedText,
29398
+ blocks: [block]
29399
+ });
29280
29400
  const diagnostic = sourceSectionSupportDiagnostic({
29281
29401
  kind: input.section.kind,
29282
29402
  summary: input.section.summary,
29283
29403
  content: input.section.content,
29284
29404
  detail: input.section.detail,
29285
- citedText
29405
+ citedText: supportText
29286
29406
  });
29287
29407
  if (diagnostic.matchedTermCount === 0 && diagnostic.overlapRatio === 0)
29288
29408
  return null;
@@ -29320,6 +29440,10 @@ async function diagnoseSectionSourceSupport(input) {
29320
29440
  throw new Error(`${input.action} source_ref "${input.section.source_ref}" does not point to an active source`);
29321
29441
  }
29322
29442
  const citedText = resolved.cited_text;
29443
+ const { supportText, headingContext } = supportTextWithHeadingContext({
29444
+ citedText,
29445
+ blocks: resolved.blocks
29446
+ });
29323
29447
  const alias = `src-${resolved.alias_index}`;
29324
29448
  const rawBlocks = resolved.raw_text !== null ? extractRawBlocks(resolved.raw_text) : [];
29325
29449
  const evidenceBlock = {
@@ -29332,7 +29456,7 @@ async function diagnoseSectionSourceSupport(input) {
29332
29456
  summary: input.section.summary,
29333
29457
  content: input.section.content,
29334
29458
  detail: input.section.detail,
29335
- citedText
29459
+ citedText: supportText
29336
29460
  });
29337
29461
  const evidenceBlockCandidates = resolved.raw_text !== null ? await rankEvidenceBlockCandidates({
29338
29462
  ctxDir: input.ctxDir,
@@ -29347,6 +29471,7 @@ async function diagnoseSectionSourceSupport(input) {
29347
29471
  sourceId,
29348
29472
  sourceRef: resolved.source_ref,
29349
29473
  citedText,
29474
+ ...headingContext !== undefined ? { headingContext } : {},
29350
29475
  diagnostic,
29351
29476
  evidenceBlock,
29352
29477
  ...evidenceBlockCandidates !== undefined ? { evidenceBlockCandidates } : {}
@@ -29365,11 +29490,11 @@ function weakSourceSupportGuidance(action, verdict) {
29365
29490
  if (verdict !== "weak")
29366
29491
  return "";
29367
29492
  if (action === "keep_separate") {
29368
- return " Key facts match, but support is weak; ask the user to confirm the summary/compression during review, then apply the final keep_separate decision marked decided_by: user. Auto mode or general permission to continue is not confirmation.";
29493
+ return " Key facts match, but support is weak; ask the user to confirm the content compression during review, then apply the final keep_separate decision marked decided_by: user. Auto mode or general permission to continue is not confirmation.";
29369
29494
  }
29370
29495
  return " Key facts match, but this action rewrites or reanchors existing knowledge and requires direct support; choose source_ref/source_refs from the prepared evidence, split the claim, or ask the user before choosing a different final action.";
29371
29496
  }
29372
- var DEFAULT_THRESHOLDS, CJK_ONLY_RE, CJK_CHAR_RE, URL_RE2, TRAILING_URL_PUNCTUATION_RE, TRACKING_QUERY_PARAM_RE, EN_URL_REFERENCE_LABEL_RE, CJK_URL_REFERENCE_LABEL_RE, CONSTRAINT_PATTERNS, PLACEHOLDER_ANCHOR_PARTS;
29497
+ var DEFAULT_THRESHOLDS, CJK_ONLY_RE, CJK_CHAR_RE, URL_RE2, TRAILING_URL_PUNCTUATION_RE, TRAILING_HARD_TERM_PUNCTUATION_RE, TRACKING_QUERY_PARAM_RE, EN_URL_REFERENCE_LABEL_RE, CJK_URL_REFERENCE_LABEL_RE, CONSTRAINT_PATTERNS, PLACEHOLDER_ANCHOR_PARTS;
29373
29498
  var init_sourceSupport = __esm(() => {
29374
29499
  init_normalize();
29375
29500
  init_sectionDetail();
@@ -29382,6 +29507,7 @@ var init_sourceSupport = __esm(() => {
29382
29507
  CJK_CHAR_RE = /[\u4e00-\u9fff]/gu;
29383
29508
  URL_RE2 = /https?:\/\/[^\s)\]}>"')】》」』,。;:!?]+/giu;
29384
29509
  TRAILING_URL_PUNCTUATION_RE = /[.,;:!?)\]\}>"',。;:!?)】》」』]+$/u;
29510
+ TRAILING_HARD_TERM_PUNCTUATION_RE = /[.,;:!?)\]\}>"',。;:!?)】》」』]+$/u;
29385
29511
  TRACKING_QUERY_PARAM_RE = /^(?:utm_|fbclid$|gclid$|mc_cid$|mc_eid$)/iu;
29386
29512
  EN_URL_REFERENCE_LABEL_RE = /\b(?:url|urls|link|links|doc|docs|document|documents|documentation|reference|references|page|pages|guide|guides|entry|entries|website|official|source|sources)\b/giu;
29387
29513
  CJK_URL_REFERENCE_LABEL_RE = /文档|链接|入口|参考|资料|页面|地址|官网|官方|指南|来源|网址/gu;
@@ -32069,12 +32195,13 @@ async function upsertCoverageCandidates(input) {
32069
32195
  dispositions: existing?.dispositions ?? []
32070
32196
  });
32071
32197
  }
32198
+ const candidates = await adjustMaterializedCandidates(input.ctxDir, [...byId.values()]);
32072
32199
  await writeCoverageStateFile(input.ctxDir, {
32073
32200
  schema_version: COVERAGE_STATE_SCHEMA_VERSION,
32074
32201
  updated_at: (input.now ?? new Date).toISOString(),
32075
- candidates: [...byId.values()]
32202
+ candidates
32076
32203
  });
32077
- return summarizeCoverageCandidates([...byId.values()], 0, pruned.staleDropped);
32204
+ return summarizeCoverageCandidates(candidates, 0, pruned.staleDropped);
32078
32205
  }
32079
32206
  function dispositionError(code, message) {
32080
32207
  const hint = {
@@ -33208,16 +33335,13 @@ function urlsIn(text) {
33208
33335
  }
33209
33336
  function proposedTextForAction(action) {
33210
33337
  if (action.op === "add")
33211
- return `${action.summary ?? ""}
33212
- ${action.content}
33338
+ return `${action.content}
33213
33339
  ${action.detail ?? ""}`;
33214
33340
  if (action.op === "update")
33215
- return `${action.summary ?? ""}
33216
- ${action.content ?? ""}
33341
+ return `${action.content ?? ""}
33217
33342
  ${action.detail ?? ""}`;
33218
33343
  if (action.op === "supersede")
33219
- return `${action.new.summary ?? ""}
33220
- ${action.new.content}
33344
+ return `${action.new.content}
33221
33345
  ${action.new.detail ?? ""}`;
33222
33346
  return "";
33223
33347
  }
@@ -34010,8 +34134,24 @@ async function contentForBlock(ctxDir, source2, block, sourceEntry, options = {}
34010
34134
  const fullQuote = lines.slice(start2 - 1, end).join(`
34011
34135
  `).trim() || block.text_preview || "";
34012
34136
  const compactQuote = fullQuote.slice(0, MAX_SNIPPET_CHARS);
34137
+ if (options.fullText === true) {
34138
+ const blockLines = lines.slice(start2 - 1, end);
34139
+ const page = fullTextPageForBlock({
34140
+ nodeSlug: options.nodeSlug ?? "",
34141
+ blockId: block.block_id,
34142
+ blockLines,
34143
+ blockRawStartLine: start2,
34144
+ ...options.lineRange !== undefined ? { requestedRange: options.lineRange } : {}
34145
+ });
34146
+ return {
34147
+ quote: page.quote.length > 0 ? page.quote : compactQuote,
34148
+ compactQuote,
34149
+ fullTextPage: page.info,
34150
+ ...raw.note !== undefined ? { note: raw.note } : {}
34151
+ };
34152
+ }
34013
34153
  return {
34014
- quote: options.fullText === true ? fullQuote : compactQuote,
34154
+ quote: compactQuote,
34015
34155
  compactQuote,
34016
34156
  ...raw.note !== undefined ? { note: raw.note } : {}
34017
34157
  };
@@ -34044,6 +34184,7 @@ function toSnippet(input) {
34044
34184
  block_id: input.entry.block_id,
34045
34185
  ...input.entry.block_locator_id !== undefined ? { block_locator_id: input.entry.block_locator_id } : {},
34046
34186
  ...input.entry.source_ref !== undefined ? { source_ref: input.entry.source_ref } : {},
34187
+ ...input.entry.full_text_page !== undefined ? { full_text_page: input.entry.full_text_page } : {},
34047
34188
  ...input.sourceType !== undefined ? { source_type: input.sourceType } : {},
34048
34189
  ...input.note !== undefined ? {
34049
34190
  note_intent: input.note.intent,
@@ -34076,6 +34217,117 @@ function graphContext(edges, slug) {
34076
34217
  function tokenEstimate2(value) {
34077
34218
  return Math.ceil(value.length / 4);
34078
34219
  }
34220
+ function lineRangeText(range) {
34221
+ return `${range.start}:${range.end}`;
34222
+ }
34223
+ function requestFullTextPageCommand(slug, blockId, range) {
34224
+ return `context compile --context ${slug} --request-full-text ${blockId} --request-full-text-range ${range} --format json`;
34225
+ }
34226
+ function isFenceLine(line) {
34227
+ return /^\s*(```|~~~)/u.test(line);
34228
+ }
34229
+ function isTableLine(line) {
34230
+ return /^\s*\|.*\|\s*$/u.test(line);
34231
+ }
34232
+ function isMarkdownHeading(line) {
34233
+ return /^\s{0,3}#{1,6}\s+\S/u.test(line);
34234
+ }
34235
+ function isNaturalBreak(line, nextLine, inFenceAfterLine) {
34236
+ if (line.trim() === "")
34237
+ return true;
34238
+ if (isFenceLine(line) && !inFenceAfterLine)
34239
+ return true;
34240
+ if (inFenceAfterLine)
34241
+ return false;
34242
+ if (nextLine !== undefined && isMarkdownHeading(nextLine))
34243
+ return true;
34244
+ if (isTableLine(line))
34245
+ return true;
34246
+ return false;
34247
+ }
34248
+ function selectFullTextPageEnd(lines, startIndex) {
34249
+ if (startIndex >= lines.length)
34250
+ return { endIndex: lines.length };
34251
+ if (tokenEstimate2(lines[startIndex] ?? "") > FULL_TEXT_PAGE_HARD_TOKENS) {
34252
+ return {
34253
+ endIndex: startIndex + 1,
34254
+ warning: "single-line page exceeds the target token budget; content was kept intact at the line boundary"
34255
+ };
34256
+ }
34257
+ let inFence = false;
34258
+ let targetEnd;
34259
+ for (let index = startIndex;index < lines.length; index += 1) {
34260
+ const line = lines[index] ?? "";
34261
+ if (isFenceLine(line))
34262
+ inFence = !inFence;
34263
+ const pageText = lines.slice(startIndex, index + 1).join(`
34264
+ `);
34265
+ const tokens = tokenEstimate2(pageText);
34266
+ const nextLine = lines[index + 1];
34267
+ if (tokens >= FULL_TEXT_PAGE_TARGET_TOKENS && targetEnd === undefined && !inFence) {
34268
+ targetEnd = index + 1;
34269
+ }
34270
+ if (tokens >= FULL_TEXT_PAGE_MIN_TOKENS && isNaturalBreak(line, nextLine, inFence)) {
34271
+ return { endIndex: index + 1 };
34272
+ }
34273
+ if (tokens >= FULL_TEXT_PAGE_MAX_TOKENS && !inFence) {
34274
+ return { endIndex: targetEnd ?? index + 1 };
34275
+ }
34276
+ if (tokens >= FULL_TEXT_PAGE_HARD_TOKENS) {
34277
+ return {
34278
+ endIndex: index + 1,
34279
+ ...inFence ? { warning: "page reached the hard token budget inside a fenced block; content was kept intact at the line boundary" } : {}
34280
+ };
34281
+ }
34282
+ }
34283
+ return { endIndex: lines.length };
34284
+ }
34285
+ function normalizeRequestedFullTextRange(range, lineCount) {
34286
+ if (lineCount === 0)
34287
+ return { start: 1, end: 1 };
34288
+ if (range === undefined)
34289
+ return { start: 1, end: lineCount };
34290
+ if (range.start < 1 || range.end < range.start || range.start > lineCount) {
34291
+ throw new ContextError(ExitCode.UserError, `request_full_text range ${lineRangeText(range)} is outside the requested block`, {
34292
+ category: ErrorCategory.UserInputInvalid,
34293
+ agent_hints: [{
34294
+ code: "compile-request-full-text-invalid",
34295
+ severity: "error",
34296
+ message: "request_full_text range must use block-relative line numbers inside the selected block.",
34297
+ path: "request_full_text.range",
34298
+ reason_code: "range-out-of-bounds",
34299
+ next_action: "Use full_text_page.next_range from the previous response, or retry without --request-full-text-range."
34300
+ }]
34301
+ });
34302
+ }
34303
+ return { start: range.start, end: Math.min(range.end, lineCount) };
34304
+ }
34305
+ function fullTextPageForBlock(input) {
34306
+ const requested = normalizeRequestedFullTextRange(input.requestedRange, input.blockLines.length);
34307
+ const shouldAutoPage = input.requestedRange !== undefined || tokenEstimate2(input.blockLines.slice(requested.start - 1, requested.end).join(`
34308
+ `)) > FULL_TEXT_PAGE_MAX_TOKENS;
34309
+ const startIndex = requested.start - 1;
34310
+ const selected = shouldAutoPage ? selectFullTextPageEnd(input.blockLines.slice(0, requested.end), startIndex) : { endIndex: requested.end };
34311
+ const relativeEnd = Math.max(requested.start, Math.min(selected.endIndex, requested.end));
34312
+ const quote = input.blockLines.slice(startIndex, relativeEnd).join(`
34313
+ `).trim();
34314
+ const hasMore = relativeEnd < input.blockLines.length;
34315
+ const nextRange = hasMore ? `${relativeEnd + 1}:${Math.min(input.blockLines.length, relativeEnd + (relativeEnd - requested.start + 1))}` : undefined;
34316
+ const range = `${requested.start}:${relativeEnd}`;
34317
+ const info2 = {
34318
+ block_id: input.blockId,
34319
+ status: hasMore || input.requestedRange !== undefined ? "paged" : "full",
34320
+ range,
34321
+ raw_line_range: [input.blockRawStartLine + requested.start - 1, input.blockRawStartLine + relativeEnd - 1],
34322
+ block_line_count: input.blockLines.length,
34323
+ token_estimate: tokenEstimate2(quote),
34324
+ has_more: hasMore,
34325
+ ...nextRange !== undefined ? { next_range: nextRange } : {},
34326
+ ...nextRange !== undefined && input.nodeSlug.length > 0 ? { next_command: requestFullTextPageCommand(input.nodeSlug, input.blockId, nextRange) } : {},
34327
+ ...selected.warning !== undefined ? { warning: selected.warning } : {}
34328
+ };
34329
+ return { quote, info: info2 };
34330
+ }
34079
34331
  function requestableFullTextBlockIds(ownership, slug) {
34080
34332
  return ownership.sources.flatMap((source2) => source2.blocks.filter((block) => isPrimaryEvidence(block, slug) || isSecondaryShared(block, slug)).map((block) => block.block_id));
34081
34333
  }
@@ -34129,9 +34381,44 @@ function invalidRequestFullTextDetails(input) {
34129
34381
  reasonCode: "not-visible-evidence"
34130
34382
  };
34131
34383
  }
34384
+ function fullTextPagingHints(input) {
34385
+ const pages = input.snippets.flatMap((snippet) => snippet.full_text_page?.has_more === true ? [snippet.full_text_page] : []);
34386
+ if (pages.length === 0 && input.addedTokens <= input.tokenGrowthBudget)
34387
+ return [];
34388
+ return [{
34389
+ code: "compile-request-full-text-paged",
34390
+ severity: "info",
34391
+ message: pages.length > 0 ? "request_full_text returned line-bounded page(s) for long evidence instead of failing the NodeContext token budget." : "request_full_text expansion exceeded the historical growth budget but stayed within page-bounded output.",
34392
+ target_node: input.nodeSlug,
34393
+ next_action: pages.length > 0 ? "Use raw_snippets[].full_text_page.next_command to continue reading the same block; cite only source_refs already present in NodeContext." : "Continue with the returned NodeContext.",
34394
+ ...pages[0]?.next_command !== undefined ? { command: pages[0].next_command } : {},
34395
+ diagnostics: {
34396
+ added_tokens: input.addedTokens,
34397
+ historical_growth_budget: input.tokenGrowthBudget,
34398
+ page_count_with_more: pages.length,
34399
+ pages: pages.map((page) => ({
34400
+ block_id: page.block_id,
34401
+ range: page.range,
34402
+ raw_line_range: page.raw_line_range,
34403
+ token_estimate: page.token_estimate,
34404
+ next_range: page.next_range,
34405
+ next_command: page.next_command
34406
+ }))
34407
+ }
34408
+ }];
34409
+ }
34132
34410
  function validateExpandRequest(input) {
34133
34411
  const requested = [...new Set(input.request.blockIds)].filter((blockId) => blockId.length > 0);
34134
34412
  const availableBlockIds = requestableFullTextBlockIds(input.ownership, input.slug);
34413
+ if (input.request.lineRange !== undefined && requested.length !== 1) {
34414
+ throw requestFullTextError({
34415
+ slug: input.slug,
34416
+ message: "request_full_text range requires exactly one --request-full-text block id",
34417
+ reasonCode: "range-requires-single-block",
34418
+ availableBlockIds,
34419
+ requestedBlockIds: requested
34420
+ });
34421
+ }
34135
34422
  if (requested.length > MAX_FULL_TEXT_REQUESTS) {
34136
34423
  throw requestFullTextError({
34137
34424
  slug: input.slug,
@@ -34187,7 +34474,10 @@ async function appendFinalizedBlock(input) {
34187
34474
  const blockId = input.block.block_id;
34188
34475
  const expanded = input.expandedBlockIds.has(blockId);
34189
34476
  const fullText = primary || expanded;
34190
- const content = await contentForBlock(input.ctxDir, input.source, input.block, input.sourceEntry, { fullText: expanded });
34477
+ const content = await contentForBlock(input.ctxDir, input.source, input.block, input.sourceEntry, {
34478
+ fullText: expanded,
34479
+ ...expanded ? { lineRange: input.expandLineRange, nodeSlug: input.nodeSlug } : {}
34480
+ });
34191
34481
  const quote = content.quote;
34192
34482
  const compactPrefix = (input.block.context_prefix ?? input.block.text_preview ?? quote).replace(/\s+/g, " ").slice(0, MAX_CONTEXT_PREFIX_CHARS);
34193
34483
  if (expanded) {
@@ -34213,6 +34503,7 @@ async function appendFinalizedBlock(input) {
34213
34503
  ownership_reason: input.block.reason,
34214
34504
  ...input.block.primary_owner !== undefined ? { primary_owner: input.block.primary_owner } : {},
34215
34505
  ...input.block.owners !== undefined ? { owners: [...input.block.owners] } : {},
34506
+ ...content.fullTextPage !== undefined ? { full_text_page: content.fullTextPage } : {},
34216
34507
  ...secondary && !expanded ? {
34217
34508
  request_full_text: {
34218
34509
  available: true,
@@ -34275,6 +34566,7 @@ async function buildNodeContextFromFinalizedOwnership(input) {
34275
34566
  source: source2,
34276
34567
  block,
34277
34568
  expandedBlockIds,
34569
+ ...input.options?.expand?.lineRange !== undefined ? { expandLineRange: input.options.expand.lineRange } : {},
34278
34570
  fullTextTokenGrowth,
34279
34571
  groups,
34280
34572
  snippets,
@@ -34286,20 +34578,12 @@ async function buildNodeContextFromFinalizedOwnership(input) {
34286
34578
  const addedTokens = fullTextTokenGrowth.reduce((sum, value) => sum + value, 0);
34287
34579
  const baseTokens = Math.max(1, snippets.reduce((sum, snippet) => sum + tokenEstimate2(snippet.quote), 0) - addedTokens);
34288
34580
  const tokenGrowthBudget = Math.max(MIN_CONTEXT_TOKEN_GROWTH_TOKENS, Math.ceil(baseTokens * MAX_CONTEXT_TOKEN_GROWTH_RATIO));
34289
- if (addedTokens > tokenGrowthBudget) {
34290
- throw requestFullTextError({
34291
- slug: input.node.slug,
34292
- message: "request_full_text expansion exceeds the NodeContext token growth budget",
34293
- reasonCode: "token-growth-budget",
34294
- availableBlockIds: requestableFullTextBlockIds(input.ownership, input.node.slug),
34295
- requestedBlockIds: [...expandedBlockIds],
34296
- repairOptions: [
34297
- "Retry with fewer visible evidence blocks.",
34298
- "Continue from the compact NodeContext snippet when it is enough.",
34299
- "Use pending_ownership_challenge only if a secondary shared block should become owned/shared-primary evidence for this Node."
34300
- ]
34301
- });
34302
- }
34581
+ const pagingHints = fullTextPagingHints({
34582
+ nodeSlug: input.node.slug,
34583
+ snippets,
34584
+ addedTokens,
34585
+ tokenGrowthBudget
34586
+ });
34303
34587
  return {
34304
34588
  node: {
34305
34589
  ...input.node,
@@ -34312,10 +34596,11 @@ async function buildNodeContextFromFinalizedOwnership(input) {
34312
34596
  finalized_ownership: {
34313
34597
  source: "finalized-ownership",
34314
34598
  groups
34315
- }
34599
+ },
34600
+ ...pagingHints.length > 0 ? { agent_hints: pagingHints } : {}
34316
34601
  };
34317
34602
  }
34318
- var MAX_FULL_TEXT_REQUESTS = 3, MAX_CONTEXT_TOKEN_GROWTH_RATIO = 0.3, MIN_CONTEXT_TOKEN_GROWTH_TOKENS = 120, MAX_SNIPPET_CHARS = 1800, MAX_CONTEXT_PREFIX_CHARS = 320, NON_EVIDENCE_PLACEHOLDERS2;
34603
+ var MAX_FULL_TEXT_REQUESTS = 3, MAX_CONTEXT_TOKEN_GROWTH_RATIO = 0.3, MIN_CONTEXT_TOKEN_GROWTH_TOKENS = 120, MAX_SNIPPET_CHARS = 1800, MAX_CONTEXT_PREFIX_CHARS = 320, FULL_TEXT_PAGE_MIN_TOKENS = 600, FULL_TEXT_PAGE_TARGET_TOKENS = 800, FULL_TEXT_PAGE_MAX_TOKENS = 1000, FULL_TEXT_PAGE_HARD_TOKENS = 1200, NON_EVIDENCE_PLACEHOLDERS2;
34319
34604
  var init_compileFinalizedContext = __esm(() => {
34320
34605
  init_ref();
34321
34606
  init_manifest();
@@ -34464,7 +34749,7 @@ async function readCompileAlignNodes(ctxDir) {
34464
34749
  const node2 = compileAlignNodeFromKnowledge(located.parsed.node);
34465
34750
  return [node2.slug, node2];
34466
34751
  }));
34467
- const finalizedOwnership = await readCurrentSourceOwnership(ctxDir);
34752
+ const finalizedOwnership = await readFreshCurrentSourceOwnership(ctxDir);
34468
34753
  for (const node2 of finalizedOwnership?.nodes ?? []) {
34469
34754
  const finalized = compileAlignNodeFromFinalized(node2);
34470
34755
  const existing = bySlug.get(finalized.slug);
@@ -34496,7 +34781,7 @@ async function readCompileAlignEdges(ctxDir) {
34496
34781
  for (const edge2 of await loadKnowledgeGraphEdges(ctxDir)) {
34497
34782
  add(edge2.type, edge2.from, edge2.to, edge2.note);
34498
34783
  }
34499
- const finalizedOwnership = await readCurrentSourceOwnership(ctxDir);
34784
+ const finalizedOwnership = await readFreshCurrentSourceOwnership(ctxDir);
34500
34785
  for (const node2 of finalizedOwnership?.nodes ?? []) {
34501
34786
  if (typeof node2.contains_parent === "string" && node2.contains_parent.length > 0) {
34502
34787
  add("contains", node2.contains_parent, node2.slug);
@@ -34660,7 +34945,8 @@ async function readChangedBlockSnippet(ctxDir, block, source2, aliasIndex2) {
34660
34945
  }
34661
34946
  async function buildChangedNodeContext(ctxDir, slug, options = {}) {
34662
34947
  const full = await buildFullNodeContextWithOptions(ctxDir, slug, {
34663
- ...options.requestFullTextBlockIds !== undefined ? { requestFullTextBlockIds: options.requestFullTextBlockIds } : {}
34948
+ ...options.requestFullTextBlockIds !== undefined ? { requestFullTextBlockIds: options.requestFullTextBlockIds } : {},
34949
+ ...options.requestFullTextRange !== undefined ? { requestFullTextRange: options.requestFullTextRange } : {}
34664
34950
  });
34665
34951
  const changes = options.changes ?? await computeCompileChanges({
34666
34952
  ctxDir,
@@ -34739,7 +35025,7 @@ async function buildFullNodeContextWithOptions(ctxDir, slug, options = {}) {
34739
35025
  } catch {
34740
35026
  existing = undefined;
34741
35027
  }
34742
- const finalizedOwnership = await readCurrentSourceOwnership(ctxDir);
35028
+ const finalizedOwnership = await readFreshCurrentSourceOwnership(ctxDir);
34743
35029
  if (finalizedOwnership === null) {
34744
35030
  throw new ContextError(ExitCode.WorkspaceStateError, "compile context requires finalized source ownership; run /context:align finalize first", {
34745
35031
  category: ErrorCategory.WorkspaceStateInvalid,
@@ -34749,7 +35035,12 @@ async function buildFullNodeContextWithOptions(ctxDir, slug, options = {}) {
34749
35035
  }
34750
35036
  const sourcesFile = await loadSources(ctxDir);
34751
35037
  const edges = await readCompileAlignEdges(ctxDir);
34752
- const finalizedOptions = options.requestFullTextBlockIds !== undefined ? { expand: { blockIds: options.requestFullTextBlockIds } } : {};
35038
+ const finalizedOptions = options.requestFullTextBlockIds !== undefined ? {
35039
+ expand: {
35040
+ blockIds: options.requestFullTextBlockIds,
35041
+ ...options.requestFullTextRange !== undefined ? { lineRange: options.requestFullTextRange } : {}
35042
+ }
35043
+ } : {};
34753
35044
  const baseContext = await buildNodeContextFromFinalizedOwnership({
34754
35045
  ctxDir,
34755
35046
  node: node2,
@@ -36146,6 +36437,7 @@ var init_agentHintRegistry = __esm(() => {
36146
36437
  "compile-prepare-missing-draft",
36147
36438
  "compile-prepare-missing-node",
36148
36439
  "compile-request-full-text-invalid",
36440
+ "compile-request-full-text-paged",
36149
36441
  "compile-secondary-shared-compact",
36150
36442
  "compile-scan-changes-required",
36151
36443
  "compile-section-kind-guidance",
@@ -36195,6 +36487,7 @@ var init_agentHintRegistry = __esm(() => {
36195
36487
  "query-recall-truncated",
36196
36488
  "query-recall-uneven-anchors",
36197
36489
  "query-select-needs-scope",
36490
+ "raw-snapshot-pending-compile",
36198
36491
  "ready-no-op-review",
36199
36492
  "ready-review-output",
36200
36493
  "reconcile-prepare-draft-mode-invalid",
@@ -37402,7 +37695,8 @@ function draftSessionError(message) {
37402
37695
  severity: "error",
37403
37696
  message,
37404
37697
  next_action: "Read the draft status, then submit a corrected compile draft patch.",
37405
- command: "context compile --draft-status <slug> --format json"
37698
+ command: "context compile --draft-status <slug> --format json",
37699
+ patch_schema_version: COMPILE_DRAFT_PATCH_SCHEMA_VERSION
37406
37700
  }]
37407
37701
  });
37408
37702
  }
@@ -37484,7 +37778,7 @@ function parseCompileDraftPatch(value) {
37484
37778
  if (value.patches !== undefined) {
37485
37779
  throw draftSessionError("compile draft patch field patches is not supported; use operations[]");
37486
37780
  }
37487
- if (value.schema_version !== COMPILE_DRAFT_PATCH_SCHEMA_VERSION) {
37781
+ if (value.schema_version !== undefined && value.schema_version !== COMPILE_DRAFT_PATCH_SCHEMA_VERSION) {
37488
37782
  throw draftSessionError(`schema_version must be ${COMPILE_DRAFT_PATCH_SCHEMA_VERSION}`);
37489
37783
  }
37490
37784
  if (!Array.isArray(value.operations) || value.operations.length === 0) {
@@ -38111,8 +38405,11 @@ function uniqueSourceEntries(values) {
38111
38405
  continue;
38112
38406
  const base = sourceIdWithoutVersion(value);
38113
38407
  const current = byBase.get(base);
38114
- if (current === undefined || !current.includes("@") && value.includes("@"))
38408
+ const currentVersion = current === undefined ? undefined : parseVersionedSourceId(current).snapshotHash;
38409
+ const nextVersion = parseVersionedSourceId(value).snapshotHash;
38410
+ if (current === undefined || nextVersion !== undefined && currentVersion === undefined || nextVersion !== undefined && currentVersion !== undefined && nextVersion !== currentVersion) {
38115
38411
  byBase.set(base, value);
38412
+ }
38116
38413
  }
38117
38414
  return [...byBase.values()];
38118
38415
  }
@@ -38697,7 +38994,7 @@ async function compileSourceSupport(input) {
38697
38994
  if (typeof candidate.section.content !== "string" || candidate.section.content.length === 0)
38698
38995
  return;
38699
38996
  const sourceRefs = draftActionSourceRefs(input.action);
38700
- const { sourceRef, citedText, diagnostic, evidenceBlock, evidenceBlockCandidates } = await diagnoseSectionSourceSupport({
38997
+ const { sourceRef, citedText, headingContext, diagnostic, evidenceBlock, evidenceBlockCandidates } = await diagnoseSectionSourceSupport({
38701
38998
  ctxDir: input.ctxDir,
38702
38999
  nodeSources: input.node.parsed.node.sources,
38703
39000
  sourceById: input.sourceById,
@@ -38713,7 +39010,10 @@ async function compileSourceSupport(input) {
38713
39010
  return {
38714
39011
  source_ref: sourceRef,
38715
39012
  cited_text: citedText,
39013
+ ...headingContext !== undefined ? { heading_context: headingContext } : {},
38716
39014
  verdict: diagnostic.verdict,
39015
+ ...diagnostic.checkedFields !== undefined ? { checked_fields: diagnostic.checkedFields } : {},
39016
+ ...candidate.section.summary !== undefined ? { summary_policy: "advisory_not_hard_gated" } : {},
38717
39017
  content_term_count: diagnostic.contentTermCount,
38718
39018
  matched_term_count: diagnostic.matchedTermCount,
38719
39019
  required_term_count: diagnostic.requiredTermCount,
@@ -39227,6 +39527,8 @@ async function prepareCompileReconcileContext(input) {
39227
39527
  });
39228
39528
  resolvedItems.push({
39229
39529
  item_id: itemId,
39530
+ ...typeof action.action_id === "string" ? { draft_action_id: action.action_id } : {},
39531
+ draft_action_index: indexInDraft + 1,
39230
39532
  proposed: temporal.proposed,
39231
39533
  ...defaultDecision !== undefined ? { default_decision: defaultDecision } : {},
39232
39534
  ...target !== undefined ? { target } : {},
@@ -40891,16 +41193,17 @@ function queryAgentOutput(value) {
40891
41193
  return rest;
40892
41194
  }
40893
41195
  function semanticStatusAgentView(status) {
40894
- const refreshedNoteSourceIds = status.refreshed_source_pending_compile.source_ids.filter((sourceId) => sourceId.startsWith("note:"));
40895
- const agentHints = refreshedNoteSourceIds.length > 0 ? [{
40896
- code: "refresh-note-pending-compile",
41196
+ const refreshedSourceIds = status.refreshed_source_pending_compile.source_ids;
41197
+ const agentHints = refreshedSourceIds.length > 0 ? [{
41198
+ code: "raw-snapshot-pending-compile",
40897
41199
  severity: "info",
40898
- message: "One or more captured note sources are pending compile absorption.",
40899
- next_action: "Run /context:compile and process semantic.refreshed_source_pending_compile.source_ids before the ordinary per-Node draft loop.",
41200
+ message: "One or more active sources have newer raw snapshots that have not been absorbed into compiled knowledge.",
41201
+ next_action: "Run /context:compile. The CLI will refresh deterministic source ownership when possible; if ownership cannot be reanchored, rerun context align --scan before compiling.",
40900
41202
  command: "/context:compile",
40901
41203
  diagnostics: {
40902
- source_ids: refreshedNoteSourceIds,
40903
- status_field: "semantic.refreshed_source_pending_compile.source_ids"
41204
+ source_ids: refreshedSourceIds,
41205
+ status_field: "semantic.refreshed_source_pending_compile.source_ids",
41206
+ note: "This status means raw snapshots changed; it does not mean finalized ownership or node.sources[] are already updated."
40904
41207
  }
40905
41208
  }] : [];
40906
41209
  return agentPathFreeView({
@@ -41763,6 +42066,40 @@ async function refreshSources(input) {
41763
42066
  const file = await loadSources2(input.ctxDir);
41764
42067
  const rows = [];
41765
42068
  for (const s of file.sources.filter((source2) => source2.status === "active")) {
42069
+ if (s.type === "local" && typeof s.origin === "string" && s.origin.length > 0) {
42070
+ const path2 = isAbsolute2(s.origin) ? s.origin : input.workspaceRoot === undefined ? null : join21(input.workspaceRoot, s.origin);
42071
+ if (path2 === null || !existsSync19(path2)) {
42072
+ rows.push({
42073
+ sourceId: s.id,
42074
+ status: "skipped",
42075
+ action: "skipped",
42076
+ error: path2 === null ? "local source refresh requires workspaceRoot" : `local source origin not found: ${s.origin}`
42077
+ });
42078
+ continue;
42079
+ }
42080
+ try {
42081
+ const outcome = await captureFile({
42082
+ path: path2,
42083
+ cwd: input.workspaceRoot ?? process.cwd(),
42084
+ ctxDir: input.ctxDir,
42085
+ ...input.workspaceRoot !== undefined ? { workspaceRoot: input.workspaceRoot } : {},
42086
+ ...input.now !== undefined ? { now: input.now } : {}
42087
+ });
42088
+ rows.push({
42089
+ sourceId: s.id,
42090
+ status: "ok",
42091
+ action: outcome.action === "unchanged" || outcome.action === "moved" || outcome.action === "duplicate" ? "unchanged" : "new-snapshot"
42092
+ });
42093
+ } catch (err2) {
42094
+ rows.push({
42095
+ sourceId: s.id,
42096
+ status: "failed",
42097
+ action: "failed",
42098
+ error: err2 instanceof Error ? err2.message : String(err2)
42099
+ });
42100
+ }
42101
+ continue;
42102
+ }
41766
42103
  if (s.type !== "feishu" || typeof s.url !== "string" || s.url.length === 0) {
41767
42104
  rows.push({ sourceId: s.id, status: "skipped", action: "skipped" });
41768
42105
  continue;
@@ -51128,7 +51465,7 @@ function reviewAgentHints(input) {
51128
51465
  code: "split-by-evidence-blocks",
51129
51466
  severity: "error",
51130
51467
  message: `${splitByEvidenceBlockCount} unsupported decision(s) appear to combine facts from multiple evidence blocks.`,
51131
- next_action: "Split the proposed summary into one draft action per source_support.evidence_block_candidates[] entry, then rerun prepare/review. If the draft session is available, patch only the affected action with context compile --draft-patch <slug> --input - --plan.",
51468
+ next_action: "Split the proposed content into one draft action per source_support.evidence_block_candidates[] entry, then rerun prepare/review. If the draft session is available, patch only the affected action with context compile --draft-patch <slug> --input - --plan.",
51132
51469
  command: "context compile --draft-patch <slug> --input - --plan"
51133
51470
  });
51134
51471
  }
@@ -51138,7 +51475,7 @@ function reviewAgentHints(input) {
51138
51475
  code: "review-content-shape",
51139
51476
  severity: "error",
51140
51477
  message: `${contentShapeIssueCount} proposed content field(s) contain long, multi-line, or code-block material.`,
51141
- next_action: "Use proposed.content for the active Section text and add proposed.summary for long content; rerun review with the current semantic-decisions schema.",
51478
+ next_action: "Use proposed.content for the active Section text; proposed.summary is optional and is not a replacement for content. Rerun review with the current semantic-decisions schema.",
51142
51479
  schema: "context schema semantic-decisions --format yaml"
51143
51480
  });
51144
51481
  }
@@ -51216,7 +51553,7 @@ function reviewAgentHints(input) {
51216
51553
  code: "weak-source-support-warning",
51217
51554
  severity: "warning",
51218
51555
  message: `${weakSupportCount} item(s) are only weakly supported by the cited evidence.`,
51219
- next_action: "Proceed only if the weak summary/compression is acceptable for this workflow; otherwise narrow content or choose/split source_ref/source_refs."
51556
+ next_action: "Proceed only if the weak content compression is acceptable for this workflow; otherwise narrow content or choose/split source_ref/source_refs."
51220
51557
  });
51221
51558
  }
51222
51559
  const exampleDetailQuestionCount = input.questions.filter((question) => question.type === "example_content_preservation").length;
@@ -51225,7 +51562,7 @@ function reviewAgentHints(input) {
51225
51562
  code: "example-content-preservation",
51226
51563
  severity: "warning",
51227
51564
  message: `${exampleDetailQuestionCount} example decision(s) cite code/config/command evidence without preserving it in content.`,
51228
- next_action: "Move the relevant fenced block into proposed.content and rerun review, or ask the user to confirm prose-only summary is acceptable. Auto mode or general permission to continue is not confirmation."
51565
+ next_action: "Move the relevant fenced block into proposed.content and rerun review, or ask the user to confirm prose-only content is acceptable. Auto mode or general permission to continue is not confirmation."
51229
51566
  });
51230
51567
  }
51231
51568
  const urlReferenceQuestionCount = input.questions.filter((question) => question.type === "url_reference_preservation").length;
@@ -51645,6 +51982,7 @@ function supportDiagnosticFromItem(input) {
51645
51982
  if (input.decision.judge_support_verdict !== undefined) {
51646
51983
  return {
51647
51984
  verdict: input.decision.judge_support_verdict,
51985
+ checkedFields: ["content"],
51648
51986
  contentTermCount: 1,
51649
51987
  matchedTermCount: input.decision.judge_support_verdict === "unsupported" ? 0 : 1,
51650
51988
  requiredTermCount: 1,
@@ -51661,11 +51999,15 @@ function supportDiagnosticFromItem(input) {
51661
51999
  const detail = input.decision.proposed?.detail === null || typeof input.decision.proposed?.detail === "string" ? input.decision.proposed.detail : undefined;
51662
52000
  const itemSupport = input.item?.source_support;
51663
52001
  if (itemSupport !== undefined && itemSupport.source_ref === sourceRef && typeof itemSupport.cited_text === "string") {
51664
- return sourceSectionSupportDiagnostic({ kind, content, detail, citedText: itemSupport.cited_text });
52002
+ const citedText2 = itemSupport.heading_context !== undefined && itemSupport.heading_context.length > 0 ? `${itemSupport.heading_context.join(`
52003
+ `)}
52004
+ ${itemSupport.cited_text}` : itemSupport.cited_text;
52005
+ return sourceSectionSupportDiagnostic({ kind, content, detail, citedText: citedText2 });
51665
52006
  }
51666
52007
  if (itemSupport !== undefined && itemSupport.source_ref === sourceRef && preparedSupportContentMatches({ content, item: input.item })) {
51667
52008
  return {
51668
52009
  verdict: itemSupport.verdict,
52010
+ ...itemSupport.checked_fields !== undefined ? { checkedFields: itemSupport.checked_fields } : {},
51669
52011
  contentTermCount: itemSupport.content_term_count,
51670
52012
  matchedTermCount: itemSupport.matched_term_count,
51671
52013
  requiredTermCount: itemSupport.required_term_count,
@@ -51682,7 +52024,7 @@ function sourceSupportIssue(input) {
51682
52024
  const action = input.decision.action;
51683
52025
  const strict = actionRequiresStrictSourceSupport(action);
51684
52026
  const splitCandidates = input.support?.evidence_block_candidates?.slice(0, 6).map((candidate) => `${candidate.source_ref} (${candidate.block_locator_id})`).join("; ");
51685
- const repairHint = strict ? "Use source_ref/source_refs copied from prepared evidence that fully covers the rewritten/reanchored content, split separable claims, or ask the user before choosing a different final action." : "Use source_ref/source_refs copied from prepared evidence that covers every sentence, split the summary into separately supported claims, or ask the user to confirm a weak summary only after the evidence boundary is correct.";
52027
+ const repairHint = strict ? "Use source_ref/source_refs copied from prepared evidence that fully covers the rewritten/reanchored content, split separable claims, or ask the user before choosing a different final action." : "Use source_ref/source_refs copied from prepared evidence that covers every sentence, split the content into separately supported claims, or ask the user to confirm weak content only after the evidence boundary is correct.";
51686
52028
  const splitHint = splitCandidates !== undefined && splitCandidates.length > 0 ? ` Candidate evidence blocks for split decisions: ${splitCandidates}.` : "";
51687
52029
  const splitCandidateCount = input.support?.evidence_block_candidates?.length ?? 0;
51688
52030
  return {
@@ -51699,7 +52041,7 @@ function weakSupportWarning(input) {
51699
52041
  severity: "warning",
51700
52042
  code: "weak-source-support",
51701
52043
  message: `proposed keep_separate is weakly supported by its cited raw text: ${formatSupportDiagnostic(input.diagnostic)}.`,
51702
- next_action: "Review the cited evidence if the summary looks surprising; missing hard facts still remain errors."
52044
+ next_action: "Review the cited evidence if the content looks surprising; missing hard facts still remain errors."
51703
52045
  };
51704
52046
  }
51705
52047
  function citedTextForDecision2(decision, item) {
@@ -51731,7 +52073,7 @@ function exampleDetailPreservationQuestion(input) {
51731
52073
  type: "example_content_preservation",
51732
52074
  ...input.summary !== undefined ? { candidate_summary: { active: input.summary.active, archive: input.summary.archive } } : {},
51733
52075
  ...input.decision.proposed?.content !== undefined ? { proposed_content: input.decision.proposed.content } : {},
51734
- prompt: "The cited raw evidence contains a fenced code/config/command block, but this example decision does not preserve it in proposed.content. " + "Add the relevant fenced block to proposed.content and rerun review, or ask the user to confirm that a prose-only summary is acceptable and mark the decision decided_by: user. Auto mode or general permission to continue is not confirmation."
52076
+ prompt: "The cited raw evidence contains a fenced code/config/command block, but this example decision does not preserve it in proposed.content. " + "Add the relevant fenced block to proposed.content and rerun review, or ask the user to confirm that prose-only content is acceptable and mark the decision decided_by: user. Auto mode or general permission to continue is not confirmation."
51735
52077
  };
51736
52078
  }
51737
52079
  function sourceSupportFromDiagnostic(input) {
@@ -51739,6 +52081,7 @@ function sourceSupportFromDiagnostic(input) {
51739
52081
  source_ref: input.sourceRef,
51740
52082
  cited_text: input.citedText,
51741
52083
  verdict: input.diagnostic.verdict,
52084
+ ...input.diagnostic.checkedFields !== undefined ? { checked_fields: input.diagnostic.checkedFields } : {},
51742
52085
  content_term_count: input.diagnostic.contentTermCount,
51743
52086
  matched_term_count: input.diagnostic.matchedTermCount,
51744
52087
  required_term_count: input.diagnostic.requiredTermCount,
@@ -56776,6 +57119,7 @@ init_cliFeedback();
56776
57119
  init_agentHints();
56777
57120
  init_exitCode();
56778
57121
  init_compile();
57122
+ init_compileDraftSession();
56779
57123
  init_currentWorkflow();
56780
57124
  init_workflowPayloadStore();
56781
57125
  function compileChangesFormat(value) {
@@ -57100,13 +57444,17 @@ function compileDraftRevisionHints(slug, digest2) {
57100
57444
  code: "compile-draft-patch-ready",
57101
57445
  severity: "info",
57102
57446
  message: "Current draft can be revised with a node-scoped patch instead of resubmitting all actions.",
57103
- next_action: "Replace, add, or remove only the affected action; the CLI resolves the current draft payload automatically.",
57447
+ next_action: "Replace, add, or remove only the affected action; schema_version may be omitted, and the CLI resolves the current draft payload automatically.",
57104
57448
  command: `context compile --draft-patch ${slug} --input - --plan`,
57449
+ patch_schema_version: COMPILE_DRAFT_PATCH_SCHEMA_VERSION,
57105
57450
  diagnostics: { optional_payload_digest: digest2 }
57106
57451
  }];
57107
57452
  }
57108
57453
  function writeCompileDraftStatus(input) {
57109
- const actions = summarizeCompileDraftSession(input.session);
57454
+ const actions = compileDraftStatusActions({
57455
+ session: input.session,
57456
+ ...input.preparedReconcile !== undefined ? { preparedItems: input.preparedReconcile.items } : {}
57457
+ });
57110
57458
  const hints = [
57111
57459
  ...compileDraftRevisionHints(input.session.node, input.digest),
57112
57460
  ...input.session.agent_hints ?? []
@@ -57115,9 +57463,11 @@ function writeCompileDraftStatus(input) {
57115
57463
  writeJson3({
57116
57464
  node: input.session.node,
57117
57465
  draft_digest: input.digest,
57466
+ patch_schema_version: COMPILE_DRAFT_PATCH_SCHEMA_VERSION,
57118
57467
  show_command: input.showCommand,
57119
57468
  next_command: input.showCommand,
57120
57469
  action_count: actions.length,
57470
+ ...input.preparedReconcile !== undefined ? { reconcile_prepare: { digest: input.preparedReconcile.digest, item_count: input.preparedReconcile.items.length } } : {},
57121
57471
  actions,
57122
57472
  agent_hints: hints
57123
57473
  });
@@ -57128,17 +57478,38 @@ function writeCompileDraftStatus(input) {
57128
57478
  `digest: ${input.digest}`,
57129
57479
  `show: ${input.showCommand}`,
57130
57480
  `actions: ${actions.length}`,
57481
+ ...input.preparedReconcile !== undefined ? [`reconcile prepare: ${input.preparedReconcile.digest} (${input.preparedReconcile.items.length} item(s))`] : [],
57131
57482
  "",
57132
- "| action_id | op | kind | source_refs | preview |",
57133
- "|-----------|----|------|-------------|---------|"
57483
+ "| action_id | claim_id | op | kind | source_refs | preview |",
57484
+ "|-----------|----------|----|------|-------------|---------|"
57134
57485
  ];
57135
57486
  for (const action of actions) {
57136
- lines.push(`| ${action.action_id} | ${action.op} | ${action.kind ?? ""} | ${action.source_ref_count} | ${(action.content_preview ?? "").replace(/\|/g, "\\|")} |`);
57487
+ lines.push(`| ${action.action_id} | ${action.reconcile_item_id ?? ""} | ${action.op} | ${action.kind ?? ""} | ${action.source_ref_count} | ${(action.content_preview ?? "").replace(/\|/g, "\\|")} |`);
57137
57488
  }
57138
57489
  lines.push("", `next: ${hints[0].command}`);
57139
57490
  process.stdout.write(lines.join(`
57140
57491
  `));
57141
57492
  }
57493
+ function claimIdForActionIndex(index) {
57494
+ return `claim-${String(index + 1).padStart(3, "0")}`;
57495
+ }
57496
+ function compileDraftStatusActions(input) {
57497
+ const itemsById = new Map((input.preparedItems ?? []).map((item) => [item.item_id, item]));
57498
+ const itemsByActionId = new Map((input.preparedItems ?? []).filter((item) => typeof item.draft_action_id === "string").map((item) => [item.draft_action_id, item]));
57499
+ const hasActionIdMapping = itemsByActionId.size > 0;
57500
+ return summarizeCompileDraftSession(input.session).map((action, index) => {
57501
+ const itemId = claimIdForActionIndex(index);
57502
+ const item = itemsByActionId.get(action.action_id) ?? (hasActionIdMapping ? undefined : itemsById.get(itemId));
57503
+ if (item === undefined)
57504
+ return action;
57505
+ return {
57506
+ ...action,
57507
+ reconcile_item_id: item.item_id,
57508
+ reconcile_status: item.status ?? "pending",
57509
+ ...item.source_support?.verdict !== undefined ? { source_support: item.source_support.verdict } : {}
57510
+ };
57511
+ });
57512
+ }
57142
57513
  function rejectSourceRefTextMainPath(draft) {
57143
57514
  const unsupportedEvidenceFieldNames = ["source_ref_text", "evidence_refs"];
57144
57515
  const hasUnsupportedEvidenceField = (value) => unsupportedEvidenceFieldNames.some((field) => (field in value));
@@ -57938,25 +58309,29 @@ function normalizeAlignCoarseReadPayloads(value) {
57938
58309
  reject("$", "align-coarse-read payload must be an object");
57939
58310
  if (Array.isArray(value.coarse_reads)) {
57940
58311
  walkForbidden(value, "$");
57941
- if (value.schema_version !== undefined && value.schema_version !== ALIGN_COARSE_READ_BATCH_SCHEMA_VERSION) {
57942
- reject("schema_version", `schema_version must be ${ALIGN_COARSE_READ_BATCH_SCHEMA_VERSION} for a coarse_reads envelope`);
58312
+ if (value.schema_version !== undefined && value.schema_version !== ALIGN_COARSE_READ_BATCH_SCHEMA_VERSION && value.schema_version !== ALIGN_COARSE_READ_SCHEMA_VERSION) {
58313
+ reject("schema_version", `schema_version must be omitted or be ${ALIGN_COARSE_READ_BATCH_SCHEMA_VERSION} for a coarse_reads envelope`);
57943
58314
  }
57944
58315
  if (value.coarse_reads.length === 0) {
57945
58316
  reject("coarse_reads", "coarse_reads must contain at least one coarse-read record");
57946
58317
  }
57947
58318
  return value.coarse_reads.map((record, index) => {
57948
- const normalized = isRecord25(record) ? {
58319
+ const normalized2 = isRecord25(record) ? {
57949
58320
  ...record,
57950
58321
  schema_version: record.schema_version ?? ALIGN_COARSE_READ_SCHEMA_VERSION,
57951
58322
  reading_anchors: record.reading_anchors ?? [],
57952
58323
  section_proposals: record.section_proposals ?? []
57953
58324
  } : record;
57954
- validateAlignCoarseReadPayload(normalized, `coarse_reads[${index}]`);
57955
- return normalized;
58325
+ validateAlignCoarseReadPayload(normalized2, `coarse_reads[${index}]`);
58326
+ return normalized2;
57956
58327
  });
57957
58328
  }
57958
- validateAlignCoarseReadPayload(value);
57959
- return [value];
58329
+ const normalized = {
58330
+ ...value,
58331
+ schema_version: value.schema_version ?? ALIGN_COARSE_READ_SCHEMA_VERSION
58332
+ };
58333
+ validateAlignCoarseReadPayload(normalized);
58334
+ return [normalized];
57960
58335
  }
57961
58336
 
57962
58337
  // src/workflow/alignCandidateLedger.ts
@@ -59295,6 +59670,8 @@ var termTags = TERM_TAG_VALUES;
59295
59670
  var entityTags = [...ENTITY_TAG_A_VALUES, ...ENTITY_TAG_B_VALUES, ...termTags];
59296
59671
  var TERM_ENTITY_NOTES = [
59297
59672
  "Node type order: action first only when scale plus process evidence both pass; otherwise concrete/term entity; otherwise child-bearing domain; otherwise no Node.",
59673
+ "Classify the Node by evidence referent, not by source title or heading. Source title/heading is ordinary evidence and does not automatically become node.title, aliases[], or slug.",
59674
+ "After node_type is chosen, generate node.title to fit that type: entity titles name concrete objects or atomic terms; domain titles name grouping scopes with child refs; action titles name executable processes. Scope/process words such as 方案, 架构, 体系, 演练, 流程, 策略, 能力, 机制, framework, architecture, system, strategy, process, or drill in an Entity title require review.",
59298
59675
  "Action scale means at least two planned Section kinds or a child Action; process evidence means steps, phases, trigger->handling->result, role collaboration, or repeatable plan. Parallel options/config lists are not action evidence.",
59299
59676
  "Entity tag term is for a pure stable named definition only. term is mutually exclusive with A/B concrete tags; React is [lib], not [lib, term]. Split a separate [term] Entity when both identities matter.",
59300
59677
  "Legal Entity tag shapes: one A tag, one B tag, one A plus one B, or only [term]. A+A, B+B, term+A/B, and empty entity tags are invalid.",
@@ -59473,7 +59850,7 @@ var SCHEMAS = {
59473
59850
  },
59474
59851
  "align-coarse-read": {
59475
59852
  schema: "align-coarse-read",
59476
- schema_version: "align.coarse-read.v1",
59853
+ schema_version: ALIGN_COARSE_READ_SCHEMA_VERSION,
59477
59854
  producer: "LLM Pass 0 + CLI id normalizer",
59478
59855
  consumer: "CLI storage / Discovery LLM",
59479
59856
  role: "Whole-document reading anchors, section planning, neutral content signals, and density profile.",
@@ -59482,10 +59859,10 @@ var SCHEMAS = {
59482
59859
  content_signal_key: ALIGN_CONTENT_SIGNAL_KEYS,
59483
59860
  content_signal_level: ALIGN_CONTENT_SIGNAL_LEVELS
59484
59861
  },
59485
- required: ["schema_version", "source_id", "density_profile", "reading_anchors", "section_proposals"],
59862
+ required: ["source_id", "density_profile", "reading_anchors", "section_proposals"],
59486
59863
  forbidden_fields: [...ALIGN_WORKFLOW_FORBIDDEN_FIELDS, "node_type", "tags", "recommended_node", "action_tag"],
59487
59864
  example: {
59488
- schema_version: "align.coarse-read.v1",
59865
+ schema_version: ALIGN_COARSE_READ_SCHEMA_VERSION,
59489
59866
  artifact_kind: "cli_normalized_llm_output",
59490
59867
  source_id: "local:demo",
59491
59868
  density_profile: "meso",
@@ -59512,12 +59889,27 @@ var SCHEMAS = {
59512
59889
  source_window_ids: ["w0001"]
59513
59890
  }]
59514
59891
  },
59892
+ batch_example: {
59893
+ coarse_reads: [{
59894
+ source_id: "local:demo-a",
59895
+ density_profile: "micro",
59896
+ reading_anchors: [],
59897
+ section_proposals: []
59898
+ }, {
59899
+ source_id: "local:demo-b",
59900
+ density_profile: "macro",
59901
+ reading_anchors: [],
59902
+ section_proposals: []
59903
+ }]
59904
+ },
59515
59905
  notes: [
59906
+ "When submitting through context align --coarse-read, schema_version may be omitted; the CLI infers single-source vs batch from the presence of coarse_reads[].",
59907
+ "For batch input, prefer { coarse_reads: [...] } without a top-level schema_version. Each entry is a single-source coarse-read record.",
59516
59908
  "density_profile must be one of macro, meso, micro, or single_pass.",
59517
59909
  "content_signals keys must be temporal_density, actor_density, step_density, or directive_density.",
59518
59910
  "content_signals values must be high, med, or low. Use med, not medium.",
59519
59911
  "content_signals describe text shape only; they are not action/type/tag hints.",
59520
- `Submit multiple sources as { schema_version: "${ALIGN_COARSE_READ_BATCH_SCHEMA_VERSION}", coarse_reads: [...] }; the single-source payload remains valid.`,
59912
+ `If you include a top-level batch schema_version, use ${ALIGN_COARSE_READ_BATCH_SCHEMA_VERSION}; omitting it is simpler.`,
59521
59913
  "align-coarse-read is the latest checkpoint. Durable multi-source recall lives in align-candidate-ledger.source_readings."
59522
59914
  ]
59523
59915
  },
@@ -59528,7 +59920,7 @@ var SCHEMAS = {
59528
59920
  consumer: "CLI ledger reducer",
59529
59921
  role: "One transactional batch of candidate ledger operations.",
59530
59922
  enums: enums(),
59531
- required: ["schema_version", "batch_id", "sections_processed", "ops"],
59923
+ required: ["batch_id", "sections_processed", "ops"],
59532
59924
  forbidden_fields: ALIGN_WORKFLOW_FORBIDDEN_FIELDS,
59533
59925
  example: {
59534
59926
  schema_version: ALIGN_CANDIDATE_OPS_SCHEMA_VERSION,
@@ -59595,6 +59987,7 @@ var SCHEMAS = {
59595
59987
  }]
59596
59988
  },
59597
59989
  notes: [
59990
+ "When submitting through context align --ops, schema_version may be omitted; the CLI normalizes it to the current align-candidate-ops schema.",
59598
59991
  "add_candidate uses local:<name>; reducer assigns immutable candidate_id.",
59599
59992
  ...TERM_ENTITY_NOTES,
59600
59993
  "Action candidates must include boolean action_probe gate fields and structured action_signals; do not use free-form steps[] in candidate ops.",
@@ -61739,6 +62132,12 @@ function validateCandidateOpsAgainstSegments(opsInput, segments) {
61739
62132
  walk(opsInput.ops, "ops", "ops");
61740
62133
  throwIfIssues();
61741
62134
  }
62135
+ function normalizeCandidateOpsInput(value) {
62136
+ return {
62137
+ ...value,
62138
+ schema_version: value.schema_version ?? ALIGN_CANDIDATE_OPS_SCHEMA_VERSION
62139
+ };
62140
+ }
61742
62141
  async function readExistingLedger(input) {
61743
62142
  try {
61744
62143
  const record = await readWorkflowPayloadByDigest({
@@ -61757,12 +62156,13 @@ async function readExistingLedger(input) {
61757
62156
  }
61758
62157
  }
61759
62158
  async function runAlignCandidateOpsCommand(input) {
61760
- const opsInput = await readStructuredInput2(resolveStdinOnlyInput(input.options.ops, "--ops"));
61761
- if (typeof opsInput !== "object" || opsInput === null || Array.isArray(opsInput)) {
62159
+ const rawOpsInput = await readStructuredInput2(resolveStdinOnlyInput(input.options.ops, "--ops"));
62160
+ if (typeof rawOpsInput !== "object" || rawOpsInput === null || Array.isArray(rawOpsInput)) {
61762
62161
  throw new ContextError(ExitCode.UserError, "align candidate ops input must be an object", {
61763
62162
  category: ErrorCategory.SchemaInvalid
61764
62163
  });
61765
62164
  }
62165
+ const opsInput = normalizeCandidateOpsInput(rawOpsInput);
61766
62166
  const segmentsDigest = input.workflowState.input_digests["align-segments"];
61767
62167
  if (segmentsDigest === undefined) {
61768
62168
  throw alignSegmentsRequiredError("candidate-ops");
@@ -62140,7 +62540,8 @@ async function saveRecoverableCompileDraft(input) {
62140
62540
  severity: "error",
62141
62541
  message: "Compile draft parsed, but validation failed. A patchable draft session was saved.",
62142
62542
  next_action: "Read draft status, patch only the failed action(s), then rerun compile draft prepare.",
62143
- command: `context compile --draft-status ${input.slug} --format json`
62543
+ command: `context compile --draft-status ${input.slug} --format json`,
62544
+ patch_schema_version: COMPILE_DRAFT_PATCH_SCHEMA_VERSION
62144
62545
  };
62145
62546
  const session = createCompileDraftSession({
62146
62547
  node: input.slug,
@@ -62172,6 +62573,7 @@ function throwCompileDraftRecoveredError(input) {
62172
62573
  draft_digest: input.recovery.payload.digest,
62173
62574
  draft_status_command: `context compile --draft-status ${input.slug} --format json`,
62174
62575
  patch_command: `context compile --draft-patch ${input.slug} --input - --plan`,
62576
+ patch_schema_version: COMPILE_DRAFT_PATCH_SCHEMA_VERSION,
62175
62577
  issues: input.recovery.issues,
62176
62578
  agent_hints: input.recovery.agent_hints
62177
62579
  });
@@ -62695,6 +63097,9 @@ async function prepareCompileDraftWorkflow(input) {
62695
63097
  }
62696
63098
 
62697
63099
  // src/commands/compileDraftCommand.ts
63100
+ function isRecord31(value) {
63101
+ return typeof value === "object" && value !== null && !Array.isArray(value);
63102
+ }
62698
63103
  async function writeCompileDraftChallengePayloads2(input) {
62699
63104
  const payloads = await compileDraftChallengePayloads({
62700
63105
  ctxDir: input.ctxDir,
@@ -62722,6 +63127,24 @@ async function writeCompileDraftChallengePayloads2(input) {
62722
63127
  });
62723
63128
  }
62724
63129
  }
63130
+ function preparedReconcileItems(value, slug) {
63131
+ if (!isRecord31(value) || !Array.isArray(value.items))
63132
+ return [];
63133
+ const target = isRecord31(value.target) ? value.target : undefined;
63134
+ if (target !== undefined && target.node !== slug)
63135
+ return [];
63136
+ return value.items.filter(isRecord31).flatMap((item) => {
63137
+ if (typeof item.item_id !== "string")
63138
+ return [];
63139
+ const sourceSupport = isRecord31(item.source_support) && typeof item.source_support.verdict === "string" ? { verdict: item.source_support.verdict } : undefined;
63140
+ return [{
63141
+ item_id: item.item_id,
63142
+ ...typeof item.draft_action_id === "string" ? { draft_action_id: item.draft_action_id } : {},
63143
+ ...typeof item.status === "string" ? { status: item.status } : {},
63144
+ ...sourceSupport !== undefined ? { source_support: sourceSupport } : {}
63145
+ }];
63146
+ });
63147
+ }
62725
63148
  async function runDraftStatus(input) {
62726
63149
  const current = requireCurrentWorkflow(await readCurrentWorkflow(input.ctxDir));
62727
63150
  if (current.family !== "compile") {
@@ -62738,11 +63161,22 @@ async function runDraftStatus(input) {
62738
63161
  });
62739
63162
  const session = parseCompileDraftSession(payload.value);
62740
63163
  assertCompileDraftSessionNode(session, input.slug);
63164
+ const preparePayload = await readWorkflowPayloadByDigest({
63165
+ ctxDir: input.ctxDir,
63166
+ workflowId: current.workflow_id,
63167
+ scopeId: nodeRunIdForSlug(input.slug),
63168
+ payload: "prepare",
63169
+ fallbackToUniqueScope: true
63170
+ }).catch(() => {
63171
+ return;
63172
+ });
63173
+ const prepareItems = preparePayload === undefined ? [] : preparedReconcileItems(preparePayload.value, input.slug);
62741
63174
  writeCompileDraftStatus({
62742
63175
  session,
62743
63176
  digest: payload.digest,
62744
63177
  showCommand: workflowPayloadShowCommand(payload),
62745
- format: compileChangesFormat(input.format)
63178
+ format: compileChangesFormat(input.format),
63179
+ ...preparePayload !== undefined && prepareItems.length > 0 ? { preparedReconcile: { digest: preparePayload.digest, items: prepareItems } } : {}
62746
63180
  });
62747
63181
  }
62748
63182
  async function runDraftPatch(input) {
@@ -62986,6 +63420,40 @@ function collectRequestedBlock(value, previous) {
62986
63420
  function ignoredSourceIds(value) {
62987
63421
  return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
62988
63422
  }
63423
+ function parseRequestFullTextRange(value) {
63424
+ if (value === undefined)
63425
+ return;
63426
+ if (typeof value !== "string") {
63427
+ throw new ContextError(ExitCode.UserError, "--request-full-text-range must be start:end", {
63428
+ category: ErrorCategory.UserInputInvalid,
63429
+ flag: "--request-full-text-range"
63430
+ });
63431
+ }
63432
+ const match = /^(\d+):(\d+)$/u.exec(value.trim());
63433
+ if (!match) {
63434
+ throw new ContextError(ExitCode.UserError, "--request-full-text-range must be start:end", {
63435
+ category: ErrorCategory.UserInputInvalid,
63436
+ flag: "--request-full-text-range",
63437
+ agent_hints: [{
63438
+ code: "compile-request-full-text-invalid",
63439
+ severity: "error",
63440
+ message: "request_full_text pagination uses block-relative line ranges such as 21:40.",
63441
+ path: "request_full_text.range",
63442
+ reason_code: "range-format-invalid",
63443
+ next_action: "Use raw_snippets[].full_text_page.next_range or omit --request-full-text-range."
63444
+ }]
63445
+ });
63446
+ }
63447
+ const start2 = Number(match[1]);
63448
+ const end = Number(match[2]);
63449
+ if (!Number.isSafeInteger(start2) || !Number.isSafeInteger(end) || start2 < 1 || end < start2) {
63450
+ throw new ContextError(ExitCode.UserError, "--request-full-text-range must be an increasing positive line range", {
63451
+ category: ErrorCategory.UserInputInvalid,
63452
+ flag: "--request-full-text-range"
63453
+ });
63454
+ }
63455
+ return { start: start2, end };
63456
+ }
62989
63457
  function coverageSkipMissingOptionsError(missingFields, mode = "single") {
62990
63458
  const flag = mode === "bulk" ? "--coverage-skip-unresolved" : "--coverage-skip";
62991
63459
  const command = mode === "bulk" ? 'context compile --coverage-skip-unresolved --coverage-disposition-node <slug> --reason "<reason>"' : 'context compile --coverage-skip <candidate-id> --coverage-disposition-node <slug> --reason "<reason>"';
@@ -63029,7 +63497,7 @@ function compileScopeForOptions(options) {
63029
63497
  function registerWorkflowCommands(program2) {
63030
63498
  registerWorkflowStateCommands(program2);
63031
63499
  registerAlignWorkflowCommand(program2);
63032
- program2.command("compile").description("Workflow helpers for /context:compile").option("--scan-changes", "print incremental compile workset without creating a workflow lock").addOption(new Option("--scan", "removed; use --scan-changes").hideHelp()).option("--format <format>", "with --scan-changes/--context/--source-refs, output format: json | table", "table").option("--view <view>", "compact output view: full | summary | source-refs | issues").option("--ignore-source <source-id>", "with --scan-changes, --context, or --source-refs, ignore refresh-applied source ids", collectIgnoredSource, []).option("--context <slug>", "prepare NodeContext payload for one align node and print summary/digests").option("--cover-uncovered-only", "with --context, return only unresolved coverage candidates for a targeted repair round").option("--source-refs <slug>", "print compact citation source_ref list for one align node").option("--request-full-text <block-id>", "with --context, expand one visible finalized evidence block", collectRequestedBlock, []).option("--node-cycle <slug>", "validate one node draft, prepare reconcile, review safe defaults, and apply in one command").option("--delegated", "with a new compile workflow, record user-authorized delegated mode for low-risk review decisions").option("--draft <slug>", "validate a compile draft JSON/YAML document from stdin, or with --prepare reuse the saved draft").option("--prepare", "with --draft --plan, also prepare the semantic reconcile payload").option("--draft-status <slug>", "print the current compile draft session summary").option("--draft-patch <slug>", "apply a compile draft patch for one node").option("--input <stdin>", "compile draft JSON/YAML document used with --draft; use '-' for stdin").option("--accept-safe-defaults", "with --node-cycle, accept mechanically safe defaults and stop if anything needs judgment").option("--coverage-disposition <stdin>", "coverage disposition patch JSON/YAML document; use '-' for stdin").option("--coverage-skip <candidate-id>", "skip one coverage candidate without writing a disposition file").option("--coverage-skip-unresolved", "skip every unresolved coverage candidate in the node payload").option("--coverage-disposition-node <slug>", "node slug for --coverage-disposition").option("--coverage-disposition-replace", "replace existing dispositions for the patched coverage candidates").option("--payload-digest <digest>", "optional workflow payload digest stale guard for write commands").option("--reason <text>", "reason for coverage skip shortcuts").option("--plan", "validate the draft without writing knowledge").option("--close", "run compile close: compact, rebuild index, append changelog, verify").option("--save-input", "with --draft, save the consumed draft under output/compile/compile.<slug>.draft.yaml").action(async (options) => {
63500
+ program2.command("compile").description("Workflow helpers for /context:compile").option("--scan-changes", "print incremental compile workset without creating a workflow lock").addOption(new Option("--scan", "removed; use --scan-changes").hideHelp()).option("--format <format>", "with --scan-changes/--context/--source-refs, output format: json | table", "table").option("--view <view>", "compact output view: full | summary | source-refs | issues").option("--ignore-source <source-id>", "with --scan-changes, --context, or --source-refs, ignore refresh-applied source ids", collectIgnoredSource, []).option("--context <slug>", "prepare NodeContext payload for one align node and print summary/digests").option("--cover-uncovered-only", "with --context, return only unresolved coverage candidates for a targeted repair round").option("--source-refs <slug>", "print compact citation source_ref list for one align node").option("--request-full-text <block-id>", "with --context, expand one visible finalized evidence block", collectRequestedBlock, []).option("--request-full-text-range <start:end>", "with --request-full-text, read a block-relative line page").option("--node-cycle <slug>", "validate one node draft, prepare reconcile, review safe defaults, and apply in one command").option("--delegated", "with a new compile workflow, record user-authorized delegated mode for low-risk review decisions").option("--draft <slug>", "validate a compile draft JSON/YAML document from stdin, or with --prepare reuse the saved draft").option("--prepare", "with --draft --plan, also prepare the semantic reconcile payload").option("--draft-status <slug>", "print the current compile draft session summary").option("--draft-patch <slug>", "apply a compile draft patch for one node").option("--input <stdin>", "compile draft JSON/YAML document used with --draft; use '-' for stdin").option("--accept-safe-defaults", "with --node-cycle, accept mechanically safe defaults and stop if anything needs judgment").option("--coverage-disposition <stdin>", "coverage disposition patch JSON/YAML document; use '-' for stdin").option("--coverage-skip <candidate-id>", "skip one coverage candidate without writing a disposition file").option("--coverage-skip-unresolved", "skip every unresolved coverage candidate in the node payload").option("--coverage-disposition-node <slug>", "node slug for --coverage-disposition").option("--coverage-disposition-replace", "replace existing dispositions for the patched coverage candidates").option("--payload-digest <digest>", "optional workflow payload digest stale guard for write commands").option("--reason <text>", "reason for coverage skip shortcuts").option("--plan", "validate the draft without writing knowledge").option("--close", "run compile close: compact, rebuild index, append changelog, verify").option("--save-input", "with --draft, save the consumed draft under output/compile/compile.<slug>.draft.yaml").action(async (options) => {
63033
63501
  if (options.scan === true) {
63034
63502
  throw new ContextError(ExitCode.UserError, "context compile --scan was removed; use context compile --scan-changes", {
63035
63503
  category: ErrorCategory.UserInputInvalid,
@@ -63045,7 +63513,7 @@ function registerWorkflowCommands(program2) {
63045
63513
  const wantsChanges = options.scanChanges === true;
63046
63514
  const actionCount = countActions(wantsChanges, typeof options.context === "string", typeof options.sourceRefs === "string", typeof options.nodeCycle === "string", typeof options.draft === "string", typeof options.draftStatus === "string", typeof options.draftPatch === "string", typeof options.coverageDisposition === "string", typeof options.coverageSkip === "string", options.coverageSkipUnresolved === true, options.close === true);
63047
63515
  if (actionCount !== 1) {
63048
- throw new ContextError(ExitCode.UserError, "usage: context compile --scan-changes [--delegated] | --context <slug> [--delegated] | --source-refs <slug> | --node-cycle <slug> --input - --accept-safe-defaults [--delegated] | --draft <slug> --input - --plan [--prepare] [--delegated] | --draft <slug> --plan --prepare | --draft-status <slug> | --draft-patch <slug> --input - --plan [--payload-digest <digest>] | --coverage-disposition - --coverage-disposition-node <slug> [--payload-digest <digest>] | --coverage-skip <candidate-id> --coverage-disposition-node <slug> --reason <text> | --coverage-skip-unresolved --coverage-disposition-node <slug> --reason <text> | --close", { category: ErrorCategory.UserInputInvalid });
63516
+ throw new ContextError(ExitCode.UserError, "usage: context compile --scan-changes [--delegated] | --context <slug> [--request-full-text <block-id> [--request-full-text-range <start:end>]] [--delegated] | --source-refs <slug> | --node-cycle <slug> --input - --accept-safe-defaults [--delegated] | --draft <slug> --input - --plan [--prepare] [--delegated] | --draft <slug> --plan --prepare | --draft-status <slug> | --draft-patch <slug> --input - --plan [--payload-digest <digest>] | --coverage-disposition - --coverage-disposition-node <slug> [--payload-digest <digest>] | --coverage-skip <candidate-id> --coverage-disposition-node <slug> --reason <text> | --coverage-skip-unresolved --coverage-disposition-node <slug> --reason <text> | --close", { category: ErrorCategory.UserInputInvalid });
63049
63517
  }
63050
63518
  const viewWasExplicit = typeof options.view === "string";
63051
63519
  const view = compileOutputView(options.view);
@@ -63073,6 +63541,7 @@ function registerWorkflowCommands(program2) {
63073
63541
  }
63074
63542
  }
63075
63543
  const requestFullTextBlockIds = ignoredSourceIds(options.requestFullText);
63544
+ const requestFullTextRange = parseRequestFullTextRange(options.requestFullTextRange);
63076
63545
  const coverUncoveredOnly = options.coverUncoveredOnly === true;
63077
63546
  const delegated = options.delegated === true;
63078
63547
  const delegatedAllowed = wantsChanges || typeof options.context === "string" || typeof options.nodeCycle === "string" || typeof options.draft === "string";
@@ -63111,6 +63580,20 @@ function registerWorkflowCommands(program2) {
63111
63580
  flag: "--request-full-text"
63112
63581
  });
63113
63582
  }
63583
+ if (requestFullTextRange !== undefined && requestFullTextBlockIds.length !== 1) {
63584
+ throw new ContextError(ExitCode.UserError, "--request-full-text-range requires exactly one --request-full-text block id", {
63585
+ category: ErrorCategory.UserInputInvalid,
63586
+ flag: "--request-full-text-range",
63587
+ agent_hints: [{
63588
+ code: "compile-request-full-text-invalid",
63589
+ severity: "error",
63590
+ message: "request_full_text range continues one long block page at a time.",
63591
+ path: "request_full_text.range",
63592
+ reason_code: "range-requires-single-block",
63593
+ next_action: "Retry with one --request-full-text <block_id>, or omit --request-full-text-range."
63594
+ }]
63595
+ });
63596
+ }
63114
63597
  if (coverUncoveredOnly && typeof options.context !== "string") {
63115
63598
  throw new ContextError(ExitCode.UserError, "--cover-uncovered-only is only valid with --context", {
63116
63599
  category: ErrorCategory.UserInputInvalid,
@@ -63229,7 +63712,8 @@ function registerWorkflowCommands(program2) {
63229
63712
  if (typeof options.sourceRefs === "string") {
63230
63713
  const format = compileChangesFormat(options.format);
63231
63714
  const context = await getNodeContext(ctx.ctxDir, options.sourceRefs, {
63232
- ...requestFullTextBlockIds.length > 0 ? { requestFullTextBlockIds } : {}
63715
+ ...requestFullTextBlockIds.length > 0 ? { requestFullTextBlockIds } : {},
63716
+ ...requestFullTextRange !== undefined ? { requestFullTextRange } : {}
63233
63717
  });
63234
63718
  writeCompileSourceRefs(context, format);
63235
63719
  return;
@@ -63247,7 +63731,8 @@ function registerWorkflowCommands(program2) {
63247
63731
  let context = await getNodeContext(ctx.ctxDir, options.context, {
63248
63732
  ...view !== "source-refs" && !coverUncoveredOnly ? { mode: "changed-only" } : {},
63249
63733
  ...ignoreSourceIds.length > 0 ? { ignoreSourceIds } : {},
63250
- ...requestFullTextBlockIds.length > 0 ? { requestFullTextBlockIds } : {}
63734
+ ...requestFullTextBlockIds.length > 0 ? { requestFullTextBlockIds } : {},
63735
+ ...requestFullTextRange !== undefined ? { requestFullTextRange } : {}
63251
63736
  });
63252
63737
  const sourceFinalize = await readCurrentSourceOwnershipRecord(ctx.ctxDir).then((record) => record === null ? undefined : publishedSourceOwnershipSummary(record));
63253
63738
  const selectedCoverageStatus = coverUncoveredOnly ? await readCoverageWorkspaceStatus(ctx.ctxDir) : undefined;
@@ -63334,13 +63819,15 @@ function registerWorkflowCommands(program2) {
63334
63819
  scopeId,
63335
63820
  payload: "coverage-candidates",
63336
63821
  ...requestedDigest !== undefined ? { digest: requestedDigest } : {},
63337
- fallbackToUniqueScope: true
63822
+ fallbackToUniqueScope: requestedNode === undefined
63338
63823
  });
63339
63824
  if (requestedNode === undefined)
63340
63825
  inferred.push(`node/scope: ${payload.scope_id}`);
63341
63826
  if (requestedDigest === undefined)
63342
63827
  inferred.push("coverage payload digest");
63343
63828
  const value = payload.value;
63829
+ const subjectNode = requestedNode ?? value.node ?? scopeId;
63830
+ const nodeMetadataWarning = requestedNode !== undefined && value.node !== undefined && value.node !== requestedNode ? `coverage payload node metadata was ${value.node}; using requested node ${requestedNode}` : undefined;
63344
63831
  const candidates = Array.isArray(value.candidates) ? value.candidates : [];
63345
63832
  const patchInput = typeof options.coverageSkip === "string" ? coverageSkipPatch({ candidateId: options.coverageSkip, reason: String(options.reason).trim() }) : options.coverageSkipUnresolved === true ? coverageSkipUnresolvedPatch({ candidates, reason: String(options.reason).trim() }) : await readStructuredInput2(resolveStdinOnlyInput(options.coverageDisposition, "--coverage-disposition"));
63346
63833
  const patch = await validateCoverageDispositions({ ctxDir: ctx.ctxDir, candidates, patch: patchInput });
@@ -63354,10 +63841,11 @@ function registerWorkflowCommands(program2) {
63354
63841
  process.stdout.write(formatFeedback({
63355
63842
  symbol: status.high_signal_unresolved > 0 ? "⚠" : "✓",
63356
63843
  action: wantsCoverageSkip ? "skipped" : "applied",
63357
- subject: typeof options.coverageSkip === "string" ? `coverage candidate ${options.coverageSkip}` : options.coverageSkipUnresolved === true ? `${skippedCount} unresolved coverage candidate(s) for ${value.node ?? scopeId}` : `coverage disposition ${options.coverageDispositionNode}`,
63844
+ subject: typeof options.coverageSkip === "string" ? `coverage candidate ${options.coverageSkip}` : options.coverageSkipUnresolved === true ? `${skippedCount} unresolved coverage candidate(s) for ${subjectNode}` : `coverage disposition ${options.coverageDispositionNode}`,
63358
63845
  headline: formatCoverageSummary(status),
63359
63846
  body: [
63360
63847
  workflowSummaryLine(workflowState),
63848
+ nodeMetadataWarning,
63361
63849
  ...inferred.length > 0 ? [`inferred from current workflow: ${inferred.join("; ")}`] : [],
63362
63850
  `payload digest: ${payload.digest} (optional stale guard)`
63363
63851
  ],
@@ -64742,7 +65230,7 @@ function reconcileSchemaExample(name) {
64742
65230
  "Run context reconcile review before apply. Review persists the ready artifact; apply reads it from the current workflow scope.",
64743
65231
  "If prepare items include default_decision and you accept it, emit one compact decision object per item_id with accept_default: true; review hydrates target/proposed/action from that default.",
64744
65232
  "Do not use decisions: [] to accept all defaults; an empty decisions array means no decisions were made.",
64745
- "Keep proposed.content as the user-facing Section text. For long content, include proposed.summary as one plain paragraph; do not emit retired long-form fields.",
65233
+ "Keep proposed.content as the user-facing Section text. Include proposed.summary only when it helps reader scanability or query output; do not emit retired long-form fields.",
64746
65234
  "Do not copy basis/evidence text into hidden fields just to show the original raw text; source_ref/source_refs already provide traceability. Preserved raw wording is acceptable only when it is active user-facing Section content.",
64747
65235
  "For kind=example, preserving the cited fenced code/config/command block in proposed.content is active example knowledge, not raw-evidence echo.",
64748
65236
  'Use source_ref/source_refs copied from raw_snippets[].source_ref. Only run context source resolve-ref --node <slug> --text "<quote>" for manual recovery from a short quoted text fragment.',
@@ -64846,6 +65334,7 @@ function compileSchemaExample(name) {
64846
65334
  "Op naming standard: compile-draft actions[] already targets Sections, so Section lifecycle ops are verb-only names. Use op: add for a new Section; do not use add_section, write_section, or propose_section.",
64847
65335
  "Every add/update/supersede Section write uses content plus source_refs[]. content is the user-facing Section text and may be long or contain fenced code.",
64848
65336
  "If content is longer than 200 characters, provide summary. summary is LLM-authored, one plain paragraph, about content length / 10, minimum 10 characters, recommended maximum 120. The CLI reports summary quality as warning hints, not hard errors.",
65337
+ "source_support hard-term matching checks content (and legacy detail when present), not summary. Keep summary faithful to content, but do not add raw-only keywords just to satisfy source_support.",
64849
65338
  "content should default to the cited raw wording. Only make semantic-preserving edits for formatting, typos, casing, entity/alias consistency, or sentence cleanup. If raw is already clear, keep content equal to raw.",
64850
65339
  "source_support is a lexical diagnostic, not a target to game. There is no separate default evidence-echo warning; preserve source wording only when it is active user-facing knowledge, not traceability padding.",
64851
65340
  "For description/spec, a short summary plus cited descriptive bullets in content is allowed when the bullets themselves are useful knowledge. For code/config/command samples, prefer kind=example and keep the sample in content.",
@@ -64967,12 +65456,13 @@ function compileSchemaExample(name) {
64967
65456
  consumed_by: [
64968
65457
  "context compile --draft-patch <slug> --input - --plan"
64969
65458
  ],
64970
- required: ["schema_version", "operations"],
65459
+ required: ["operations"],
64971
65460
  enums: {
64972
65461
  op: ["replace_action", "remove_action", "add_action"]
64973
65462
  },
64974
65463
  notes: [
64975
65464
  "Read the current draft with context compile --draft-status <slug> --format json.",
65465
+ "schema_version may be omitted; when present it must match the schema_version shown here or draft-status.patch_schema_version.",
64976
65466
  "The CLI reads the current draft session by default; add --payload-digest only when an explicit stale guard is needed.",
64977
65467
  "replace_action keeps the same action_id; add_action receives a new CLI-owned action_id.",
64978
65468
  "The CLI validates the whole patched draft before saving it, so failed patches do not corrupt the current draft session."
@@ -65044,7 +65534,7 @@ function compileSchemaExample(name) {
65044
65534
  "section.content",
65045
65535
  "user_facing_report"
65046
65536
  ],
65047
- instruction: "Generate knowledge titles, summaries, and user-facing reports in Chinese; keep node.summary concise (target <15 tokens, never >30 tokens); section.summary is a short one-paragraph reader/query aid for long content; for source-bound Section content, prefer the cited source language when it differs from Chinese; preserve product names, code identifiers, CLI flags, block_id/source_ref tokens, slugs, and quoted evidence exactly when needed."
65537
+ instruction: "Generate knowledge titles, summaries, and user-facing reports in Chinese; keep node.summary concise (target <15 tokens, never >30 tokens); section.summary is optional reader/query aid when useful; for source-bound Section content, prefer the cited source language when it differs from Chinese; preserve product names, code identifiers, CLI flags, block_id/source_ref tokens, slugs, and quoted evidence exactly when needed."
65048
65538
  },
65049
65539
  existing: {
65050
65540
  sections: [{