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

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
@@ -17167,6 +17167,10 @@ function normalizeProposed(value, issues, path2) {
17167
17167
  validateProposedContent(value.content, issues, `${path2}.content`);
17168
17168
  proposed.content = value.content;
17169
17169
  }
17170
+ if (value.summary === null || typeof value.summary === "string")
17171
+ proposed.summary = value.summary;
17172
+ if (value.raw === null || typeof value.raw === "string")
17173
+ proposed.raw = value.raw;
17170
17174
  if (value.detail === null || typeof value.detail === "string")
17171
17175
  proposed.detail = value.detail;
17172
17176
  if (typeof value.confidence === "string")
@@ -17203,7 +17207,9 @@ function normalizeProposed(value, issues, path2) {
17203
17207
  validateProposedContent(section.content, issues, `${path2}.sections[${index}].content`);
17204
17208
  sections.push({
17205
17209
  kind: section.kind,
17210
+ ...section.summary === null || typeof section.summary === "string" ? { summary: section.summary } : {},
17206
17211
  content: section.content,
17212
+ ...section.raw === null || typeof section.raw === "string" ? { raw: section.raw } : {},
17207
17213
  source_ref: section.source_ref,
17208
17214
  ...section.detail === null || typeof section.detail === "string" ? { detail: section.detail } : {},
17209
17215
  ...typeof section.confidence === "string" ? { confidence: section.confidence } : {},
@@ -17216,15 +17222,8 @@ function normalizeProposed(value, issues, path2) {
17216
17222
  return proposed;
17217
17223
  }
17218
17224
  function validateProposedContent(content, issues, path2) {
17219
- if (content.length > MAX_PROPOSED_CONTENT_CHARS) {
17220
- pushIssue(issues, path2, `content must be ${MAX_PROPOSED_CONTENT_CHARS} characters or fewer; current length is ${content.length} (over by ${content.length - MAX_PROPOSED_CONTENT_CHARS}); move long-form prose into detail`);
17221
- }
17222
- if (content.includes(`
17223
- `) || content.includes("\r")) {
17224
- pushIssue(issues, path2, "content must be a single-line claim; move multi-line prose or code into detail");
17225
- }
17226
- if (FENCED_CODE_RE.test(content)) {
17227
- pushIssue(issues, path2, "content must not contain fenced code; put code blocks in detail");
17225
+ if (content.trim().length === 0) {
17226
+ pushIssue(issues, path2, "content must be non-empty");
17228
17227
  }
17229
17228
  }
17230
17229
  function normalizeUserConfirmation(value) {
@@ -17459,7 +17458,7 @@ function parseSemanticDecisionDocument(value) {
17459
17458
  }
17460
17459
  return result.document;
17461
17460
  }
17462
- var SEMANTIC_DECISION_SCHEMA_VERSION = "1.0", SEMANTIC_LEDGER_SCHEMA_VERSION = "decisions.semantic.v2", DECISION_ARCHIVE_SCHEMA_VERSION = "decisions.archive.v1", SEMANTIC_SAFE_DEFAULT_POLICY_VERSION = "reconcile.safe-default.v2", SEMANTIC_RECONCILE_MODES, SEMANTIC_RELATIONS, SEMANTIC_ACTIONS, MAX_PROPOSED_CONTENT_CHARS = 256, FENCED_CODE_RE, TARGET_REQUIRED_ACTIONS, OMIT_ALLOWED_RELATIONS, ASK_USER_TARGET_RELATIONS;
17461
+ var SEMANTIC_DECISION_SCHEMA_VERSION = "1.0", SEMANTIC_LEDGER_SCHEMA_VERSION = "decisions.semantic.v2", DECISION_ARCHIVE_SCHEMA_VERSION = "decisions.archive.v1", SEMANTIC_SAFE_DEFAULT_POLICY_VERSION = "reconcile.safe-default.v2", SEMANTIC_RECONCILE_MODES, SEMANTIC_RELATIONS, SEMANTIC_ACTIONS, TARGET_REQUIRED_ACTIONS, OMIT_ALLOWED_RELATIONS, ASK_USER_TARGET_RELATIONS;
17463
17462
  var init_types3 = __esm(() => {
17464
17463
  SEMANTIC_RECONCILE_MODES = ["compile", "drop", "refresh", "restore"];
17465
17464
  SEMANTIC_RELATIONS = [
@@ -17485,7 +17484,6 @@ var init_types3 = __esm(() => {
17485
17484
  "omit",
17486
17485
  "ask_user"
17487
17486
  ];
17488
- FENCED_CODE_RE = /(^|\n)\s*(```|~~~)/u;
17489
17487
  TARGET_REQUIRED_ACTIONS = new Set([
17490
17488
  "duplicate_skip",
17491
17489
  "merge_update",
@@ -18906,6 +18904,12 @@ function validateSection(section) {
18906
18904
  if (typeof section.content !== "string" || section.content.length === 0) {
18907
18905
  issues.push("content is required");
18908
18906
  }
18907
+ if (section.summary !== undefined && typeof section.summary !== "string") {
18908
+ issues.push("summary must be a string when present");
18909
+ }
18910
+ if (section.raw !== undefined && typeof section.raw !== "string") {
18911
+ issues.push("raw must be a string when present");
18912
+ }
18909
18913
  if (!includesValue(SECTION_STATUS_VALUES, section.status)) {
18910
18914
  issues.push(`status must be one of ${SECTION_STATUS_VALUES.join(", ")}`);
18911
18915
  }
@@ -19058,20 +19062,43 @@ var init_nodeDisplayGroups = __esm(() => {
19058
19062
  "Changelog"
19059
19063
  ];
19060
19064
  SECTION_GROUP_TITLES = new Set([
19061
- ...SECTION_GROUP_ORDER,
19062
- "Details"
19065
+ ...SECTION_GROUP_ORDER
19063
19066
  ]);
19064
19067
  });
19065
19068
 
19066
19069
  // src/lib/nodeParserSections.ts
19067
- function tokeniseCommentBody(body2) {
19068
- const matches = body2.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
19069
- return matches.map((match) => {
19070
- if (match.startsWith('"') && match.endsWith('"') || match.startsWith("'") && match.endsWith("'")) {
19071
- return match.slice(1, -1);
19070
+ function isSectionOpenComment(line) {
19071
+ return SECTION_OPEN_RE.test(line.trim());
19072
+ }
19073
+ function isLegacySectionOpenComment(line) {
19074
+ const match = LEGACY_SECTION_OPEN_RE.exec(line.trim());
19075
+ const kind = match?.[1];
19076
+ return kind !== undefined && Object.values(SectionKind).includes(kind);
19077
+ }
19078
+ function decodeAttr(value) {
19079
+ return value.replace(/"/gu, '"').replace(/-/gu, "-").replace(/&/gu, "&");
19080
+ }
19081
+ function parseAttributes(raw) {
19082
+ const text = raw.trim();
19083
+ const attributes = new Map;
19084
+ let index = 0;
19085
+ while (index < text.length) {
19086
+ while (/\s/u.test(text[index] ?? ""))
19087
+ index += 1;
19088
+ if (index >= text.length)
19089
+ break;
19090
+ ATTR_RE.lastIndex = index;
19091
+ const match = ATTR_RE.exec(text);
19092
+ if (!match) {
19093
+ throw new Error(`invalid c4a comment attribute near: ${text.slice(index, index + 32)}`);
19072
19094
  }
19073
- return match;
19074
- });
19095
+ const key = match[1];
19096
+ const value = match[2];
19097
+ if (key !== undefined && value !== undefined)
19098
+ attributes.set(key, decodeAttr(value));
19099
+ index = ATTR_RE.lastIndex;
19100
+ }
19101
+ return attributes;
19075
19102
  }
19076
19103
  function parseScalarAttribute(value) {
19077
19104
  const maybeNumber = Number(value);
@@ -19080,120 +19107,120 @@ function parseScalarAttribute(value) {
19080
19107
  }
19081
19108
  return value;
19082
19109
  }
19083
- function parseSectionRelationComment(line, currentSlug) {
19084
- const match = /^<!--\s*related=(.+?):(implements|supersedes|refines|conflicts|references)\s*-->$/.exec(line.trim());
19110
+ function requiredAttribute(attributes, key) {
19111
+ const value = attributes.get(key);
19112
+ if (value === undefined || value.length === 0) {
19113
+ throw new Error(`section comment must provide ${key}`);
19114
+ }
19115
+ return value;
19116
+ }
19117
+ function parseSectionComment(comment) {
19118
+ const match = SECTION_OPEN_RE.exec(comment.trim());
19085
19119
  if (!match) {
19120
+ throw new Error("section block must start with a c4a:section comment");
19121
+ }
19122
+ const attributes = parseAttributes(match[1] ?? "");
19123
+ const id2 = requiredAttribute(attributes, "id");
19124
+ const kind = requiredAttribute(attributes, "kind");
19125
+ const sourceRef = requiredAttribute(attributes, "source_ref");
19126
+ if (!Object.values(SectionKind).includes(kind)) {
19127
+ throw new Error(`unsupported section kind: ${kind}`);
19128
+ }
19129
+ return {
19130
+ id: id2,
19131
+ kind,
19132
+ sourceRef: sourceRef === "-" ? "" : sourceRef,
19133
+ attributes
19134
+ };
19135
+ }
19136
+ function parseSectionRelationComment(line) {
19137
+ const match = RELATION_RE.exec(line.trim());
19138
+ if (!match)
19086
19139
  return null;
19140
+ const attributes = parseAttributes(match[1] ?? "");
19141
+ const target = requiredAttribute(attributes, "target");
19142
+ const relation = requiredAttribute(attributes, "relation");
19143
+ if (!["implements", "supersedes", "refines", "conflicts", "references"].includes(relation)) {
19144
+ throw new Error(`unsupported section relation: ${relation}`);
19087
19145
  }
19088
- const target = match[1] ?? "";
19089
- const relation = match[2];
19090
19146
  const slashIndex = target.lastIndexOf("/");
19091
- const targetSlug = slashIndex >= 0 ? target.slice(0, slashIndex) : currentSlug;
19092
- const targetSectionId = slashIndex >= 0 ? target.slice(slashIndex + 1) : target;
19147
+ if (slashIndex <= 0 || slashIndex === target.length - 1) {
19148
+ throw new Error("c4a:relation target must use <slug>/<section-id>");
19149
+ }
19093
19150
  return {
19094
- target_slug: targetSlug,
19095
- target_section_id: targetSectionId,
19151
+ target_slug: target.slice(0, slashIndex),
19152
+ target_section_id: target.slice(slashIndex + 1),
19096
19153
  relation
19097
19154
  };
19098
19155
  }
19099
- function parseBlockquoteLines(lines) {
19100
- const stripped = lines.map((line) => line.replace(/^>\s?/, ""));
19101
- while (stripped.length > 0 && stripped[0]?.trim() === "") {
19102
- stripped.shift();
19103
- }
19104
- while (stripped.length > 0 && stripped[stripped.length - 1]?.trim() === "") {
19105
- stripped.pop();
19106
- }
19107
- if (stripped.length === 0) {
19108
- throw new Error("section blockquote is empty");
19109
- }
19110
- const firstLine = stripped[0] ?? "";
19111
- const content = firstLine.trim();
19112
- const detailLines = stripped.slice(1);
19113
- while (detailLines.length > 0 && detailLines[0]?.trim() === "") {
19114
- detailLines.shift();
19115
- }
19116
- const detail = unwrapRenderedDetail(detailLines).join(`
19117
- `).trim();
19118
- return detail.length > 0 ? { content, detail } : { content };
19156
+ function trimBlankEdges(lines) {
19157
+ const next = [...lines];
19158
+ while (next.length > 0 && (next[0]?.trim() ?? "") === "")
19159
+ next.shift();
19160
+ while (next.length > 0 && (next[next.length - 1]?.trim() ?? "") === "")
19161
+ next.pop();
19162
+ return next;
19119
19163
  }
19120
- function unwrapRenderedDetail(lines) {
19121
- if ((lines[0] ?? "").trim().toLowerCase() !== "<details>") {
19122
- return lines;
19123
- }
19124
- let closeIndex = -1;
19125
- for (let index = lines.length - 1;index >= 0; index -= 1) {
19126
- if (lines[index]?.trim().toLowerCase() === "</details>") {
19127
- closeIndex = index;
19128
- break;
19129
- }
19164
+ function parseOptionalCommentBlock(input) {
19165
+ const start2 = input.lines.findIndex((line) => input.open.test(line.trim()));
19166
+ const end = input.lines.findIndex((line) => input.close.test(line.trim()));
19167
+ if (start2 < 0 && end >= 0) {
19168
+ throw new Error(`section ${input.sectionId} has c4a:${input.name} close without open`);
19130
19169
  }
19131
- if (closeIndex < 0) {
19132
- return lines;
19170
+ if (start2 >= 0 && end < 0) {
19171
+ throw new Error(`section ${input.sectionId} is missing /c4a:${input.name}`);
19133
19172
  }
19134
- let bodyStart = 1;
19135
- if ((lines[bodyStart] ?? "").trim().toLowerCase() === "<summary>details</summary>") {
19136
- bodyStart += 1;
19173
+ if (start2 >= 0 && end < start2) {
19174
+ throw new Error(`section ${input.sectionId} has /c4a:${input.name} before c4a:${input.name}`);
19137
19175
  }
19138
- const unwrapped = lines.slice(bodyStart, closeIndex);
19139
- while (unwrapped.length > 0 && unwrapped[0]?.trim() === "") {
19140
- unwrapped.shift();
19176
+ if (start2 >= 0 && input.lines.slice(start2 + 1).some((line, offset) => offset !== end - start2 - 1 && input.open.test(line.trim()))) {
19177
+ throw new Error(`section ${input.sectionId} must contain at most one c4a:${input.name} block`);
19141
19178
  }
19142
- while (unwrapped.length > 0 && unwrapped[unwrapped.length - 1]?.trim() === "") {
19143
- unwrapped.pop();
19179
+ if (start2 < 0 || end < 0)
19180
+ return { before: [...input.lines], after: [] };
19181
+ const valueLines = trimBlankEdges(input.lines.slice(start2 + 1, end));
19182
+ if (valueLines.length === 0) {
19183
+ throw new Error(`section ${input.sectionId} has empty c4a:${input.name}`);
19144
19184
  }
19145
- return unwrapped;
19185
+ return {
19186
+ value: valueLines.join(`
19187
+ `).trim(),
19188
+ before: trimBlankEdges(input.lines.slice(0, start2)),
19189
+ after: trimBlankEdges(input.lines.slice(end + 1))
19190
+ };
19146
19191
  }
19147
- function parseSectionComment(comment) {
19148
- const commentMatch = /^<!--\s*([\s\S]+?)\s*-->$/.exec(comment);
19149
- if (!commentMatch) {
19150
- throw new Error("section block must start with a comment");
19151
- }
19152
- const tokens = tokeniseCommentBody(commentMatch[1] ?? "");
19153
- const [id2, kindToken, sourceRefToken, ...restTokens] = tokens;
19154
- if (typeof id2 !== "string" || typeof kindToken !== "string" || typeof sourceRefToken !== "string") {
19155
- throw new Error("section comment must provide id, kind, and source_ref");
19156
- }
19157
- if (!(kindToken in SectionKind)) {
19158
- throw new Error(`unsupported section kind: ${kindToken}`);
19159
- }
19160
- const lineRangeToken = restTokens[0];
19161
- const hasInlineLineRange = typeof lineRangeToken === "string" && /^L\d+-L?\d+(?:@[a-f0-9]{8,64})?$/iu.test(lineRangeToken);
19162
- const sourceRefBase = sourceRefToken === "-" ? "" : sourceRefToken;
19163
- const sourceRef = hasInlineLineRange ? `${sourceRefBase} ${lineRangeToken}`.trim() : sourceRefBase;
19164
- const attributeTokens = hasInlineLineRange ? restTokens.slice(1) : restTokens;
19165
- const attributes = new Map;
19166
- for (const token of attributeTokens) {
19167
- const splitIndex = token.indexOf("=");
19168
- if (splitIndex <= 0)
19169
- continue;
19170
- attributes.set(token.slice(0, splitIndex), token.slice(splitIndex + 1));
19192
+ function parseSectionBody(lines, sectionId) {
19193
+ const summary = parseOptionalCommentBlock({
19194
+ lines,
19195
+ sectionId,
19196
+ name: "summary",
19197
+ open: SUMMARY_OPEN_RE,
19198
+ close: SUMMARY_CLOSE_RE
19199
+ });
19200
+ if (summary.value !== undefined && summary.before.length > 0) {
19201
+ throw new Error(`section ${sectionId} has content before c4a:summary`);
19171
19202
  }
19172
- return { id: id2, kind: kindToken, sourceRef, attributes };
19173
- }
19174
- function collectSectionRelations(lines, startIndex, anchorSlug) {
19175
- const relations = [];
19176
- let index = startIndex;
19177
- while (index < lines.length) {
19178
- const relation = parseSectionRelationComment(lines[index] ?? "", anchorSlug);
19179
- if (!relation)
19180
- break;
19181
- relations.push(relation);
19182
- index += 1;
19203
+ 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
+ });
19211
+ if (raw.after.length > 0) {
19212
+ throw new Error(`section ${sectionId} has content after /c4a:raw`);
19183
19213
  }
19184
- return { relations, nextIndex: index };
19185
- }
19186
- function collectBlockquote(lines, startIndex) {
19187
- const quoteLines = [];
19188
- let index = startIndex;
19189
- while (index < lines.length) {
19190
- const line = lines[index] ?? "";
19191
- if (!line.startsWith(">"))
19192
- break;
19193
- quoteLines.push(line);
19194
- index += 1;
19214
+ const contentLines = trimBlankEdges(raw.before);
19215
+ if (contentLines.length === 0) {
19216
+ throw new Error(`section ${sectionId} is missing content`);
19195
19217
  }
19196
- return { quoteLines, nextIndex: index };
19218
+ return {
19219
+ ...summary.value !== undefined ? { summary: summary.value } : {},
19220
+ content: contentLines.join(`
19221
+ `).trim(),
19222
+ ...raw.value !== undefined ? { raw: raw.value } : {}
19223
+ };
19197
19224
  }
19198
19225
  function sectionFromParts(input) {
19199
19226
  const refersToNodes = input.attributes.get("refers_to_nodes");
@@ -19204,11 +19231,12 @@ function sectionFromParts(input) {
19204
19231
  id: input.id,
19205
19232
  anchor_slug: input.anchorSlug,
19206
19233
  kind: input.kind,
19234
+ ...input.body.summary !== undefined ? { summary: input.body.summary } : {},
19207
19235
  content: input.body.content,
19236
+ ...input.body.raw !== undefined ? { raw: input.body.raw } : {},
19208
19237
  status: input.attributes.get("status") ?? SectionStatus.active,
19209
19238
  confidence: input.attributes.get("confidence") ?? Confidence.confirmed,
19210
19239
  source_ref: input.sourceRef,
19211
- ...input.body.detail !== undefined ? { detail: input.body.detail } : {},
19212
19240
  ...refersToNodes ? { refers_to_nodes: refersToNodes.split(",").filter(Boolean) } : {},
19213
19241
  ...workspaceId !== undefined ? { workspace_id: workspaceId } : {},
19214
19242
  ...validFrom !== undefined ? { valid_from: parseScalarAttribute(validFrom) } : {},
@@ -19219,29 +19247,58 @@ function sectionFromParts(input) {
19219
19247
  function parseSectionBlock(lines, startIndex, anchorSlug) {
19220
19248
  const comment = lines[startIndex]?.trim() ?? "";
19221
19249
  const parsed = parseSectionComment(comment);
19222
- const relationResult = collectSectionRelations(lines, startIndex + 1, anchorSlug);
19223
- const quoteResult = collectBlockquote(lines, relationResult.nextIndex);
19224
- if (quoteResult.quoteLines.length === 0) {
19225
- throw new Error(`section ${parsed.id} is missing blockquote content`);
19250
+ const relations = [];
19251
+ const bodyLines = [];
19252
+ let index = startIndex + 1;
19253
+ let foundClose = false;
19254
+ while (index < lines.length) {
19255
+ const line = lines[index] ?? "";
19256
+ if (SECTION_CLOSE_RE.test(line.trim())) {
19257
+ foundClose = true;
19258
+ index += 1;
19259
+ break;
19260
+ }
19261
+ if (isSectionOpenComment(line)) {
19262
+ throw new Error(`section ${parsed.id} is missing /c4a:section before next section`);
19263
+ }
19264
+ const relation = bodyLines.length === 0 ? parseSectionRelationComment(line) : null;
19265
+ if (relation) {
19266
+ relations.push(relation);
19267
+ } else {
19268
+ bodyLines.push(line);
19269
+ }
19270
+ index += 1;
19271
+ }
19272
+ if (!foundClose) {
19273
+ throw new Error(`section ${parsed.id} is missing /c4a:section`);
19226
19274
  }
19227
19275
  const section = sectionFromParts({
19228
19276
  ...parsed,
19229
19277
  anchorSlug,
19230
- relations: relationResult.relations,
19231
- body: parseBlockquoteLines(quoteResult.quoteLines)
19278
+ relations,
19279
+ body: parseSectionBody(bodyLines, parsed.id)
19232
19280
  });
19233
19281
  const issues = validateSection(section);
19234
19282
  if (issues.length > 0) {
19235
19283
  throw new Error(`invalid section ${parsed.id}: ${issues.join("; ")}`);
19236
19284
  }
19237
- let index = quoteResult.nextIndex;
19238
19285
  while (index < lines.length && (lines[index]?.trim() ?? "") === "") {
19239
19286
  index += 1;
19240
19287
  }
19241
19288
  return { section, nextIndex: index };
19242
19289
  }
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;
19243
19291
  var init_nodeParserSections = __esm(() => {
19244
19292
  init_knowledge();
19293
+ SECTION_OPEN_RE = /^<!--\s*c4a:section\b([\s\S]*?)\s*-->$/u;
19294
+ SECTION_CLOSE_RE = /^<!--\s*\/c4a:section\s*-->$/u;
19295
+ LEGACY_SECTION_OPEN_RE = /^<!--\s*section-\d+\s+([A-Za-z_][\w-]*)\b[\s\S]*-->$/u;
19296
+ SUMMARY_OPEN_RE = /^<!--\s*c4a:summary\s*-->$/u;
19297
+ 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;
19300
+ RELATION_RE = /^<!--\s*c4a:relation\b([\s\S]*?)\s*-->$/u;
19301
+ ATTR_RE = /([A-Za-z_][\w:-]*)="([^"]*)"/yu;
19245
19302
  });
19246
19303
 
19247
19304
  // src/lib/nodeParser.ts
@@ -19360,7 +19417,8 @@ function parseContainsList(lines, startIndex, rootSlug) {
19360
19417
  let index = startIndex + 1;
19361
19418
  while (index < lines.length) {
19362
19419
  const line = lines[index] ?? "";
19363
- if (/^<!--\s*section-\d+\s+/.test(line.trim())) {
19420
+ assertNoLegacySectionMarker(line);
19421
+ if (isSectionOpenComment(line)) {
19364
19422
  break;
19365
19423
  }
19366
19424
  const nextHeading = /^(#{1,6})\s+/.exec(line);
@@ -19396,7 +19454,8 @@ function skipDerivedList(lines, startIndex, title) {
19396
19454
  let index = startIndex + 1;
19397
19455
  while (index < lines.length) {
19398
19456
  const line = lines[index] ?? "";
19399
- if (/^<!--\s*section-\d+\s+/.test(line.trim()))
19457
+ assertNoLegacySectionMarker(line);
19458
+ if (isSectionOpenComment(line))
19400
19459
  break;
19401
19460
  const nextHeading = /^(#{1,6})\s+/.exec(line);
19402
19461
  if (nextHeading && (nextHeading[1]?.length ?? 0) <= level)
@@ -19407,6 +19466,11 @@ function skipDerivedList(lines, startIndex, title) {
19407
19466
  index += 1;
19408
19467
  return index;
19409
19468
  }
19469
+ function assertNoLegacySectionMarker(line) {
19470
+ if (!isLegacySectionOpenComment(line))
19471
+ return;
19472
+ throw new Error('legacy section marker detected; this workspace uses retired section comments. Re-run compile/close with a current CLI or migrate the article to <!-- c4a:section id="..." kind="..." source_ref="..." --> blocks before querying or compiling.');
19473
+ }
19410
19474
  function collapseBlankEdges(value) {
19411
19475
  return value.replace(/^\n+/, "").replace(/\n+$/, "");
19412
19476
  }
@@ -19476,6 +19540,7 @@ function parseNodeBlock(lines, startIndex, headingLevel) {
19476
19540
  const childHeadingPattern = new RegExp(`^#{${childLevel}}\\s+.+`);
19477
19541
  while (index < lines.length) {
19478
19542
  const currentLine = lines[index] ?? "";
19543
+ assertNoLegacySectionMarker(currentLine);
19479
19544
  const siblingMatch = /^(#{1,6})\s+/.exec(currentLine);
19480
19545
  if (siblingMatch) {
19481
19546
  const level = siblingMatch[1]?.length ?? 0;
@@ -19491,7 +19556,7 @@ function parseNodeBlock(lines, startIndex, headingLevel) {
19491
19556
  continue;
19492
19557
  }
19493
19558
  }
19494
- if (/^<!--\s*section-\d+\s+/.test(currentLine.trim())) {
19559
+ if (isSectionOpenComment(currentLine)) {
19495
19560
  const sectionParsed = parseSectionBlock(lines, index, node2.id);
19496
19561
  sections.push(sectionParsed.section);
19497
19562
  index = sectionParsed.nextIndex;
@@ -19627,13 +19692,14 @@ function parseRootNodeFrontmatterFirst(normalized) {
19627
19692
  const childLevel = 2;
19628
19693
  while (index < lines.length) {
19629
19694
  const currentLine = lines[index] ?? "";
19695
+ assertNoLegacySectionMarker(currentLine);
19630
19696
  if (new RegExp(`^#{${childLevel}}\\s+.+`).test(currentLine) && peekIsFrontmatterAfterHeading(lines, index)) {
19631
19697
  const childResult = parseNodeBlock(lines, index, childLevel);
19632
19698
  children.push(childResult.parsed);
19633
19699
  index = childResult.nextIndex;
19634
19700
  continue;
19635
19701
  }
19636
- if (/^<!--\s*section-\d+\s+/.test(currentLine.trim())) {
19702
+ if (isSectionOpenComment(currentLine)) {
19637
19703
  const sectionParsed = parseSectionBlock(lines, index, node2.id);
19638
19704
  sections.push(sectionParsed.section);
19639
19705
  index = sectionParsed.nextIndex;
@@ -19697,62 +19763,6 @@ var init_nodeParser = __esm(() => {
19697
19763
  import_yaml9 = __toESM(require_dist(), 1);
19698
19764
  });
19699
19765
 
19700
- // src/lib/sectionDetail.ts
19701
- function stripEvidenceEchoPrefix(value) {
19702
- const match = EVIDENCE_ECHO_PREFIX_RE.exec(value);
19703
- if (match === null)
19704
- return { text: value };
19705
- const label = match[1];
19706
- if (label === undefined)
19707
- return { text: value };
19708
- return {
19709
- text: value.slice(match[0].length),
19710
- label
19711
- };
19712
- }
19713
- function normalizeDetailEchoText(value) {
19714
- return value.normalize("NFKC").replace(/[`*~>#\[\]()[\]{},。;!?'"“”‘’、]+/gu, " ").replace(/\s+/gu, " ").trim().toLowerCase();
19715
- }
19716
- function isEvidenceEchoDetail(detail, basisTexts = []) {
19717
- const trimmed = detail.trim();
19718
- if (trimmed.length === 0)
19719
- return false;
19720
- const stripped = stripEvidenceEchoPrefix(trimmed);
19721
- const normalizedDetail = normalizeDetailEchoText(stripped.text);
19722
- if (normalizedDetail.length === 0)
19723
- return false;
19724
- const normalizedBasis = normalizeDetailEchoText(basisTexts.join(`
19725
- `));
19726
- if (normalizedBasis.length > 0 && normalizedDetail === normalizedBasis)
19727
- return true;
19728
- return stripped.label !== undefined && PREFIX_ONLY_ECHO_LABEL_RE.test(stripped.label);
19729
- }
19730
- function fencedBlocksIn(value) {
19731
- return [...value.matchAll(FENCED_DETAIL_BLOCK_RE)].map((match) => normalizeMarkdown(match[0]).trim()).filter((block) => block.length > 0);
19732
- }
19733
- function basisText(value) {
19734
- return typeof value === "string" ? value : value.join(`
19735
- `);
19736
- }
19737
- function hasCitedFencedDetail(detail, basisTexts) {
19738
- const cited = normalizeMarkdown(basisText(basisTexts)).trim();
19739
- if (cited.length === 0)
19740
- return false;
19741
- return fencedBlocksIn(detail).some((block) => cited.includes(block));
19742
- }
19743
- function renderableSectionDetail(detail) {
19744
- if (detail === undefined || detail.trim().length === 0)
19745
- return;
19746
- return isEvidenceEchoDetail(detail) ? undefined : detail;
19747
- }
19748
- var EVIDENCE_ECHO_PREFIX_RE, PREFIX_ONLY_ECHO_LABEL_RE, FENCED_DETAIL_BLOCK_RE;
19749
- var init_sectionDetail = __esm(() => {
19750
- init_normalize();
19751
- EVIDENCE_ECHO_PREFIX_RE = /^\s*(原文|原始文本|原始引用|引用|证据|source|raw|quote|original)\s*[::]\s*/iu;
19752
- PREFIX_ONLY_ECHO_LABEL_RE = /^(?:原文|原始文本|原始引用|引用|证据)$/iu;
19753
- FENCED_DETAIL_BLOCK_RE = /```[^\n`]*\n[\s\S]*?```|~~~[^\n~]*\n[\s\S]*?~~~/gu;
19754
- });
19755
-
19756
19766
  // src/lib/nodeRenderer.ts
19757
19767
  function sectionOrdinal(id2) {
19758
19768
  const match = /^section-(\d+)$/.exec(id2);
@@ -19800,48 +19810,51 @@ function renderFrontmatter(node2) {
19800
19810
  frontmatter.symbol_kind = node2.symbol_kind;
19801
19811
  return import_yaml10.default.stringify(frontmatter).trimEnd();
19802
19812
  }
19813
+ function encodeAttr(value) {
19814
+ return value.replace(/&/gu, "&amp;").replace(/"/gu, "&quot;").replace(/-/gu, (match, offset, full) => full[offset - 1] === "-" || full[offset + 1] === "-" ? "&#45;" : match);
19815
+ }
19816
+ function renderAttrs(attrs) {
19817
+ return attrs.map(([key, value]) => `${key}="${encodeAttr(value)}"`).join(" ");
19818
+ }
19803
19819
  function renderSectionComment(section) {
19804
19820
  const attrs = [];
19821
+ attrs.push(`id="${encodeAttr(section.id)}"`);
19822
+ attrs.push(`kind="${encodeAttr(section.kind)}"`);
19823
+ attrs.push(`source_ref="${encodeAttr(section.source_ref.length > 0 ? section.source_ref : "-")}"`);
19805
19824
  if (section.status !== SectionStatus.active) {
19806
- attrs.push(`status=${section.status}`);
19825
+ attrs.push(`status="${encodeAttr(section.status)}"`);
19807
19826
  }
19808
19827
  if (section.confidence !== Confidence.confirmed) {
19809
- attrs.push(`confidence=${section.confidence}`);
19828
+ attrs.push(`confidence="${encodeAttr(section.confidence)}"`);
19810
19829
  }
19811
19830
  if (section.refers_to_nodes && section.refers_to_nodes.length > 0) {
19812
- attrs.push(`refers_to_nodes=${section.refers_to_nodes.join(",")}`);
19831
+ attrs.push(`refers_to_nodes="${encodeAttr(section.refers_to_nodes.join(","))}"`);
19813
19832
  }
19814
19833
  if (section.workspace_id !== undefined) {
19815
- attrs.push(`workspace_id=${section.workspace_id}`);
19834
+ attrs.push(`workspace_id="${encodeAttr(section.workspace_id)}"`);
19816
19835
  }
19817
19836
  if (section.valid_from !== undefined) {
19818
- attrs.push(`valid_from=${String(section.valid_from)}`);
19837
+ attrs.push(`valid_from="${encodeAttr(String(section.valid_from))}"`);
19819
19838
  }
19820
19839
  if (section.valid_until !== undefined) {
19821
- attrs.push(`valid_until=${String(section.valid_until)}`);
19840
+ attrs.push(`valid_until="${encodeAttr(String(section.valid_until))}"`);
19822
19841
  }
19823
- const relationLines = section.relations?.map((relation) => `<!-- related=${relation.target_slug}/${relation.target_section_id}:${relation.relation} -->`) ?? [];
19824
- const sourceRefToken = section.source_ref.length > 0 ? section.source_ref : "-";
19825
- const head = `<!-- ${section.id} ${section.kind} ${sourceRefToken}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} -->`;
19842
+ const relationLines = section.relations?.map((relation) => `<!-- c4a:relation ${renderAttrs([
19843
+ ["target", `${relation.target_slug}/${relation.target_section_id}`],
19844
+ ["relation", relation.relation]
19845
+ ])} -->`) ?? [];
19846
+ const head = `<!-- c4a:section ${attrs.join(" ")} -->`;
19826
19847
  return [head, ...relationLines].join(`
19827
19848
  `);
19828
19849
  }
19829
- function renderBlockquote(section) {
19830
- const lines = [`> ${section.content}`];
19831
- const detail = renderableSectionDetail(section.detail);
19832
- if (detail !== undefined) {
19833
- lines.push(">");
19834
- lines.push("> <details>");
19835
- lines.push("> <summary>Details</summary>");
19836
- lines.push(">");
19837
- for (const line of detail.split(`
19838
- `)) {
19839
- lines.push(`> ${line}`);
19840
- }
19841
- lines.push(">");
19842
- lines.push("> </details>");
19843
- }
19844
- return lines.join(`
19850
+ function renderSectionBody(section) {
19851
+ return [
19852
+ renderSectionComment(section),
19853
+ ...section.summary !== undefined ? ["<!-- c4a:summary -->", section.summary.trim(), "<!-- /c4a:summary -->", ""] : [],
19854
+ section.content.trim(),
19855
+ ...section.raw !== undefined ? ["", "<!-- c4a:raw -->", section.raw.trim(), "<!-- /c4a:raw -->"] : [],
19856
+ "<!-- /c4a:section -->"
19857
+ ].join(`
19845
19858
  `);
19846
19859
  }
19847
19860
  function groupSections(sections) {
@@ -19862,8 +19875,7 @@ function renderSectionGroups(sections, headingLevel) {
19862
19875
  const groupSections2 = grouped.get(title) ?? [];
19863
19876
  if (groupSections2.length === 0)
19864
19877
  continue;
19865
- const renderedSections = groupSections2.map((section) => [renderSectionComment(section), renderBlockquote(section)].join(`
19866
- `));
19878
+ const renderedSections = groupSections2.map(renderSectionBody);
19867
19879
  parts.push([`${hashes} ${title}`, "", renderedSections.join(`
19868
19880
 
19869
19881
  `)].join(`
@@ -19998,7 +20010,6 @@ var import_yaml10, SECTION_RENDER_ORDER_MAP;
19998
20010
  var init_nodeRenderer = __esm(() => {
19999
20011
  init_knowledge();
20000
20012
  init_nodeDisplayGroups();
20001
- init_sectionDetail();
20002
20013
  import_yaml10 = __toESM(require_dist(), 1);
20003
20014
  SECTION_RENDER_ORDER_MAP = new Map(SECTION_RENDER_ORDER.map((kind, index) => [kind, index]));
20004
20015
  });
@@ -21258,6 +21269,7 @@ function sectionHitFromNode(nodeSlug, nodeUpdated, section) {
21258
21269
  node_slug: nodeSlug,
21259
21270
  section_id: section.id,
21260
21271
  kind: section.kind,
21272
+ ...section.summary !== undefined ? { summary: section.summary } : {},
21261
21273
  content: section.content,
21262
21274
  source_ref: section.source_ref,
21263
21275
  updated: nodeUpdated,
@@ -21505,7 +21517,7 @@ function buildTermIndex(input) {
21505
21517
  addTextPostings(postings, [node2.slug, node2.title, node2.summary, ...node2.aliases, ...node2.tags].filter((value) => typeof value === "string").join(" "), { node_slug: node2.slug, field: "node" });
21506
21518
  }
21507
21519
  for (const section of input.sections) {
21508
- addTextPostings(postings, [section.content, section.detail, section.source_ref].filter((value) => typeof value === "string").join(" "), { node_slug: section.node_slug, section_id: section.section_id, field: "section" });
21520
+ addTextPostings(postings, [section.summary, section.content, section.detail, section.source_ref].filter((value) => typeof value === "string").join(" "), { node_slug: section.node_slug, section_id: section.section_id, field: "section" });
21509
21521
  }
21510
21522
  for (const source2 of input.sources) {
21511
21523
  addTextPostings(postings, [source2.source_id, source2.title, source2.origin, source2.latest_snapshot_file].filter((value) => typeof value === "string").join(" "), { node_slug: source2.source_id, field: "source" });
@@ -22137,6 +22149,8 @@ function semanticRecordSourceIds(decision, evidence) {
22137
22149
  function shouldRecordSemanticDecision(decision, evidence) {
22138
22150
  if (decision.action !== "omit")
22139
22151
  return true;
22152
+ if (decision.decided_by === "cli_reviewed_no_write")
22153
+ return true;
22140
22154
  return semanticRecordSourceIds(decision, evidence).some((sourceId) => sourceId.startsWith("note:"));
22141
22155
  }
22142
22156
  async function recordSemanticDecisions(input) {
@@ -22403,7 +22417,9 @@ function archiveRecordsFromNode(input) {
22403
22417
  node_title: input.parsed.node.title,
22404
22418
  section_id: section.id,
22405
22419
  kind: section.kind,
22420
+ ...section.summary !== undefined ? { summary: section.summary } : {},
22406
22421
  content: section.content,
22422
+ ...section.raw !== undefined ? { raw: section.raw } : {},
22407
22423
  ...section.detail !== undefined ? { detail: section.detail } : {},
22408
22424
  source_ref: section.source_ref,
22409
22425
  source_id: input.manifest.source_id,
@@ -22507,7 +22523,9 @@ async function buildRetrievalIndex(input) {
22507
22523
  node_title: node2.parsed.node.title,
22508
22524
  section_id: section.id,
22509
22525
  kind: section.kind,
22526
+ ...section.summary !== undefined ? { summary: section.summary } : {},
22510
22527
  content: section.content,
22528
+ ...section.raw !== undefined ? { raw: section.raw } : {},
22511
22529
  ...section.detail !== undefined ? { detail: section.detail } : {},
22512
22530
  source_ref: section.source_ref,
22513
22531
  ...sourceId !== undefined ? { source_id: sourceId } : {},
@@ -22951,6 +22969,112 @@ var init_workspaceCache = __esm(() => {
22951
22969
  init_inputSummary();
22952
22970
  });
22953
22971
 
22972
+ // src/workflow/currentWorkflowErrors.ts
22973
+ function stateScope(state) {
22974
+ return state.scope_id ?? state.workflow_id;
22975
+ }
22976
+ function legacyWorkflowError(result) {
22977
+ const hint = {
22978
+ code: "workflow-schema-legacy",
22979
+ severity: "warning",
22980
+ message: "Current workflow state uses an unsupported schema version.",
22981
+ next_action: "Run `context workflow status` to inspect it, then rebuild or abandon the legacy workflow before writing.",
22982
+ command: "context workflow abandon --current"
22983
+ };
22984
+ return new ContextError(ExitCode.WorkspaceStateError, "current workflow schema is legacy; rebuild or abandon before writing", {
22985
+ category: ErrorCategory.WorkspaceStateInvalid,
22986
+ path: result.path,
22987
+ schema_version: result.schema_version,
22988
+ agent_hints: [hint]
22989
+ });
22990
+ }
22991
+ function crossFamilyError(active, requested) {
22992
+ const abandonCommand = `context workflow abandon --workflow-id ${active.workflow_id}`;
22993
+ const hint = {
22994
+ code: "workflow-cross-family-rejected",
22995
+ severity: "error",
22996
+ message: `Active ${active.family} workflow is at stage ${active.stage}; cannot start ${requested}.`,
22997
+ next_action: "Inspect and continue the active workflow. Abandon it only when you intentionally want to discard that in-progress work; if you meant another repository, change to that workspace root first.",
22998
+ command: "context workflow status --format json",
22999
+ diagnostics: {
23000
+ discard_command: abandonCommand,
23001
+ requested_family: requested
23002
+ }
23003
+ };
23004
+ return new ContextError(ExitCode.WorkspaceStateError, `active ${active.family} workflow ${active.workflow_id} is at ${active.stage}; cannot run ${requested}`, {
23005
+ category: ErrorCategory.WorkspaceStateInvalid,
23006
+ workflow_id: active.workflow_id,
23007
+ family: active.family,
23008
+ stage: active.stage,
23009
+ requested_family: requested,
23010
+ agent_hints: [hint]
23011
+ });
23012
+ }
23013
+ function rawUpdateBlockedError(active) {
23014
+ const abandonCommand = `context workflow abandon --workflow-id ${active.workflow_id}`;
23015
+ const hint = {
23016
+ code: "workflow-cross-family-rejected",
23017
+ severity: "error",
23018
+ message: `Active ${active.family} workflow is at stage ${active.stage}; cannot change raw sources.`,
23019
+ next_action: "Inspect and continue the active workflow. Capture/raw updates must wait until it is finished; abandon only when you intentionally want to discard that workflow.",
23020
+ command: "context workflow status --format json",
23021
+ diagnostics: {
23022
+ discard_command: abandonCommand,
23023
+ requested_family: "capture"
23024
+ }
23025
+ };
23026
+ return new ContextError(ExitCode.WorkspaceStateError, `active ${active.family} workflow ${active.workflow_id} is at ${active.stage}; cannot capture or update raw sources`, {
23027
+ category: ErrorCategory.WorkspaceStateInvalid,
23028
+ workflow_id: active.workflow_id,
23029
+ family: active.family,
23030
+ stage: active.stage,
23031
+ requested_family: "capture",
23032
+ agent_hints: [hint]
23033
+ });
23034
+ }
23035
+ function activeNodeRunError(active, requestedNodeRun) {
23036
+ const hint = {
23037
+ code: "workflow-active-node-run-rejected",
23038
+ severity: "error",
23039
+ message: `Active compile workflow is locked on ${active.active_node_run ?? "unknown node run"} at stage ${active.stage}.`,
23040
+ next_action: "Finish, close, or abandon the active node run before reviewing or applying another node.",
23041
+ command: "context workflow status"
23042
+ };
23043
+ return new ContextError(ExitCode.WorkspaceStateError, `active compile workflow ${active.workflow_id} is locked on ${active.active_node_run}; cannot switch to ${requestedNodeRun}`, {
23044
+ category: ErrorCategory.WorkspaceStateInvalid,
23045
+ workflow_id: active.workflow_id,
23046
+ family: active.family,
23047
+ stage: active.stage,
23048
+ active_node_run: active.active_node_run,
23049
+ requested_node_run: requestedNodeRun,
23050
+ agent_hints: [hint]
23051
+ });
23052
+ }
23053
+ function scopeConflictError(active, requestedScope) {
23054
+ const activeScope = stateScope(active);
23055
+ const hint = {
23056
+ code: "workflow-scope-conflict",
23057
+ severity: "error",
23058
+ message: `Active ${active.family} workflow is scoped to ${activeScope}; cannot switch to ${requestedScope}.`,
23059
+ next_action: "Continue, close, or abandon the active workflow before starting another scope.",
23060
+ command: "context workflow status"
23061
+ };
23062
+ return new ContextError(ExitCode.WorkspaceStateError, `active ${active.family} workflow ${active.workflow_id} is scoped to ${activeScope}; cannot switch to ${requestedScope}`, {
23063
+ category: ErrorCategory.WorkspaceStateInvalid,
23064
+ workflow_id: active.workflow_id,
23065
+ family: active.family,
23066
+ stage: active.stage,
23067
+ scope_id: activeScope,
23068
+ requested_scope_id: requestedScope,
23069
+ agent_hints: [hint]
23070
+ });
23071
+ }
23072
+ var init_currentWorkflowErrors = __esm(() => {
23073
+ init_cliFeedback();
23074
+ init_errors();
23075
+ init_exitCode();
23076
+ });
23077
+
22954
23078
  // src/workflow/currentWorkflow.ts
22955
23079
  import { randomBytes } from "node:crypto";
22956
23080
  import { mkdir as mkdir10, readFile as readFile18, readdir as readdir6, rm as rm4, writeFile as writeFile7 } from "node:fs/promises";
@@ -23017,16 +23141,21 @@ function workflowNextAction(state) {
23017
23141
  function workflowSummaryLine(state) {
23018
23142
  if (!state)
23019
23143
  return false;
23020
- return `workflow: ${state.family} at ${state.stage} (scope: ${workflowScope(state)})`;
23144
+ const mode = state.execution_mode === "delegated" ? ", delegated" : "";
23145
+ return `workflow: ${state.family} at ${state.stage} (scope: ${workflowScope(state)}${mode})`;
23021
23146
  }
23022
23147
  function workflowMetadata(state) {
23023
- return {
23148
+ const metadata2 = {
23024
23149
  workflow_id: state.workflow_id,
23025
23150
  family: state.family,
23026
23151
  stage: state.stage,
23027
23152
  scope_id: workflowScope(state),
23028
23153
  next_action: workflowNextAction(state)
23029
23154
  };
23155
+ return state.execution_mode !== undefined ? { ...metadata2, execution_mode: state.execution_mode } : metadata2;
23156
+ }
23157
+ function isDelegatedWorkflow(state) {
23158
+ return state?.execution_mode === "delegated";
23030
23159
  }
23031
23160
  function isObject3(value) {
23032
23161
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -23053,6 +23182,13 @@ function parseWorkflowState(raw) {
23053
23182
  created_at: raw.created_at,
23054
23183
  updated_at: raw.updated_at,
23055
23184
  review_policy: raw.review_policy === "skipped" ? "skipped" : "required",
23185
+ ...raw.execution_mode === "delegated" || raw.execution_mode === "manual" ? { execution_mode: raw.execution_mode } : {},
23186
+ ...isObject3(raw.delegated_authority) && raw.delegated_authority.source === "user_conversation" && raw.delegated_authority.scope === "low_risk_review_decisions" ? {
23187
+ delegated_authority: {
23188
+ source: "user_conversation",
23189
+ scope: "low_risk_review_decisions"
23190
+ }
23191
+ } : {},
23056
23192
  ...typeof raw.scope_id === "string" ? { scope_id: raw.scope_id } : {},
23057
23193
  ...typeof raw.active_node_run === "string" ? { active_node_run: raw.active_node_run } : {}
23058
23194
  };
@@ -23083,102 +23219,6 @@ async function writeCurrentWorkflow(ctxDir, state) {
23083
23219
  await mkdir10(dirname9(snapshotPath), { recursive: true });
23084
23220
  await writeFile7(snapshotPath, import_yaml15.default.stringify(state), "utf8");
23085
23221
  }
23086
- function legacyWorkflowError(result) {
23087
- const hint = {
23088
- code: "workflow-schema-legacy",
23089
- severity: "warning",
23090
- message: "Current workflow state uses an unsupported schema version.",
23091
- next_action: "Run `context workflow status` to inspect it, then rebuild or abandon the legacy workflow before writing.",
23092
- command: "context workflow abandon --current"
23093
- };
23094
- return new ContextError(ExitCode.WorkspaceStateError, "current workflow schema is legacy; rebuild or abandon before writing", {
23095
- category: ErrorCategory.WorkspaceStateInvalid,
23096
- path: result.path,
23097
- schema_version: result.schema_version,
23098
- agent_hints: [hint]
23099
- });
23100
- }
23101
- function crossFamilyError(active, requested) {
23102
- const abandonCommand = `context workflow abandon --workflow-id ${active.workflow_id}`;
23103
- const hint = {
23104
- code: "workflow-cross-family-rejected",
23105
- severity: "error",
23106
- message: `Active ${active.family} workflow is at stage ${active.stage}; cannot start ${requested}.`,
23107
- next_action: "Inspect and continue the active workflow. Abandon it only when you intentionally want to discard that in-progress work; if you meant another repository, change to that workspace root first.",
23108
- command: "context workflow status --format json",
23109
- diagnostics: {
23110
- discard_command: abandonCommand,
23111
- requested_family: requested
23112
- }
23113
- };
23114
- return new ContextError(ExitCode.WorkspaceStateError, `active ${active.family} workflow ${active.workflow_id} is at ${active.stage}; cannot run ${requested}`, {
23115
- category: ErrorCategory.WorkspaceStateInvalid,
23116
- workflow_id: active.workflow_id,
23117
- family: active.family,
23118
- stage: active.stage,
23119
- requested_family: requested,
23120
- agent_hints: [hint]
23121
- });
23122
- }
23123
- function rawUpdateBlockedError(active) {
23124
- const abandonCommand = `context workflow abandon --workflow-id ${active.workflow_id}`;
23125
- const hint = {
23126
- code: "workflow-cross-family-rejected",
23127
- severity: "error",
23128
- message: `Active ${active.family} workflow is at stage ${active.stage}; cannot change raw sources.`,
23129
- next_action: "Inspect and continue the active workflow. Capture/raw updates must wait until it is finished; abandon only when you intentionally want to discard that workflow.",
23130
- command: "context workflow status --format json",
23131
- diagnostics: {
23132
- discard_command: abandonCommand,
23133
- requested_family: "capture"
23134
- }
23135
- };
23136
- return new ContextError(ExitCode.WorkspaceStateError, `active ${active.family} workflow ${active.workflow_id} is at ${active.stage}; cannot capture or update raw sources`, {
23137
- category: ErrorCategory.WorkspaceStateInvalid,
23138
- workflow_id: active.workflow_id,
23139
- family: active.family,
23140
- stage: active.stage,
23141
- requested_family: "capture",
23142
- agent_hints: [hint]
23143
- });
23144
- }
23145
- function activeNodeRunError(active, requestedNodeRun) {
23146
- const hint = {
23147
- code: "workflow-active-node-run-rejected",
23148
- severity: "error",
23149
- message: `Active compile workflow is locked on ${active.active_node_run ?? "unknown node run"} at stage ${active.stage}.`,
23150
- next_action: "Finish, close, or abandon the active node run before reviewing or applying another node.",
23151
- command: "context workflow status"
23152
- };
23153
- return new ContextError(ExitCode.WorkspaceStateError, `active compile workflow ${active.workflow_id} is locked on ${active.active_node_run}; cannot switch to ${requestedNodeRun}`, {
23154
- category: ErrorCategory.WorkspaceStateInvalid,
23155
- workflow_id: active.workflow_id,
23156
- family: active.family,
23157
- stage: active.stage,
23158
- active_node_run: active.active_node_run,
23159
- requested_node_run: requestedNodeRun,
23160
- agent_hints: [hint]
23161
- });
23162
- }
23163
- function scopeConflictError(active, requestedScope) {
23164
- const activeScope = workflowScope(active);
23165
- const hint = {
23166
- code: "workflow-scope-conflict",
23167
- severity: "error",
23168
- message: `Active ${active.family} workflow is scoped to ${activeScope}; cannot switch to ${requestedScope}.`,
23169
- next_action: "Continue, close, or abandon the active workflow before starting another scope.",
23170
- command: "context workflow status"
23171
- };
23172
- return new ContextError(ExitCode.WorkspaceStateError, `active ${active.family} workflow ${active.workflow_id} is scoped to ${activeScope}; cannot switch to ${requestedScope}`, {
23173
- category: ErrorCategory.WorkspaceStateInvalid,
23174
- workflow_id: active.workflow_id,
23175
- family: active.family,
23176
- stage: active.stage,
23177
- scope_id: activeScope,
23178
- requested_scope_id: requestedScope,
23179
- agent_hints: [hint]
23180
- });
23181
- }
23182
23222
  function createState(input, now) {
23183
23223
  const workflowId = createWorkflowId(input.family, now);
23184
23224
  const timestamp = now.toISOString();
@@ -23191,12 +23231,14 @@ function createState(input, now) {
23191
23231
  created_at: timestamp,
23192
23232
  updated_at: timestamp,
23193
23233
  review_policy: input.reviewPolicy ?? "required",
23234
+ ...input.executionMode !== undefined ? { execution_mode: input.executionMode } : {},
23235
+ ...input.delegatedAuthority !== undefined ? { delegated_authority: input.delegatedAuthority } : {},
23194
23236
  ...input.scopeId !== undefined ? { scope_id: input.scopeId } : {},
23195
23237
  ...input.activeNodeRun !== undefined ? { active_node_run: input.activeNodeRun } : {}
23196
23238
  };
23197
23239
  }
23198
23240
  function updateState(state, input, now) {
23199
- return {
23241
+ const next = {
23200
23242
  ...state,
23201
23243
  stage: input.stage ?? state.stage,
23202
23244
  input_digests: { ...state.input_digests, ...input.inputDigests ?? {} },
@@ -23205,11 +23247,32 @@ function updateState(state, input, now) {
23205
23247
  ...input.scopeId !== undefined ? { scope_id: input.scopeId } : {},
23206
23248
  ...input.activeNodeRun !== undefined ? { active_node_run: input.activeNodeRun } : {}
23207
23249
  };
23250
+ if (input.executionMode !== undefined)
23251
+ next.execution_mode = input.executionMode;
23252
+ if (input.delegatedAuthority !== undefined)
23253
+ next.delegated_authority = input.delegatedAuthority;
23254
+ return next;
23208
23255
  }
23209
23256
  async function ensureCurrentWorkflow(input) {
23210
23257
  const now = input.now ?? new Date;
23211
23258
  const current = await assertWorkflowWritable(input);
23212
23259
  if (current.status === "ready" && !isTerminalWorkflowStage(current.state.stage)) {
23260
+ if (input.executionMode === "delegated" && current.state.execution_mode !== "delegated") {
23261
+ throw new ContextError(ExitCode.WorkspaceStateError, "cannot enable delegated mode after workflow creation", {
23262
+ category: ErrorCategory.WorkspaceStateInvalid,
23263
+ workflow_id: current.state.workflow_id,
23264
+ family: current.state.family,
23265
+ stage: current.state.stage,
23266
+ requested_execution_mode: "delegated",
23267
+ agent_hints: [{
23268
+ code: "workflow-delegated-mode-creation-only",
23269
+ severity: "error",
23270
+ message: "Delegated mode is a workflow creation-time authorization and cannot be enabled mid-workflow.",
23271
+ next_action: "Finish or abandon the current workflow. Start a new compile workflow with --delegated only if the user explicitly authorized delegated mode at the start of the conversation.",
23272
+ command: "context workflow status --format json"
23273
+ }]
23274
+ });
23275
+ }
23213
23276
  const updated = updateState(current.state, input, now);
23214
23277
  await writeCurrentWorkflow(input.ctxDir, updated);
23215
23278
  return updated;
@@ -23358,6 +23421,7 @@ var init_currentWorkflow = __esm(() => {
23358
23421
  init_errors();
23359
23422
  init_cliFeedback();
23360
23423
  init_exitCode();
23424
+ init_currentWorkflowErrors();
23361
23425
  import_yaml15 = __toESM(require_dist(), 1);
23362
23426
  TERMINAL_STAGES = new Set(["finalized", "closed", "abandoned"]);
23363
23427
  ACTIVE_NODE_LOCK_STAGES = new Set(["node_prepared", "node_review_ready"]);
@@ -27622,12 +27686,12 @@ function workspaceGenerationPolicy(config) {
27622
27686
  language,
27623
27687
  source: source2,
27624
27688
  applies_to: APPLIES_TO,
27625
- instruction: `Generate knowledge titles, summaries, and user-facing reports in ${language}; keep node.summary concise (target <15 tokens, never >30 tokens); for source-bound Section content/detail, prefer the cited source language when it differs from ${language}; preserve product names, code identifiers, CLI flags, block_id/source_ref tokens, slugs, and quoted evidence exactly when needed.`
27689
+ instruction: `Generate knowledge titles, node summaries, Section summaries, and user-facing reports in ${language}; keep node.summary concise (target <15 tokens, never >30 tokens); for source-bound Section content, prefer the cited source language when it differs from ${language}; preserve product names, code identifiers, CLI flags, block_id/source_ref tokens, slugs, and quoted evidence exactly when needed.`
27626
27690
  };
27627
27691
  }
27628
27692
  var APPLIES_TO;
27629
27693
  var init_generationPolicy = __esm(() => {
27630
- APPLIES_TO = ["node.title", "node.summary", "section.content", "section.detail", "user_facing_report"];
27694
+ APPLIES_TO = ["node.title", "node.summary", "section.summary", "section.content", "user_facing_report"];
27631
27695
  });
27632
27696
 
27633
27697
  // src/mdrive/verifyLanguage.ts
@@ -27724,9 +27788,6 @@ function findUnclosedFence(value) {
27724
27788
  }
27725
27789
  return fence ? { marker: fence.marker.repeat(fence.length), line: fence.line } : null;
27726
27790
  }
27727
- function contentStartsWithFence(value) {
27728
- return openingFence(value) !== null;
27729
- }
27730
27791
  function pushNodeIssues(issues, file, node2, options = { experimentalChecks: false }) {
27731
27792
  for (const issue of validateKnowledgeNode(node2.node)) {
27732
27793
  issues.push({
@@ -27769,11 +27830,12 @@ function pushNodeIssues(issues, file, node2, options = { experimentalChecks: fal
27769
27830
  sectionId: section.id
27770
27831
  });
27771
27832
  }
27772
- if (contentStartsWithFence(section.content)) {
27833
+ const contentFence = findUnclosedFence(section.content);
27834
+ if (contentFence) {
27773
27835
  issues.push({
27774
27836
  severity: "error",
27775
- code: "section-content-code-fence",
27776
- message: "section content must be a short prose claim, not a fenced code block; put code blocks in section detail",
27837
+ code: "section-content-unclosed-fence",
27838
+ message: `section content opens fenced code block ${contentFence.marker} at content line ${contentFence.line} but does not close it`,
27777
27839
  path: file.relativePath,
27778
27840
  slug: node2.node.id,
27779
27841
  sectionId: section.id
@@ -28829,6 +28891,26 @@ var init_verify = __esm(() => {
28829
28891
  init_verifyWorkspaceReader();
28830
28892
  });
28831
28893
 
28894
+ // src/lib/sectionDetail.ts
28895
+ function fencedBlocksIn(value) {
28896
+ return [...value.matchAll(FENCED_DETAIL_BLOCK_RE)].map((match) => normalizeMarkdown(match[0]).trim()).filter((block) => block.length > 0);
28897
+ }
28898
+ function basisText(value) {
28899
+ return typeof value === "string" ? value : value.join(`
28900
+ `);
28901
+ }
28902
+ function hasCitedFencedDetail(detail, basisTexts) {
28903
+ const cited = normalizeMarkdown(basisText(basisTexts)).trim();
28904
+ if (cited.length === 0)
28905
+ return false;
28906
+ return fencedBlocksIn(detail).some((block) => cited.includes(block));
28907
+ }
28908
+ var FENCED_DETAIL_BLOCK_RE;
28909
+ var init_sectionDetail = __esm(() => {
28910
+ init_normalize();
28911
+ FENCED_DETAIL_BLOCK_RE = /```[^\n`]*\n[\s\S]*?```|~~~[^\n~]*\n[\s\S]*?~~~/gu;
28912
+ });
28913
+
28832
28914
  // src/reconcile/sourceSupport.ts
28833
28915
  function numbersIn(text) {
28834
28916
  return text.match(/\b\d+(?:\.\d+)*\b/gu) ?? [];
@@ -28897,7 +28979,8 @@ function hasUrlReferenceLabel(text) {
28897
28979
  return regexMatches(EN_URL_REFERENCE_LABEL_RE, text) || regexMatches(CJK_URL_REFERENCE_LABEL_RE, text);
28898
28980
  }
28899
28981
  function diagnoseUrlReferencePreservation(input) {
28900
- const proposedText = `${input.content}
28982
+ const proposedText = `${input.summary ?? ""}
28983
+ ${input.content}
28901
28984
  ${input.detail ?? ""}`;
28902
28985
  const citedUrls = evidenceUrlsIn(input.citedText);
28903
28986
  const proposedUrls = evidenceUrlsIn(proposedText);
@@ -29044,14 +29127,17 @@ function sourceTextSupportDiagnostic(content, citedText, thresholds = DEFAULT_TH
29044
29127
  };
29045
29128
  }
29046
29129
  function sourceSectionSupportDiagnostic(input) {
29130
+ const summary = typeof input.summary === "string" ? input.summary.trim() : "";
29047
29131
  const detail = typeof input.detail === "string" ? input.detail.trim() : "";
29132
+ const content = summary.length > 0 ? `${summary}
29133
+ ${input.content}` : input.content;
29048
29134
  if (detail.length === 0) {
29049
- return sourceTextSupportDiagnostic(input.content, input.citedText, thresholdsForKind(input.kind));
29135
+ return sourceTextSupportDiagnostic(content, input.citedText, thresholdsForKind(input.kind));
29050
29136
  }
29051
29137
  const thresholds = thresholdsForKind(input.kind);
29052
- const contentDiagnostic = sourceTextSupportDiagnostic(input.content, input.citedText, thresholds);
29138
+ const contentDiagnostic = sourceTextSupportDiagnostic(content, input.citedText, thresholds);
29053
29139
  const detailDiagnostic = sourceTextSupportDiagnostic(detail, input.citedText, thresholds);
29054
- const combined = sourceTextSupportDiagnostic(`${input.content}
29140
+ const combined = sourceTextSupportDiagnostic(`${content}
29055
29141
  ${detail}`, input.citedText, thresholds);
29056
29142
  const detailAware = {
29057
29143
  ...combined,
@@ -29140,6 +29226,7 @@ async function findAutoNarrowedSourceRefs(input) {
29140
29226
  continue;
29141
29227
  const diagnostic = sourceSectionSupportDiagnostic({
29142
29228
  kind: input.section.kind,
29229
+ summary: input.section.summary,
29143
29230
  content: input.section.content,
29144
29231
  detail: input.section.detail,
29145
29232
  citedText: resolved.cited_text
@@ -29192,6 +29279,7 @@ async function rankEvidenceBlockCandidates(input) {
29192
29279
  const citedText = sourceRefRangeText(input.raw, block.line_start, block.line_end);
29193
29280
  const diagnostic = sourceSectionSupportDiagnostic({
29194
29281
  kind: input.section.kind,
29282
+ summary: input.section.summary,
29195
29283
  content: input.section.content,
29196
29284
  detail: input.section.detail,
29197
29285
  citedText
@@ -29241,6 +29329,7 @@ async function diagnoseSectionSourceSupport(input) {
29241
29329
  };
29242
29330
  const diagnostic = sourceSectionSupportDiagnostic({
29243
29331
  kind: input.section.kind,
29332
+ summary: input.section.summary,
29244
29333
  content: input.section.content,
29245
29334
  detail: input.section.detail,
29246
29335
  citedText
@@ -29265,12 +29354,12 @@ async function diagnoseSectionSourceSupport(input) {
29265
29354
  }
29266
29355
  async function validateSectionSourceSupport(input) {
29267
29356
  const { sourceRef, diagnostic } = await diagnoseSectionSourceSupport(input);
29268
- input.section.source_ref = sourceRef;
29269
29357
  const strictFailure = input.strict === true && !isStrictlySupported(diagnostic);
29270
29358
  if (diagnostic.verdict === "unsupported" || diagnostic.verdict === "weak" && input.allowWeak !== true || strictFailure) {
29271
29359
  const weakGuidance = weakSourceSupportGuidance(input.action, diagnostic.verdict);
29272
29360
  throw new Error(`${input.action} final section content is not supported by a single source_ref "${input.section.source_ref}": ` + `${formatSupportDiagnostic(diagnostic)}; choose source_ref/source_refs copied from prepared evidence that cover the exact supporting text, ` + `or use context source resolve-ref --node <slug> --text "<quote>" only when a short exact quote can locate the evidence, ` + `or choose a semantic action that matches the evidence boundary: split_then_reanchor for separable supported parts, ` + `keep_separate for an independently supported claim, or ask_user when human confirmation is required.${weakGuidance}`);
29273
29361
  }
29362
+ return { sourceRef, diagnostic };
29274
29363
  }
29275
29364
  function weakSourceSupportGuidance(action, verdict) {
29276
29365
  if (verdict !== "weak")
@@ -32660,7 +32749,9 @@ function candidateFromSection(section, reason, score) {
32660
32749
  kind: section.kind,
32661
32750
  score,
32662
32751
  reasons: [reason],
32752
+ ...section.summary !== undefined ? { summary: section.summary } : {},
32663
32753
  content: section.content,
32754
+ ...section.raw !== undefined ? { raw: section.raw } : {},
32664
32755
  ...section.detail !== undefined ? { detail: section.detail } : {},
32665
32756
  confidence: section.confidence,
32666
32757
  source_ref: section.source_ref,
@@ -32895,15 +32986,17 @@ function nearDuplicateCandidates(sections, text) {
32895
32986
  return [];
32896
32987
  const out2 = [];
32897
32988
  for (const section of sections) {
32898
- const similarity = duplicateSimilarity(text, `${section.content}
32899
- ${section.detail ?? ""}`);
32989
+ const activeText = `${section.summary ?? ""}
32990
+ ${section.content}
32991
+ ${section.detail ?? ""}`;
32992
+ const similarity = duplicateSimilarity(text, activeText);
32900
32993
  if (similarity < 0.45)
32901
32994
  continue;
32902
32995
  out2.push(candidateFromSection(section, {
32903
32996
  type: "near-duplicate",
32904
32997
  reason: "shingle_jaccard",
32905
32998
  score: Number(similarity.toFixed(6)),
32906
- terms: overlappingTerms(text, section.content).slice(0, 12)
32999
+ terms: overlappingTerms(text, activeText).slice(0, 12)
32907
33000
  }, Number(similarity.toFixed(6))));
32908
33001
  }
32909
33002
  return out2;
@@ -32963,6 +33056,7 @@ function archiveCandidates(input) {
32963
33056
  record.source_id,
32964
33057
  record.source_ref,
32965
33058
  record.archive_path,
33059
+ record.summary,
32966
33060
  record.content,
32967
33061
  record.detail
32968
33062
  ].filter((value) => typeof value === "string" && value.length > 0).join(`
@@ -33114,13 +33208,16 @@ function urlsIn(text) {
33114
33208
  }
33115
33209
  function proposedTextForAction(action) {
33116
33210
  if (action.op === "add")
33117
- return `${action.content}
33211
+ return `${action.summary ?? ""}
33212
+ ${action.content}
33118
33213
  ${action.detail ?? ""}`;
33119
33214
  if (action.op === "update")
33120
- return `${action.content ?? ""}
33215
+ return `${action.summary ?? ""}
33216
+ ${action.content ?? ""}
33121
33217
  ${action.detail ?? ""}`;
33122
33218
  if (action.op === "supersede")
33123
- return `${action.new.content}
33219
+ return `${action.new.summary ?? ""}
33220
+ ${action.new.content}
33124
33221
  ${action.new.detail ?? ""}`;
33125
33222
  return "";
33126
33223
  }
@@ -33483,7 +33580,9 @@ function createSection(nodeSlug, input, sectionId, nodeType) {
33483
33580
  id: sectionId,
33484
33581
  anchor_slug: nodeSlug,
33485
33582
  kind: input.kind,
33583
+ ...input.summary !== undefined ? { summary: input.summary } : {},
33486
33584
  content: input.content,
33585
+ ...input.raw !== undefined ? { raw: input.raw } : {},
33487
33586
  source_ref: input.source_ref,
33488
33587
  confidence: input.confidence ?? Confidence.confirmed,
33489
33588
  status: input.status ?? SectionStatus.active,
@@ -33504,12 +33603,12 @@ function reassignSections(node2, sections) {
33504
33603
  node2.sections = sortSectionsByPriority(sections);
33505
33604
  }
33506
33605
  function sectionText(section) {
33507
- return [section.content, section.detail].filter((value) => typeof value === "string").join(`
33606
+ return [section.summary, section.content, section.detail].filter((value) => typeof value === "string").join(`
33508
33607
  `);
33509
33608
  }
33510
33609
  async function withInferredRefersToNodes(ctxDir, nodeSlug, input) {
33511
33610
  const inferred = inferRefersToNodes({
33512
- text: [input.content, input.detail].filter((value) => typeof value === "string").join(`
33611
+ text: [input.summary, input.content, input.detail].filter((value) => typeof value === "string").join(`
33513
33612
  `),
33514
33613
  currentNodeSlug: nodeSlug,
33515
33614
  knownNodes: await collectKnownRefersToNodes(ctxDir),
@@ -33531,6 +33630,13 @@ async function mdriveSectionUpdate(input) {
33531
33630
  }
33532
33631
  const located = await locateNode(input.ctxDir, input.nodeSlug);
33533
33632
  const section = findSection(input.nodeSlug, located.parsed, input.sectionId);
33633
+ if (input.patch.summary !== undefined) {
33634
+ if (input.patch.summary === null) {
33635
+ delete section.summary;
33636
+ } else {
33637
+ section.summary = input.patch.summary;
33638
+ }
33639
+ }
33534
33640
  if (input.patch.detail !== undefined) {
33535
33641
  if (input.patch.detail === null) {
33536
33642
  delete section.detail;
@@ -33541,6 +33647,13 @@ async function mdriveSectionUpdate(input) {
33541
33647
  if (input.patch.content !== undefined) {
33542
33648
  section.content = input.patch.content;
33543
33649
  }
33650
+ if (input.patch.raw !== undefined) {
33651
+ if (input.patch.raw === null) {
33652
+ delete section.raw;
33653
+ } else {
33654
+ section.raw = input.patch.raw;
33655
+ }
33656
+ }
33544
33657
  if (input.patch.confidence !== undefined) {
33545
33658
  section.confidence = input.patch.confidence;
33546
33659
  }
@@ -33728,6 +33841,9 @@ var init_section = __esm(() => {
33728
33841
  });
33729
33842
 
33730
33843
  // src/workflow/compileNode.ts
33844
+ function uniqueSorted2(values) {
33845
+ return [...new Set(values.filter((value) => value.length > 0).map(sourceIdWithoutVersion))].sort((left, right) => left.localeCompare(right));
33846
+ }
33731
33847
  function compileContextNodeInput(context) {
33732
33848
  return {
33733
33849
  node: {
@@ -33751,6 +33867,21 @@ function compileContextNodeInput(context) {
33751
33867
  children: []
33752
33868
  };
33753
33869
  }
33870
+ function compileContextNoWriteNodeInput(context) {
33871
+ const input = compileContextNodeInput(context);
33872
+ const contextSources = uniqueSorted2([
33873
+ ...context.node.context_sources ?? [],
33874
+ ...context.node.sources ?? []
33875
+ ]);
33876
+ return {
33877
+ ...input,
33878
+ node: {
33879
+ ...input.node,
33880
+ sources: [],
33881
+ ...contextSources.length > 0 ? { context_sources: contextSources } : {}
33882
+ }
33883
+ };
33884
+ }
33754
33885
  function compileContextLocatedNode(ctxDir, context) {
33755
33886
  const input = compileContextNodeInput(context);
33756
33887
  const parsed = parseNodeMarkdown(renderNodeMarkdown(input));
@@ -33772,9 +33903,18 @@ async function ensureCompileNodeExists(ctxDir, context) {
33772
33903
  input: compileContextNodeInput(context)
33773
33904
  });
33774
33905
  }
33906
+ async function ensureCompileNoWriteNodeExists(ctxDir, context) {
33907
+ if (context.existing)
33908
+ return;
33909
+ await mdriveNodeCreate({
33910
+ ctxDir,
33911
+ input: compileContextNoWriteNodeInput(context)
33912
+ });
33913
+ }
33775
33914
  var init_compileNode = __esm(() => {
33776
33915
  init_nodeParser();
33777
33916
  init_nodeRenderer();
33917
+ init_sources();
33778
33918
  init_node2();
33779
33919
  init_shared();
33780
33920
  });
@@ -34849,6 +34989,38 @@ function assertCitationEligibleSourceRefs(sourceRefs, slug, context, label) {
34849
34989
  });
34850
34990
  }
34851
34991
  }
34992
+ function lineRangeFromSourceRef(sourceRef) {
34993
+ const match = /\sL(\d+)-L?(\d+)@/u.exec(sourceRef);
34994
+ if (!match?.[1] || !match[2])
34995
+ return null;
34996
+ return { start: Number(match[1]), end: Number(match[2]) };
34997
+ }
34998
+ function lineDistance(left, right) {
34999
+ if (left.end < right.start)
35000
+ return right.start - left.end;
35001
+ if (right.end < left.start)
35002
+ return left.start - right.end;
35003
+ return 0;
35004
+ }
35005
+ function nearbyCitationEligibleSourceRefs(input) {
35006
+ const unresolvedRanges = input.unresolvedSourceRefs.map(lineRangeFromSourceRef).filter((range) => range !== null);
35007
+ if (unresolvedRanges.length === 0)
35008
+ return [];
35009
+ const refs = input.context.raw_snippets.filter((snippet) => typeof snippet.source_ref === "string" && snippet.source_ref.length > 0 && snippet.citation_eligible !== false && snippet.context_only !== true).map((snippet) => snippet.source_ref).filter((sourceRef, index, all) => all.indexOf(sourceRef) === index).map((sourceRef) => ({ sourceRef, range: lineRangeFromSourceRef(sourceRef) })).filter((item) => item.range !== null).map((item) => ({
35010
+ ...item,
35011
+ distance: Math.min(...unresolvedRanges.map((range) => lineDistance(range, item.range)))
35012
+ })).filter((item) => item.distance <= 3).sort((left, right) => left.distance - right.distance || left.range.start - right.range.start);
35013
+ return refs.slice(0, 8).map((item) => item.sourceRef);
35014
+ }
35015
+ function normalizedForRawCompare(value) {
35016
+ return normalizeMarkdown(value).trim();
35017
+ }
35018
+ function rawDebugForContent(content, citedText) {
35019
+ const raw = citedText.trim();
35020
+ if (raw.length === 0)
35021
+ return;
35022
+ return normalizedForRawCompare(content) === normalizedForRawCompare(raw) ? undefined : raw;
35023
+ }
34852
35024
  async function assertResolvedSourceRefs(ctxDir, sourceRefs, slug, context, label = "source_refs") {
34853
35025
  assertCitationEligibleSourceRefs(sourceRefs, slug, context, label);
34854
35026
  const resolved = await resolveSourceRefsToSourceRef({
@@ -34863,10 +35035,16 @@ async function assertResolvedSourceRefs(ctxDir, sourceRefs, slug, context, label
34863
35035
  nodeSources: context.node.sources ?? []
34864
35036
  });
34865
35037
  if (diagnostics.reason === "unresolved-source-ref") {
34866
- throw draftError(slug, context, `${label} contains a source_ref that does not resolve to a known evidence block hash`, {
35038
+ const unresolvedSourceRefs = "unresolved_source_refs" in diagnostics && Array.isArray(diagnostics.unresolved_source_refs) ? diagnostics.unresolved_source_refs.filter((ref) => typeof ref === "string") : sourceRefs;
35039
+ const nearby = nearbyCitationEligibleSourceRefs({ unresolvedSourceRefs, context });
35040
+ throw draftError(slug, context, `${label} contains a source_ref that does not match a citation-eligible evidence block. Copy the exact source_ref from the source-refs view; if the intended lines span multiple blocks, use the listed block source_refs or split the action.`, {
34867
35041
  path: label,
34868
35042
  reasonCode: "source-ref-unknown-hash",
34869
- diagnostics
35043
+ diagnostics: {
35044
+ ...diagnostics,
35045
+ reason_hint: "The requested line range/hash is not one of this Node's citation-eligible evidence blocks; it may cross block boundaries, include context-only/ignored material, or use a stale hash.",
35046
+ ...nearby.length > 0 ? { nearby_citation_eligible_source_refs: nearby } : {}
35047
+ }
34870
35048
  });
34871
35049
  }
34872
35050
  throw draftError(slug, context, `${label} could not be resolved to one contiguous source_ref; split the action or include every intervening citation-eligible source_ref`, {
@@ -34878,15 +35056,28 @@ async function assertResolvedSourceRefs(ctxDir, sourceRefs, slug, context, label
34878
35056
  }
34879
35057
  async function resolveSectionInput(ctxDir, slug, context, action, options = {}) {
34880
35058
  const sourceRef = await resolveActionSourceRef(ctxDir, slug, context, action, options);
35059
+ const raw = await resolveActionRawDebug(ctxDir, context, action, sourceRef);
34881
35060
  return {
34882
35061
  kind: action.kind,
35062
+ ...action.summary !== undefined ? { summary: action.summary } : {},
34883
35063
  content: action.content,
35064
+ ...raw !== undefined ? { raw } : {},
34884
35065
  source_ref: sourceRef,
34885
35066
  confidence: action.confidence ?? Confidence.confirmed,
34886
35067
  ...typeof action.detail === "string" ? { detail: action.detail } : {},
34887
35068
  ...action.refers_to_nodes !== undefined ? { refers_to_nodes: action.refers_to_nodes } : {}
34888
35069
  };
34889
35070
  }
35071
+ async function resolveActionRawDebug(ctxDir, context, action, sourceRef) {
35072
+ if (typeof action.raw === "string" && action.raw.trim().length > 0)
35073
+ return action.raw.trim();
35074
+ const resolved = await resolveSourceRefsToEvidence({
35075
+ ctxDir,
35076
+ sourceRefs: [sourceRef],
35077
+ nodeSources: context.node.sources ?? []
35078
+ });
35079
+ return resolved === null ? undefined : rawDebugForContent(action.content, resolved.cited_text);
35080
+ }
34890
35081
  async function resolveActionSourceRef(ctxDir, slug, context, action, options = {}) {
34891
35082
  if (action.source_refs !== undefined) {
34892
35083
  assertCitationEligibleSourceRefs(action.source_refs, slug, context, "source_refs");
@@ -34898,6 +35089,7 @@ async function resolveActionSourceRef(ctxDir, slug, context, action, options = {
34898
35089
  section: {
34899
35090
  content: action.content,
34900
35091
  ...action.kind !== undefined ? { kind: action.kind } : {},
35092
+ ...action.summary !== undefined ? { summary: action.summary } : {},
34901
35093
  ...action.detail !== undefined ? { detail: action.detail } : {}
34902
35094
  }
34903
35095
  });
@@ -34930,6 +35122,8 @@ async function resolveUpdatePatch(ctxDir, slug, context, action) {
34930
35122
  const patch = {};
34931
35123
  if (action.content !== undefined)
34932
35124
  patch.content = action.content;
35125
+ if (action.summary !== undefined)
35126
+ patch.summary = action.summary;
34933
35127
  if (action.detail !== undefined)
34934
35128
  patch.detail = action.detail;
34935
35129
  if (action.confidence !== undefined)
@@ -34945,6 +35139,12 @@ async function resolveUpdatePatch(ctxDir, slug, context, action) {
34945
35139
  });
34946
35140
  if (resolved !== null)
34947
35141
  patch.source_ref = resolved;
35142
+ if (action.content !== undefined && resolved !== null) {
35143
+ const raw = await resolveActionRawDebug(ctxDir, context, {
35144
+ content: action.content
35145
+ }, resolved);
35146
+ patch.raw = raw ?? null;
35147
+ }
34948
35148
  }
34949
35149
  return patch;
34950
35150
  }
@@ -34958,7 +35158,9 @@ function mergeUpdateCandidate(context, sectionId, patch) {
34958
35158
  }
34959
35159
  return {
34960
35160
  kind: current.kind,
35161
+ ...patch.summary !== undefined ? patch.summary === null ? {} : { summary: patch.summary } : current.summary !== undefined ? { summary: current.summary } : {},
34961
35162
  content: patch.content ?? current.content,
35163
+ ...patch.raw !== undefined ? patch.raw === null ? {} : { raw: patch.raw } : current.raw !== undefined ? { raw: current.raw } : {},
34962
35164
  source_ref: patch.source_ref ?? current.source_ref,
34963
35165
  confidence: patch.confidence ?? current.confidence,
34964
35166
  ...patch.detail !== undefined ? patch.detail === null ? {} : { detail: patch.detail } : current.detail !== undefined ? { detail: current.detail } : {},
@@ -34970,6 +35172,7 @@ var init_compileDraftSourceRefs = __esm(() => {
34970
35172
  init_cliFeedback();
34971
35173
  init_sourceRef();
34972
35174
  init_ref();
35175
+ init_normalize();
34973
35176
  init_exitCode();
34974
35177
  init_knowledge();
34975
35178
  init_compileErrors();
@@ -34980,13 +35183,13 @@ var init_compileDraftSourceRefs = __esm(() => {
34980
35183
  // src/workflow/compileDraftReferenceHints.ts
34981
35184
  function actionText(action) {
34982
35185
  if (action.op === "add")
34983
- return [action.content, action.detail].filter(Boolean).join(`
35186
+ return [action.summary, action.content, action.detail].filter(Boolean).join(`
34984
35187
  `);
34985
35188
  if (action.op === "update")
34986
- return [action.content, action.detail].filter(Boolean).join(`
35189
+ return [action.summary, action.content, action.detail].filter(Boolean).join(`
34987
35190
  `);
34988
35191
  if (action.op === "supersede")
34989
- return [action.new.content, action.new.detail].filter(Boolean).join(`
35192
+ return [action.new.summary, action.new.content, action.new.detail].filter(Boolean).join(`
34990
35193
  `);
34991
35194
  return "";
34992
35195
  }
@@ -35406,33 +35609,103 @@ function assertDraftContent(value, slug, context, label, optional = false) {
35406
35609
  if (value === undefined && optional)
35407
35610
  return;
35408
35611
  assertDraftString(value, slug, context, label);
35409
- if (value.length > MAX_DRAFT_CONTENT_CHARS) {
35410
- throw draftError(slug, context, `${label} must be ${MAX_DRAFT_CONTENT_CHARS} characters or fewer; current length is ${value.length} (over by ${value.length - MAX_DRAFT_CONTENT_CHARS}); move long-form prose into detail`, {
35411
- path: label,
35412
- reasonCode: "content-too-long",
35612
+ }
35613
+ function assertOptionalDraftString(value, slug, context, label) {
35614
+ if (value !== undefined && typeof value !== "string") {
35615
+ throw draftError(slug, context, `${label} must be a string when present`, { path: label, reasonCode: "invalid-string" });
35616
+ }
35617
+ }
35618
+ function summaryTargetLength(content) {
35619
+ return Math.min(SUMMARY_TARGET_MAX_CHARS, Math.max(SUMMARY_TARGET_MIN_CHARS, Math.round(content.length / 10)));
35620
+ }
35621
+ function pushSummaryWarnings(input) {
35622
+ const content = input.content;
35623
+ if (content === undefined)
35624
+ return;
35625
+ const target = summaryTargetLength(content);
35626
+ const summary = typeof input.summary === "string" ? input.summary : undefined;
35627
+ if (content.length > SUMMARY_REQUIRED_CONTENT_CHARS && summary === undefined) {
35628
+ input.state.warnings.push({
35629
+ code: "compile-section-summary-missing",
35630
+ severity: "warning",
35631
+ message: `${input.label}.content is ${content.length} characters; provide a one-paragraph summary before semantic review.`,
35632
+ next_action: `Add ${input.label}.summary around ${target} characters (minimum ${SUMMARY_TARGET_MIN_CHARS}, recommended maximum ${SUMMARY_TARGET_MAX_CHARS}) and keep content faithful to the cited raw.`,
35633
+ path: `${input.label}.summary`,
35413
35634
  diagnostics: {
35414
- content_length: value.length,
35415
- content_max_chars: MAX_DRAFT_CONTENT_CHARS,
35416
- over_by: value.length - MAX_DRAFT_CONTENT_CHARS
35635
+ content_length: content.length,
35636
+ target_summary_length: target,
35637
+ min_summary_length: SUMMARY_TARGET_MIN_CHARS,
35638
+ recommended_max_summary_length: SUMMARY_TARGET_MAX_CHARS
35417
35639
  }
35418
35640
  });
35641
+ return;
35419
35642
  }
35420
- if (/\r|\n/u.test(value)) {
35421
- throw draftError(slug, context, `${label} must be a single-line claim; move multi-line prose or code into detail`, {
35422
- path: label,
35423
- reasonCode: "content-multiline"
35643
+ if (content.length > SUMMARY_RECOMMENDED_CONTENT_CHARS && summary === undefined) {
35644
+ input.state.warnings.push({
35645
+ code: "compile-section-summary-recommended",
35646
+ severity: "warning",
35647
+ message: `${input.label}.content is ${content.length} characters; a short summary will improve query and reader UX.`,
35648
+ next_action: `Prefer ${input.label}.summary around ${target} characters when content is longer than ${SUMMARY_RECOMMENDED_CONTENT_CHARS} characters.`,
35649
+ path: `${input.label}.summary`,
35650
+ diagnostics: {
35651
+ content_length: content.length,
35652
+ target_summary_length: target
35653
+ }
35424
35654
  });
35655
+ return;
35425
35656
  }
35426
- if (/```|~~~/u.test(value)) {
35427
- throw draftError(slug, context, `${label} must not contain fenced code; put code blocks in detail`, {
35428
- path: label,
35429
- reasonCode: "content-fenced-code"
35657
+ if (summary === undefined)
35658
+ return;
35659
+ if (/\r|\n/u.test(summary) || /```|~~~|^#{1,6}\s|^\s*[-*+]\s/mu.test(summary)) {
35660
+ input.state.warnings.push({
35661
+ code: "compile-section-summary-format",
35662
+ severity: "warning",
35663
+ message: `${input.label}.summary should be one plain paragraph without Markdown formatting.`,
35664
+ next_action: "Rewrite summary as one short sentence or paragraph; keep lists, headings, and fenced code in content.",
35665
+ path: `${input.label}.summary`
35666
+ });
35667
+ }
35668
+ const lower = Math.max(1, Math.floor(target * 0.5));
35669
+ const upper = Math.ceil(target * 1.5);
35670
+ if (content.length > SUMMARY_REQUIRED_CONTENT_CHARS && (summary.length < lower || summary.length > upper)) {
35671
+ input.state.warnings.push({
35672
+ code: "compile-section-summary-length",
35673
+ severity: "warning",
35674
+ message: `${input.label}.summary is ${summary.length} characters; target is about ${target} for ${content.length}-character content.`,
35675
+ next_action: `Adjust summary toward ${target} characters; lenient accepted range is ${lower}-${upper}.`,
35676
+ path: `${input.label}.summary`,
35677
+ diagnostics: {
35678
+ content_length: content.length,
35679
+ summary_length: summary.length,
35680
+ target_summary_length: target,
35681
+ lenient_min_summary_length: lower,
35682
+ lenient_max_summary_length: upper
35683
+ }
35684
+ });
35685
+ } else if (summary.length > SUMMARY_LENIENT_MAX_CHARS) {
35686
+ input.state.warnings.push({
35687
+ code: "compile-section-summary-too-long",
35688
+ severity: "warning",
35689
+ message: `${input.label}.summary is ${summary.length} characters; keep summaries compact for query output.`,
35690
+ next_action: `Shorten summary to at most ${SUMMARY_LENIENT_MAX_CHARS} characters; preferred maximum is ${SUMMARY_TARGET_MAX_CHARS}.`,
35691
+ path: `${input.label}.summary`,
35692
+ diagnostics: {
35693
+ summary_length: summary.length,
35694
+ lenient_max_summary_length: SUMMARY_LENIENT_MAX_CHARS,
35695
+ recommended_max_summary_length: SUMMARY_TARGET_MAX_CHARS
35696
+ }
35430
35697
  });
35431
35698
  }
35432
35699
  }
35433
- function assertOptionalDraftString(value, slug, context, label) {
35434
- if (value !== undefined && typeof value !== "string") {
35435
- throw draftError(slug, context, `${label} must be a string when present`, { path: label, reasonCode: "invalid-string" });
35700
+ function collectDraftSummaryWarnings(output, state) {
35701
+ for (const [index, action] of output.actions.entries()) {
35702
+ if (action.op === "add") {
35703
+ pushSummaryWarnings({ state, content: action.content, summary: action.summary, label: `actions[${index}]` });
35704
+ } else if (action.op === "update") {
35705
+ pushSummaryWarnings({ state, content: action.content, summary: action.summary, label: `actions[${index}]` });
35706
+ } else if (action.op === "supersede") {
35707
+ pushSummaryWarnings({ state, content: action.new.content, summary: action.new.summary, label: `actions[${index}].new` });
35708
+ }
35436
35709
  }
35437
35710
  }
35438
35711
  function assertOptionalConfidence(value, slug, context) {
@@ -35485,35 +35758,7 @@ function assertSourceRefs(value, slug, context, label) {
35485
35758
  }
35486
35759
  });
35487
35760
  }
35488
- function bodyContent(value) {
35489
- const body2 = value.trim();
35490
- const compact2 = body2.replace(/^\s*(?:```|~~~)[^\n]*\n?/gmu, " ").replace(/^\s*(?:```|~~~)\s*$/gmu, " ").replace(/\s+/gu, " ").trim();
35491
- const content = compact2.length <= MAX_DRAFT_CONTENT_CHARS ? compact2 : `${compact2.slice(0, MAX_DRAFT_CONTENT_CHARS - 3)}...`;
35492
- if (body2 === content)
35493
- return { content };
35494
- return { content, detail: body2 };
35495
- }
35496
- function normalizeDraftAction(action) {
35497
- if (!isRecord15(action))
35498
- return action;
35499
- if (action.op === "supersede" && isRecord15(action.new)) {
35500
- return {
35501
- ...action,
35502
- new: normalizeDraftAction(action.new)
35503
- };
35504
- }
35505
- if (typeof action.body !== "string")
35506
- return action;
35507
- const split = bodyContent(action.body);
35508
- const normalized = {
35509
- ...action,
35510
- content: split.content,
35511
- ...split.detail !== undefined ? { detail: split.detail } : {}
35512
- };
35513
- delete normalized.body;
35514
- return normalized;
35515
- }
35516
- function retiredFieldIssuesForRecord(record, path8, includeContentDetail) {
35761
+ function retiredFieldIssuesForRecord(record, path8, includeWriteFields) {
35517
35762
  const issues = [];
35518
35763
  for (const field of RETIRED_EXTRACTIVE_FIELDS) {
35519
35764
  if (record[field] === undefined)
@@ -35521,27 +35766,27 @@ function retiredFieldIssuesForRecord(record, path8, includeContentDetail) {
35521
35766
  issues.push({
35522
35767
  path: `${path8}.${field}`,
35523
35768
  reason_code: "retired-draft-field",
35524
- message: `${path8}.${field} was removed in ${COMPILE_DRAFT_SCHEMA_VERSION}; emit body plus source_refs[] only`
35769
+ message: `${path8}.${field} was removed in ${COMPILE_DRAFT_SCHEMA_VERSION}; emit content, optional summary, and source_refs[] only`
35525
35770
  });
35526
35771
  }
35527
- if (includeContentDetail) {
35528
- for (const field of ["content", "detail"]) {
35772
+ if (includeWriteFields) {
35773
+ for (const field of RETIRED_DRAFT_WRITE_FIELDS) {
35529
35774
  if (record[field] === undefined)
35530
35775
  continue;
35531
35776
  issues.push({
35532
35777
  path: `${path8}.${field}`,
35533
35778
  reason_code: "retired-draft-field",
35534
- message: `${path8}.${field} is internal-only in ${COMPILE_DRAFT_SCHEMA_VERSION}; emit body instead`
35779
+ message: `${path8}.${field} is not part of the compile draft write contract; emit content, optional summary, and source_refs[]`
35535
35780
  });
35536
35781
  }
35537
35782
  }
35538
35783
  return issues;
35539
35784
  }
35540
- function bodyRequiredIssue(path8) {
35785
+ function contentRequiredIssue(path8) {
35541
35786
  return {
35542
- path: `${path8}.body`,
35543
- reason_code: "body-required",
35544
- message: `${path8}.body is required for Section write actions in ${COMPILE_DRAFT_SCHEMA_VERSION}`
35787
+ path: `${path8}.content`,
35788
+ reason_code: "content-required",
35789
+ message: `${path8}.content is required for Section write actions in ${COMPILE_DRAFT_SCHEMA_VERSION}`
35545
35790
  };
35546
35791
  }
35547
35792
  function retiredIssuesForAction(action, path8) {
@@ -35549,14 +35794,14 @@ function retiredIssuesForAction(action, path8) {
35549
35794
  return [];
35550
35795
  const op = action.op;
35551
35796
  const issues = retiredFieldIssuesForRecord(action, path8, op === "add" || op === "update");
35552
- if (op === "add" && action.body === undefined && action.content === undefined && action.detail === undefined) {
35553
- issues.push(bodyRequiredIssue(path8));
35797
+ if (op === "add" && action.content === undefined) {
35798
+ issues.push(contentRequiredIssue(path8));
35554
35799
  }
35555
35800
  if (op === "supersede") {
35556
35801
  if (isRecord15(action.new)) {
35557
35802
  issues.push(...retiredFieldIssuesForRecord(action.new, `${path8}.new`, true));
35558
- if (action.new.body === undefined && action.new.content === undefined && action.new.detail === undefined) {
35559
- issues.push(bodyRequiredIssue(`${path8}.new`));
35803
+ if (action.new.content === undefined) {
35804
+ issues.push(contentRequiredIssue(`${path8}.new`));
35560
35805
  }
35561
35806
  }
35562
35807
  }
@@ -35590,10 +35835,7 @@ function rejectRetiredAgentInputFields(input, slug, context) {
35590
35835
  function normalizeCompileDraftInput(input) {
35591
35836
  if (!isRecord15(input))
35592
35837
  return input;
35593
- return {
35594
- ...input,
35595
- actions: Array.isArray(input.actions) ? input.actions.map((action) => normalizeDraftAction(action)) : input.actions
35596
- };
35838
+ return input;
35597
35839
  }
35598
35840
  function rejectSingularSourceRef(action, slug, context, label) {
35599
35841
  if (action.source_ref === undefined)
@@ -35619,12 +35861,7 @@ function rejectUnsupportedEvidenceFields(action, slug, context, label) {
35619
35861
  function validateAddLikeActionShape(action, slug, context, knownSlugs, label) {
35620
35862
  assertSectionKind(action.kind, slug, context.raw_snippets[0]?.line ?? null, context);
35621
35863
  assertDraftContent(action.content, slug, context, `${label}.content`);
35622
- if (action.detail !== undefined && action.detail !== null && typeof action.detail !== "string") {
35623
- throw draftError(slug, context, `${label}.detail must be a string or null when present`, {
35624
- path: `${label}.detail`,
35625
- reasonCode: "invalid-detail"
35626
- });
35627
- }
35864
+ assertOptionalDraftString(action.summary, slug, context, `${label}.summary`);
35628
35865
  assertOptionalConfidence(action.confidence, slug, context);
35629
35866
  rejectUnsupportedEvidenceFields(action, slug, context, label);
35630
35867
  rejectSingularSourceRef(action, slug, context, label);
@@ -35677,12 +35914,8 @@ function validateDraftShape(output, slug, context, knownSlugs) {
35677
35914
  if (action.op === "update") {
35678
35915
  assertDraftString(action.target_section_id, slug, context, `actions[${index}].target_section_id`);
35679
35916
  assertDraftContent(action.content, slug, context, `actions[${index}].content`, true);
35680
- if (action.detail !== undefined && action.detail !== null && typeof action.detail !== "string") {
35681
- throw draftError(slug, context, `actions[${index}].detail must be a string or null when present`, {
35682
- path: `actions[${index}].detail`,
35683
- reasonCode: "invalid-detail"
35684
- });
35685
- }
35917
+ if (action.summary !== null)
35918
+ assertOptionalDraftString(action.summary, slug, context, `actions[${index}].summary`);
35686
35919
  assertOptionalConfidence(action.confidence, slug, context);
35687
35920
  rejectUnsupportedEvidenceFields(action, slug, context, `actions[${index}]`);
35688
35921
  rejectSingularSourceRef(action, slug, context, `actions[${index}]`);
@@ -35729,7 +35962,7 @@ function validateDraftShape(output, slug, context, knownSlugs) {
35729
35962
  }
35730
35963
  }
35731
35964
  function updateWritesEvidence(action) {
35732
- return action.content !== undefined || action.detail !== undefined || action.source_ref !== undefined || action.source_refs !== undefined;
35965
+ return action.content !== undefined || action.summary !== undefined || action.source_ref !== undefined || action.source_refs !== undefined;
35733
35966
  }
35734
35967
  function hasNoWriteContextSupport(context) {
35735
35968
  if (context.node.planned_sections === undefined || context.node.planned_sections.length > 0)
@@ -35796,6 +36029,7 @@ async function validateDraft(ctxDir, output, slug, context, options = {}) {
35796
36029
  }
35797
36030
  output = normalizeCompileDraftInput(output);
35798
36031
  validateDraftShape(output, slug, context, knownSlugs);
36032
+ collectDraftSummaryWarnings(output, state);
35799
36033
  if (output.target_node !== slug) {
35800
36034
  throw new ContextError(ExitCode.WorkspaceStateError, `compile draft target_node "${output.target_node}" does not match requested slug "${slug}"`, { category: ErrorCategory.SchemaInvalid });
35801
36035
  }
@@ -35832,7 +36066,7 @@ async function validateDraft(ctxDir, output, slug, context, options = {}) {
35832
36066
  }
35833
36067
  return state.warnings;
35834
36068
  }
35835
- var MAX_DRAFT_CONTENT_CHARS = 256, RETIRED_EXTRACTIVE_FIELDS;
36069
+ var SUMMARY_RECOMMENDED_CONTENT_CHARS = 160, SUMMARY_REQUIRED_CONTENT_CHARS = 200, SUMMARY_TARGET_MIN_CHARS = 10, SUMMARY_TARGET_MAX_CHARS = 120, SUMMARY_LENIENT_MAX_CHARS = 180, RETIRED_EXTRACTIVE_FIELDS, RETIRED_DRAFT_WRITE_FIELDS;
35836
36070
  var init_compileDraftValidation = __esm(() => {
35837
36071
  init_errors();
35838
36072
  init_cliFeedback();
@@ -35845,6 +36079,7 @@ var init_compileDraftValidation = __esm(() => {
35845
36079
  init_compileDraftChallenges();
35846
36080
  init_compileDraftSourceRefs();
35847
36081
  RETIRED_EXTRACTIVE_FIELDS = ["content_mode", "paraphrase_reason", "basis_spans"];
36082
+ RETIRED_DRAFT_WRITE_FIELDS = ["body", "detail", "raw", "rewrite"];
35848
36083
  });
35849
36084
 
35850
36085
  // src/lib/agentHintRegistry.ts
@@ -35885,6 +36120,7 @@ var init_agentHintRegistry = __esm(() => {
35885
36120
  "compile-cover-uncovered-only-context-required",
35886
36121
  "compile-challenges-recorded",
35887
36122
  "compile-challenges-recorded-no-reconcile-items",
36123
+ "compile-delegated-workflow-entry-only",
35888
36124
  "compile-changes-baseline-unknown",
35889
36125
  "compile-changes-content-update-skip-align",
35890
36126
  "compile-changes-draft-nodes",
@@ -35914,6 +36150,7 @@ var init_agentHintRegistry = __esm(() => {
35914
36150
  "compile-scan-changes-required",
35915
36151
  "compile-section-kind-guidance",
35916
36152
  "compile-source-refs-auto-narrowed",
36153
+ "compile-source-refs-planned-section-groups",
35917
36154
  "compile-source-finalize-published",
35918
36155
  "compile-unsupported-evidence-field-rejected",
35919
36156
  "coverage-disposition-context-inferred",
@@ -35936,7 +36173,7 @@ var init_agentHintRegistry = __esm(() => {
35936
36173
  "drop-workflow-mode-required",
35937
36174
  "evidence-manifest-schema-mismatch",
35938
36175
  "evidence-policy-invalid",
35939
- "example-detail-preservation",
36176
+ "example-content-preservation",
35940
36177
  "finalized-ownership-missing",
35941
36178
  "init-environment-language-detected",
35942
36179
  "init-residual-workflow-detected",
@@ -35947,7 +36184,9 @@ var init_agentHintRegistry = __esm(() => {
35947
36184
  "no-reconcile-items",
35948
36185
  "node-cycle-apply-failed-review-ready",
35949
36186
  "node-cycle-partial-apply-failed",
36187
+ "node-cycle-partial-applied-review-required",
35950
36188
  "node-cycle-review-required",
36189
+ "node-cycle-review-required-source-support",
35951
36190
  "ownership-challenge-invalid",
35952
36191
  "prefer-extract-for-short-evidence",
35953
36192
  "query-miss-no-local-evidence",
@@ -35962,7 +36201,7 @@ var init_agentHintRegistry = __esm(() => {
35962
36201
  "refresh-note-pending-compile",
35963
36202
  "refers-to-nodes-target-unknown",
35964
36203
  "review-errors-block-apply",
35965
- "review-content-detail-split",
36204
+ "review-content-shape",
35966
36205
  "review-safe-defaults-partial",
35967
36206
  "review-unknown-input-items",
35968
36207
  "schema-errors-empty-apply-document",
@@ -36002,6 +36241,7 @@ var init_agentHintRegistry = __esm(() => {
36002
36241
  "workflow-coverage-invalid",
36003
36242
  "workflow-coverage-summary",
36004
36243
  "workflow-cross-family-rejected",
36244
+ "workflow-delegated-mode-creation-only",
36005
36245
  "workflow-finalize-locked",
36006
36246
  "workflow-finalize-published-only",
36007
36247
  "workflow-history-listed",
@@ -36464,7 +36704,13 @@ function hasFinalizedGraphLink2(ownership, node2) {
36464
36704
  return (ownership.edges ?? []).some((edge2) => edge2.from === node2.slug || edge2.to === node2.slug);
36465
36705
  }
36466
36706
  function hasNoWritePlaceholderSupport(ownership, node2) {
36467
- return (node2.context_sources?.length ?? 0) > 0 || hasFinalizedGraphLink2(ownership, node2);
36707
+ return node2.sources.length > 0 || (node2.context_sources?.length ?? 0) > 0 || hasFinalizedGraphLink2(ownership, node2);
36708
+ }
36709
+ function noWriteContextSources(node2) {
36710
+ return [...new Set([
36711
+ ...node2.context_sources ?? [],
36712
+ ...node2.sources
36713
+ ].filter((sourceId) => sourceId.length > 0))].sort((left, right) => left.localeCompare(right));
36468
36714
  }
36469
36715
  async function ensureFinalizedContainerDomains(ctxDir, now) {
36470
36716
  const ownership = await readCurrentSourceOwnership(ctxDir);
@@ -36513,13 +36759,12 @@ async function ensureFinalizedNoWritePlaceholders(ctxDir, now) {
36513
36759
  for (const node2 of nodes) {
36514
36760
  if (!isExplicitNoWriteNode(node2))
36515
36761
  continue;
36516
- if (node2.sources.length > 0)
36517
- continue;
36518
36762
  if (!hasNoWritePlaceholderSupport(ownership, node2))
36519
36763
  continue;
36520
36764
  if (existsSync43(finalizedNodePath(ctxDir, node2)))
36521
36765
  continue;
36522
36766
  const children = finalizedContainerChildren(nodes, node2);
36767
+ const contextSources = noWriteContextSources(node2);
36523
36768
  await mdriveNodeCreate({
36524
36769
  ctxDir,
36525
36770
  input: {
@@ -36530,7 +36775,7 @@ async function ensureFinalizedNoWritePlaceholders(ctxDir, now) {
36530
36775
  tags: [...node2.tags],
36531
36776
  sources: [],
36532
36777
  updated: now.toISOString().slice(0, 10),
36533
- ...node2.context_sources !== undefined ? { context_sources: [...node2.context_sources] } : {},
36778
+ ...contextSources.length > 0 ? { context_sources: contextSources } : {},
36534
36779
  ...node2.summary !== undefined ? { summary: node2.summary } : {},
36535
36780
  ...node2.language !== undefined ? { language: node2.language } : {}
36536
36781
  },
@@ -36752,10 +36997,17 @@ async function assertFinalizedKnowledgeNodesMaterialized(ctxDir) {
36752
36997
  agent_hints: [{
36753
36998
  code: "compile-close-finalized-node-missing-knowledge",
36754
36999
  severity: "error",
36755
- message: hasMatchingChallenge ? "Every finalized Node must have materialized knowledge; recorded challenge debt documents the issue but does not unblock close." : "Every finalized Node must either have a knowledge article or be revised out of the align decision before close projects the graph.",
36756
- next_action: hasMatchingChallenge ? "Use the recorded challenge(s) to revise align ownership/structure, then rerun compile for affected Nodes before close." : "Compile the missing Node, submit a structure_challenge if align split the Node incorrectly, or rerun align finalize with the Node removed.",
37000
+ message: hasMatchingChallenge ? "Close cannot project a finalized Node that is neither a written knowledge article nor an explicit no-write placeholder. Recorded challenge debt documents the gap but does not unblock close." : "Close requires each finalized Node to materialize as a written knowledge article, or as an explicit no-write placeholder declared by align with planned_sections: [] and source/context/graph support.",
37001
+ next_action: hasMatchingChallenge ? "Resolve the recorded challenge by revising align ownership/structure or compiling the affected Node, then rerun close." : "If the Node has real citation evidence, compile it. If it is intentionally navigation-only or placeholder-only, rerun align so the Node has planned_sections: [] and its relation/placeholder blocks are context_only or ignored. If align split it incorrectly, revise/remove the Node through align.",
36757
37002
  command: hasMatchingChallenge ? "context schema align-structure-decision" : `context compile --context ${missing[0]?.slug ?? "<node-slug>"}`,
36758
- available_node_slugs: missing.map((node2) => node2.slug)
37003
+ available_node_slugs: missing.map((node2) => node2.slug),
37004
+ diagnostics: {
37005
+ materialized_knowledge: {
37006
+ article: "A CLI-written knowledge article exists for the finalized Node.",
37007
+ no_write_placeholder: "The finalized Node has planned_sections: [] plus source/context/graph support; close may materialize an empty placeholder.",
37008
+ skip_only: "A compile skip action records reviewed evidence but does not by itself materialize an arbitrary finalized Node."
37009
+ }
37010
+ }
36759
37011
  }]
36760
37012
  });
36761
37013
  }
@@ -37355,11 +37607,11 @@ function actionKind(action) {
37355
37607
  }
37356
37608
  function actionContent(action) {
37357
37609
  if (action.op === "add")
37358
- return action.content ?? action.body;
37610
+ return action.summary ?? action.content;
37359
37611
  if (action.op === "update")
37360
- return action.content ?? action.body;
37612
+ return action.summary ?? action.content;
37361
37613
  if (action.op === "supersede")
37362
- return action.new.content ?? action.new.body;
37614
+ return action.new.summary ?? action.new.content;
37363
37615
  return;
37364
37616
  }
37365
37617
  function actionTargetSection(action) {
@@ -37395,8 +37647,8 @@ function compileDraftAutoNarrowHint(event) {
37395
37647
  return {
37396
37648
  code: "compile-source-refs-auto-narrowed",
37397
37649
  severity: "warning",
37398
- message: `Draft ${event.path} cited ${event.original_source_refs.length} source_refs, but the Section body/detail is fully supported by ` + `${event.narrowed_source_refs.length}; the CLI narrowed the citation boundary.`,
37399
- next_action: "Keep the narrowed citation if the removed refs are unrelated, duplicate, or intentionally uncovered. If those refs contain distinct knowledge, add separate actions or expand this body/detail so the cited refs are actually consumed.",
37650
+ message: `Draft ${event.path} cited ${event.original_source_refs.length} source_refs, but the Section content is fully supported by ` + `${event.narrowed_source_refs.length}; the CLI narrowed the citation boundary.`,
37651
+ next_action: "Keep the narrowed citation if the removed refs are unrelated, duplicate, or intentionally uncovered. If those refs contain distinct knowledge, add separate actions or expand this content so the cited refs are actually consumed.",
37400
37652
  op: event.op,
37401
37653
  op_index: event.op_index,
37402
37654
  path: event.path,
@@ -37634,6 +37886,23 @@ function assertCitationEligibleSourceRefs2(input) {
37634
37886
  throw new Error(`${input.label} for node "${input.node}" contains non-citable source_ref "${sourceRef}"${role}; ` + "use pending_ownership_challenge or structure_challenge before citing this evidence");
37635
37887
  }
37636
37888
  }
37889
+ function normalizedForRawCompare2(value) {
37890
+ return normalizeMarkdown(value).trim();
37891
+ }
37892
+ async function rawDebugPatch(input) {
37893
+ if (typeof input.raw === "string" && input.raw.trim().length > 0) {
37894
+ return { raw: input.raw.trim() };
37895
+ }
37896
+ const resolved = await resolveSourceRefsToEvidence({
37897
+ ctxDir: input.ctxDir,
37898
+ sourceRefs: [input.sourceRef],
37899
+ nodeSources: input.nodeSources
37900
+ });
37901
+ const citedText = resolved?.cited_text.trim() ?? "";
37902
+ if (citedText.length === 0)
37903
+ return {};
37904
+ return normalizedForRawCompare2(input.content) === normalizedForRawCompare2(citedText) ? {} : { raw: citedText };
37905
+ }
37637
37906
  async function resolveDraftActionSourceRef(input) {
37638
37907
  const nodeSources = sourceRefNodeSources({
37639
37908
  sources: input.nodeSources,
@@ -37657,6 +37926,7 @@ async function resolveDraftActionSourceRef(input) {
37657
37926
  section: {
37658
37927
  content: input.action.content,
37659
37928
  ...input.action.kind !== undefined ? { kind: input.action.kind } : {},
37929
+ ...input.action.summary !== undefined ? { summary: input.action.summary } : {},
37660
37930
  ...input.action.detail !== undefined ? { detail: input.action.detail } : {}
37661
37931
  }
37662
37932
  });
@@ -37669,7 +37939,14 @@ async function resolveDraftActionSourceRef(input) {
37669
37939
  });
37670
37940
  return {
37671
37941
  source_ref: narrowed.source_ref,
37672
- source_refs: narrowed.narrowed_source_refs
37942
+ source_refs: narrowed.narrowed_source_refs,
37943
+ ...await rawDebugPatch({
37944
+ ctxDir: input.ctxDir,
37945
+ nodeSources,
37946
+ sourceRef: narrowed.source_ref,
37947
+ content: input.action.content,
37948
+ raw: input.action.raw
37949
+ })
37673
37950
  };
37674
37951
  }
37675
37952
  }
@@ -37682,7 +37959,14 @@ async function resolveDraftActionSourceRef(input) {
37682
37959
  throw new Error(`source_refs for node "${input.node}" do not resolve to one contiguous source_ref`);
37683
37960
  return {
37684
37961
  source_ref: resolved,
37685
- source_refs: input.action.source_refs
37962
+ source_refs: input.action.source_refs,
37963
+ ...input.action.content !== undefined ? await rawDebugPatch({
37964
+ ctxDir: input.ctxDir,
37965
+ nodeSources,
37966
+ sourceRef: resolved,
37967
+ content: input.action.content,
37968
+ raw: input.action.raw
37969
+ }) : {}
37686
37970
  };
37687
37971
  }
37688
37972
  if (input.action.source_ref !== undefined) {
@@ -37694,13 +37978,21 @@ async function resolveDraftActionSourceRef(input) {
37694
37978
  ...input.context !== undefined ? { context: input.context } : {}
37695
37979
  });
37696
37980
  }
37981
+ const resolved = await canonicalizeExplicitSourceRef({
37982
+ ctxDir: input.ctxDir,
37983
+ node: input.node,
37984
+ nodeSources,
37985
+ sourceRef: input.action.source_ref
37986
+ });
37697
37987
  return {
37698
- source_ref: await canonicalizeExplicitSourceRef({
37988
+ source_ref: resolved,
37989
+ ...input.action.content !== undefined ? await rawDebugPatch({
37699
37990
  ctxDir: input.ctxDir,
37700
- node: input.node,
37701
37991
  nodeSources,
37702
- sourceRef: input.action.source_ref
37703
- })
37992
+ sourceRef: resolved,
37993
+ content: input.action.content,
37994
+ raw: input.action.raw
37995
+ }) : {}
37704
37996
  };
37705
37997
  }
37706
37998
  return;
@@ -37741,7 +38033,7 @@ async function canonicalizeDraftSourceRefs(input) {
37741
38033
  path: `actions[${actionIndex}].source_refs`,
37742
38034
  onAutoNarrow: input.onAutoNarrow
37743
38035
  });
37744
- actions.push(sourceRef !== undefined ? { ...action, source_ref: sourceRef.source_ref, ...sourceRef.source_refs !== undefined ? { source_refs: sourceRef.source_refs } : {} } : action);
38036
+ actions.push(sourceRef !== undefined ? { ...action, source_ref: sourceRef.source_ref, ...sourceRef.source_refs !== undefined ? { source_refs: sourceRef.source_refs } : {}, ...sourceRef.raw !== undefined ? { raw: sourceRef.raw } : {} } : action);
37745
38037
  continue;
37746
38038
  }
37747
38039
  if (action.op === "update") {
@@ -37757,7 +38049,7 @@ async function canonicalizeDraftSourceRefs(input) {
37757
38049
  path: `actions[${actionIndex}].source_refs`,
37758
38050
  onAutoNarrow: input.onAutoNarrow
37759
38051
  });
37760
- actions.push(sourceRef !== undefined ? { ...action, source_ref: sourceRef.source_ref, ...sourceRef.source_refs !== undefined ? { source_refs: sourceRef.source_refs } : {} } : action);
38052
+ actions.push(sourceRef !== undefined ? { ...action, source_ref: sourceRef.source_ref, ...sourceRef.source_refs !== undefined ? { source_refs: sourceRef.source_refs } : {}, ...sourceRef.raw !== undefined ? { raw: sourceRef.raw } : {} } : action);
37761
38053
  continue;
37762
38054
  }
37763
38055
  if (action.op === "supersede") {
@@ -37775,7 +38067,7 @@ async function canonicalizeDraftSourceRefs(input) {
37775
38067
  });
37776
38068
  actions.push({
37777
38069
  ...action,
37778
- new: sourceRef !== undefined ? { ...action.new, source_ref: sourceRef.source_ref, ...sourceRef.source_refs !== undefined ? { source_refs: sourceRef.source_refs } : {} } : action.new
38070
+ new: sourceRef !== undefined ? { ...action.new, source_ref: sourceRef.source_ref, ...sourceRef.source_refs !== undefined ? { source_refs: sourceRef.source_refs } : {}, ...sourceRef.raw !== undefined ? { raw: sourceRef.raw } : {} } : action.new
37779
38071
  });
37780
38072
  continue;
37781
38073
  }
@@ -37803,6 +38095,7 @@ async function canonicalizeDraftSourceRefs(input) {
37803
38095
  var init_prepareCompileSourceRefs = __esm(() => {
37804
38096
  init_sourceRef();
37805
38097
  init_ref();
38098
+ init_normalize();
37806
38099
  init_sourceRefEligibility();
37807
38100
  init_sourceSupport();
37808
38101
  });
@@ -37905,7 +38198,9 @@ function compileDraftActionProposed(action) {
37905
38198
  return {
37906
38199
  op: "add",
37907
38200
  kind: action.kind,
38201
+ ...action.summary !== undefined ? { summary: action.summary } : {},
37908
38202
  content: action.content,
38203
+ ...action.raw !== undefined ? { raw: action.raw } : {},
37909
38204
  ...action.detail !== undefined ? { detail: action.detail } : {},
37910
38205
  ...action.confidence !== undefined ? { confidence: action.confidence } : {},
37911
38206
  ...action.refers_to_nodes !== undefined ? { refers_to_nodes: action.refers_to_nodes } : {},
@@ -37936,7 +38231,9 @@ function proposedPatchFromAddLike(action) {
37936
38231
  return;
37937
38232
  return {
37938
38233
  kind: action.kind,
38234
+ ...action.summary !== undefined ? { summary: action.summary } : {},
37939
38235
  content: action.content,
38236
+ ...action.raw !== undefined ? { raw: action.raw } : {},
37940
38237
  ...action.detail !== undefined ? { detail: action.detail } : {},
37941
38238
  ...action.confidence !== undefined ? { confidence: action.confidence } : {},
37942
38239
  source_ref: sourceRef,
@@ -37948,7 +38245,9 @@ function proposedPatchFromUpdate(action) {
37948
38245
  if (action.content === undefined || sourceRef === undefined)
37949
38246
  return;
37950
38247
  return {
38248
+ ...action.summary !== undefined ? { summary: action.summary } : {},
37951
38249
  content: action.content,
38250
+ ...action.raw !== undefined ? { raw: action.raw } : {},
37952
38251
  ...action.detail !== undefined ? { detail: action.detail } : {},
37953
38252
  ...action.confidence !== undefined ? { confidence: action.confidence } : {},
37954
38253
  source_ref: sourceRef,
@@ -38405,6 +38704,7 @@ async function compileSourceSupport(input) {
38405
38704
  action: `compile ${input.action.op}`,
38406
38705
  section: {
38407
38706
  kind: candidate.section.kind,
38707
+ ...candidate.section.summary !== undefined ? { summary: candidate.section.summary } : {},
38408
38708
  content: candidate.section.content,
38409
38709
  ...candidate.section.detail !== undefined ? { detail: candidate.section.detail } : {},
38410
38710
  source_ref: candidate.section.source_ref
@@ -38471,27 +38771,13 @@ function assertString(value, path8, node2) {
38471
38771
  function assertContent(value, path8, node2, optional = false) {
38472
38772
  if (value === undefined && optional)
38473
38773
  return;
38474
- const content = assertString(value, path8, node2);
38475
- if (content.length > MAX_DRAFT_CONTENT_CHARS2) {
38476
- throw compileDraftPrepareError(node2, path8, `compile draft ${path8} must be ${MAX_DRAFT_CONTENT_CHARS2} characters or fewer; move long-form prose into detail`, "content-too-long");
38477
- }
38478
- if (/\r|\n/u.test(content)) {
38479
- throw compileDraftPrepareError(node2, path8, `compile draft ${path8} must be a single-line claim; move multi-line prose or code into detail`, "content-must-be-single-line");
38480
- }
38481
- if (/```|~~~/u.test(content)) {
38482
- throw compileDraftPrepareError(node2, path8, `compile draft ${path8} must not contain fenced code; put code blocks in detail`, "content-fenced-code");
38483
- }
38774
+ assertString(value, path8, node2);
38484
38775
  }
38485
38776
  function assertOptionalString(value, path8, node2) {
38486
38777
  if (value !== undefined && typeof value !== "string") {
38487
38778
  throw compileDraftPrepareError(node2, path8, `compile draft ${path8} must be a string when present`, "invalid-string");
38488
38779
  }
38489
38780
  }
38490
- function assertOptionalDetail(value, path8, node2) {
38491
- if (value !== undefined && value !== null && typeof value !== "string") {
38492
- throw compileDraftPrepareError(node2, path8, `compile draft ${path8} must be a string or null when present`, "invalid-detail");
38493
- }
38494
- }
38495
38781
  function assertOptionalRefs(value, path8, node2, allowNull = false) {
38496
38782
  if (value === undefined || allowNull && value === null)
38497
38783
  return;
@@ -38500,6 +38786,9 @@ function assertOptionalRefs(value, path8, node2, allowNull = false) {
38500
38786
  }
38501
38787
  }
38502
38788
  function rejectUnsupportedEvidenceFields2(value, path8, node2) {
38789
+ if (value.body !== undefined || value.detail !== undefined || value.raw !== undefined) {
38790
+ throw compileDraftPrepareError(node2, path8, `compile draft ${path8} must use content plus optional summary; body/detail/raw are not accepted`, "retired-draft-field");
38791
+ }
38503
38792
  if (value.source_ref !== undefined) {
38504
38793
  throw compileDraftPrepareError(node2, `${path8}.source_ref`, `compile draft ${path8}.source_ref is retired; use source_refs: ["src-N#..."]`, "singular-source-ref-rejected");
38505
38794
  }
@@ -38511,7 +38800,7 @@ function validateAddLikeAction2(value, path8, node2) {
38511
38800
  rejectUnsupportedEvidenceFields2(value, path8, node2);
38512
38801
  assertString(value.kind, `${path8}.kind`, node2);
38513
38802
  assertContent(value.content, `${path8}.content`, node2);
38514
- assertOptionalDetail(value.detail, `${path8}.detail`, node2);
38803
+ assertOptionalString(value.summary, `${path8}.summary`, node2);
38515
38804
  assertOptionalString(value.confidence, `${path8}.confidence`, node2);
38516
38805
  assertOptionalRefs(value.source_refs, `${path8}.source_refs`, node2);
38517
38806
  assertOptionalRefs(value.refers_to_nodes, `${path8}.refers_to_nodes`, node2);
@@ -38546,7 +38835,8 @@ function validateCompileDraft(value, node2) {
38546
38835
  if (op === "update") {
38547
38836
  assertString(rawAction.target_section_id, `${path8}.target_section_id`, node2);
38548
38837
  assertContent(rawAction.content, `${path8}.content`, node2, true);
38549
- assertOptionalDetail(rawAction.detail, `${path8}.detail`, node2);
38838
+ if (rawAction.summary !== null)
38839
+ assertOptionalString(rawAction.summary, `${path8}.summary`, node2);
38550
38840
  assertOptionalString(rawAction.confidence, `${path8}.confidence`, node2);
38551
38841
  assertOptionalRefs(rawAction.source_refs, `${path8}.source_refs`, node2);
38552
38842
  assertOptionalRefs(rawAction.refers_to_nodes, `${path8}.refers_to_nodes`, node2, true);
@@ -38721,8 +39011,8 @@ function compileSourceRefAutoNarrowHints(events) {
38721
39011
  return events.map((event) => ({
38722
39012
  code: "compile-source-refs-auto-narrowed",
38723
39013
  severity: "warning",
38724
- message: `Draft ${event.path} cited ${event.original_source_refs.length} source_refs, but the Section body/detail is fully supported by ` + `${event.narrowed_source_refs.length}; the CLI narrowed the citation boundary.`,
38725
- next_action: "Keep the narrowed citation if the removed refs are unrelated, duplicate, or intentionally uncovered. If those refs contain distinct knowledge, add separate actions or expand this body/detail so the cited refs are actually consumed.",
39014
+ message: `Draft ${event.path} cited ${event.original_source_refs.length} source_refs, but the Section content is fully supported by ` + `${event.narrowed_source_refs.length}; the CLI narrowed the citation boundary.`,
39015
+ next_action: "Keep the narrowed citation if the removed refs are unrelated, duplicate, or intentionally uncovered. If those refs contain distinct knowledge, add separate actions or expand this content so the cited refs are actually consumed.",
38726
39016
  op: event.op,
38727
39017
  op_index: event.action_index,
38728
39018
  path: event.path,
@@ -38748,7 +39038,9 @@ function draftSkipCandidate(node2, action) {
38748
39038
  op: "add",
38749
39039
  section: {
38750
39040
  kind: action.kind,
39041
+ ...action.summary !== undefined ? { summary: action.summary } : {},
38751
39042
  content: action.content,
39043
+ ...action.raw !== undefined ? { raw: action.raw } : {},
38752
39044
  ...action.detail !== undefined ? { detail: action.detail } : {},
38753
39045
  ...action.confidence !== undefined ? { confidence: action.confidence } : {},
38754
39046
  ...action.refers_to_nodes !== undefined ? { refers_to_nodes: action.refers_to_nodes } : {},
@@ -38764,7 +39056,9 @@ function draftSkipCandidate(node2, action) {
38764
39056
  target_section_id: action.target_section_id,
38765
39057
  section: {
38766
39058
  kind: action.new.kind,
39059
+ ...action.new.summary !== undefined ? { summary: action.new.summary } : {},
38767
39060
  content: action.new.content,
39061
+ ...action.new.raw !== undefined ? { raw: action.new.raw } : {},
38768
39062
  ...action.new.detail !== undefined ? { detail: action.new.detail } : {},
38769
39063
  ...action.new.confidence !== undefined ? { confidence: action.new.confidence } : {},
38770
39064
  ...action.new.refers_to_nodes !== undefined ? { refers_to_nodes: action.new.refers_to_nodes } : {},
@@ -38781,7 +39075,9 @@ function draftSkipCandidate(node2, action) {
38781
39075
  target_section_id: action.target_section_id,
38782
39076
  section: {
38783
39077
  kind: current.kind,
39078
+ ...action.summary !== undefined ? action.summary === null ? {} : { summary: action.summary } : current.summary !== undefined ? { summary: current.summary } : {},
38784
39079
  content: action.content ?? current.content,
39080
+ ...action.raw !== undefined ? action.raw === null ? {} : { raw: action.raw } : current.raw !== undefined ? { raw: current.raw } : {},
38785
39081
  ...action.detail !== undefined ? { detail: action.detail } : current.detail !== undefined ? { detail: current.detail } : {},
38786
39082
  ...action.confidence !== undefined ? { confidence: action.confidence } : { confidence: current.confidence },
38787
39083
  ...action.refers_to_nodes !== undefined ? action.refers_to_nodes === null ? {} : { refers_to_nodes: action.refers_to_nodes } : current.refers_to_nodes !== undefined ? { refers_to_nodes: current.refers_to_nodes } : {},
@@ -38972,7 +39268,7 @@ async function prepareCompileReconcileContext(input) {
38972
39268
  agent_hints: [...context.agent_hints ?? [], ...judgeHint !== null ? [judgeHint] : []]
38973
39269
  };
38974
39270
  }
38975
- var import_yaml28, MAX_DRAFT_CONTENT_CHARS2 = 256;
39271
+ var import_yaml28;
38976
39272
  var init_prepareCompile = __esm(() => {
38977
39273
  init_compile();
38978
39274
  init_compileDraftValidation();
@@ -39587,7 +39883,7 @@ This directory is the C4A data root for a knowledge workspace.
39587
39883
  ## Agent Rules
39588
39884
 
39589
39885
  - Treat this data root as CLI-owned by default. Agents and users should not directly edit managed files unless a command or skill explicitly asks for a structured input file.
39590
- - Generated knowledge titles, summaries, and user-facing workflow reports follow this workspace language unless a CLI payload returns a more specific \`generation_policy\`. Keep \`node.summary\` concise: target under 15 tokens and never over 30 tokens. Source-bound Section content/detail should stay close to the cited source language when it differs from the workspace language. Preserve product names, code identifiers, command flags, slugs, \`block_id\` / \`source_ref\` tokens, and exact quoted evidence as printed when it is active user-facing knowledge.
39886
+ - Generated knowledge titles, summaries, and user-facing workflow reports follow this workspace language unless a CLI payload returns a more specific \`generation_policy\`. Keep \`node.summary\` concise: target under 15 tokens and never over 30 tokens. Source-bound Section content should stay close to the cited source language when it differs from the workspace language; add \`section.summary\` for long content as one plain paragraph. Preserve product names, code identifiers, command flags, slugs, \`block_id\` / \`source_ref\` tokens, and exact quoted evidence as printed when it is active user-facing knowledge.
39591
39887
  - Keep Agent prompts and handoff payloads stable: fixed protocol/schema rules first, existing knowledge lookup second, source-shared context third, and the current task-specific candidate/Node/question last. Do not add current timestamps, random ids, scratch paths, host absolute paths, or reordered JSON fields to Agent-authored payloads or reports unless a \`context\` command explicitly returned them as semantic workspace facts.
39592
39888
  - Existing \`knowledge/\` Nodes are the lookup registry. Use \`context mdrive glossary list|match\`, \`context mdrive node list\`, \`context mdrive query\`, or \`context query\` to reuse term, service, system, action, and domain handles. Do not create a separate dynamic registry file, and do not read \`knowledge/**\` directly just to decide whether a name already exists. When \`context mdrive glossary match <name>\` returns \`match.kind\`, \`match.matched\`, and \`match.rank\`, treat those as stable lookup hints; exact title/slug/alias hits should usually reuse the existing Node.
39593
39889
  - Naming surfaces are separate: \`/context:*\` names user slash commands, \`context ...\` names CLI primitives, and \`context:skill-*\` names packaged skills/procedures invoked by slash workflows. Do not invent \`/context:compile-draft\`, \`/context:skill-*\`, or call a packaged skill a user command.
@@ -39608,7 +39904,7 @@ This directory is the C4A data root for a knowledge workspace.
39608
39904
  - Use \`/context:align\` for structural planning. Read the current align workflow schemas, inspect \`align-segments\` through compact \`context workflow show --view ... --unwrap\` views, build candidate ledger outputs, and finalize with \`context align --finalize -\`. Align workflow payload submissions must go through stdin; do not write scratch YAML/JSON files for coarse-read, candidate ops, or structure decision. Finalize consumes only the structure-decision payload and finalized block ownership; it does not read raw directly or accept old patch inputs.
39609
39905
  - Use \`/context:compile\` for synthesis into \`knowledge/\`. Prepare NodeContext with \`context compile --context <slug> --format json\`, then inspect citation handles with \`context compile --source-refs <slug> --format json\` or \`context workflow show --payload node-context --view source-refs --unwrap --format json\` before reading the full payload. Read full NodeContext with \`context workflow show --payload node-context --unwrap --format json\` only when passing its \`.value\` into the draft procedure; the payload returns \`full-context\` when the incremental baseline is unsafe. Pass drafts to \`context compile --draft <slug> --input - --plan --prepare\`; the CLI stores workflow payloads for review/apply. Use \`--save-input\` only when an explicit draft debug copy is needed. For precise manual knowledge edits, use \`context mdrive ...\`; do not bypass the CLI for article markdown, indexes, or changelog entries.
39610
39906
  - \`/context:compile\` may process a workset, but each Node must complete its own context → draft → prepare → review → apply loop before the agent applies another Node. Do not run multiple Node reconcile/apply chains in parallel, hide several Node failures inside one batch command, or move to the next Node while the current Node has unresolved review questions, schema errors, unsupported evidence, or coverage warnings.
39611
- - Treat compile \`source_support\` as a lexical diagnostic, not a target to game. Do not copy raw text into Section detail merely to raise matched-term counts or show evidence; \`source_ref\` already provides traceability. \`rewrite=false\` and preserved prose/bullets are valid only when that wording is the actual user-facing knowledge.
39907
+ - Treat compile \`source_support\` as a lexical diagnostic, not a target to game. Do not copy raw text merely to raise matched-term counts or show evidence; \`source_ref\` already provides traceability. Keep Section \`content\` as the user-facing wording, preserving cited raw prose when it is already clear and only cleaning formatting, typos, casing, entity names, aliases, or grammar without changing meaning.
39612
39908
  - When \`context compile --draft <slug> --plan\` or \`context reconcile prepare --mode compile\` returns \`compact-source-low-coverage\` or \`dense-source-low-coverage\`, treat it as a required coverage progress warning. Check \`coverage is <covered>/<total>\` and \`remaining <N> snippet(s)\`, then return to the same draft and add actions for uncovered, source-backed, orthogonal facts before review/apply. If \`context verify\` or \`context doctor\` reports \`compact-source-evidence-undercovered\` or \`dense-source-evidence-undercovered\`, review the reported uncovered raw blocks before declaring the source complete. CLI success for one action only proves that action is supported; it does not prove the Node is complete.
39613
39909
  - Users may intentionally edit \`config.yaml\` and \`aspects/*/prompt.md\`; after doing so, run \`context doctor\` or \`context status\`.
39614
39910
  - Use \`context doctor\`, \`context verify\`, and \`context status\` to inspect workspace health instead of inferring state from partial files.
@@ -49530,7 +49826,7 @@ init_errors();
49530
49826
  init_knowledge();
49531
49827
  init_exitCode();
49532
49828
  var QUERY_FORMATS = ["json", "md", "table"];
49533
- var QUERY_INTENTS = ["orientation", "node_search", "description_search", "impact_analysis", "recall"];
49829
+ var QUERY_INTENTS = ["orientation", "node_lookup", "node_view", "section_search", "impact_analysis", "recall"];
49534
49830
  var QUERY_PROFILES = ["answer", "recall", "reconcile-dedupe", "reconcile-support", "reconcile-refresh"];
49535
49831
  var QUERY_OPTION_FLAG_LABELS = {
49536
49832
  format: "--format",
@@ -49571,7 +49867,7 @@ function normalizeContainsTree(value) {
49571
49867
  function hasStructuredFilter(options) {
49572
49868
  return options.node !== undefined || options.kind !== undefined || options.refersTo !== undefined || options.tag !== undefined || options.domain !== undefined || options.containsTree !== undefined || options.entityView !== undefined || options.scope !== undefined;
49573
49869
  }
49574
- function hasNodeSearchFilter(options) {
49870
+ function hasNodeViewFilter(options) {
49575
49871
  return options.node !== undefined || options.kind !== undefined || options.refersTo !== undefined || options.tag !== undefined || options.domain !== undefined || options.containsTree !== undefined || options.entityView !== undefined;
49576
49872
  }
49577
49873
  function hasExclusiveScopeTarget(options) {
@@ -49583,58 +49879,105 @@ function hasImpactAnchor(options) {
49583
49879
  function findImpactAnchor(options) {
49584
49880
  return options.scope ?? options.node ?? options.domain ?? options.entityView;
49585
49881
  }
49586
- function nodeSearchNeedsRetrieval(options) {
49882
+ function structuredQueryNeedsRetrieval(options) {
49587
49883
  return options.scope !== undefined || options.node !== undefined || options.domain !== undefined || options.containsTree !== undefined || options.entityView !== undefined || options.refersTo !== undefined || !hasStructuredFilter(options) && options.query !== undefined;
49588
49884
  }
49589
49885
  function presentFlags(options, flags2) {
49590
49886
  return flags2.filter((flag) => options[flag] !== undefined).map((flag) => QUERY_OPTION_FLAG_LABELS[flag]).filter((label) => label !== undefined);
49591
49887
  }
49592
- function assertParsedQueryOptions(options, hasExplicitProfile) {
49593
- const hasSearchInput = options.query !== undefined || options.scope !== undefined || hasNodeSearchFilter(options);
49594
- if (options.intent === "orientation") {
49595
- const unexpectedFlags = presentFlags(options, ["query", "node", "containsTree", "entityView", "kind", "refersTo", "scope"]);
49596
- if (unexpectedFlags.length > 0 || hasExplicitProfile) {
49597
- throw new ContextError(ExitCode.UserError, "context query --intent orientation accepts only --tag, --domain, and --format; it does not accept query text, node filters, section filters, or --profile", {
49598
- category: ErrorCategory.UserInputInvalid,
49599
- flag: "--intent",
49600
- unexpected_flags: [
49601
- ...unexpectedFlags,
49602
- ...hasExplicitProfile ? ["--profile"] : []
49603
- ]
49604
- });
49605
- }
49606
- return;
49888
+ function assertOrientationOptions(options, hasExplicitProfile) {
49889
+ if (options.intent !== "orientation")
49890
+ return false;
49891
+ const unexpectedFlags = presentFlags(options, ["query", "node", "containsTree", "entityView", "kind", "refersTo", "scope"]);
49892
+ if (unexpectedFlags.length > 0 || hasExplicitProfile) {
49893
+ throw new ContextError(ExitCode.UserError, "context query --intent orientation accepts only --tag, --domain, and --format; it does not accept query text, node filters, section filters, or --profile", {
49894
+ category: ErrorCategory.UserInputInvalid,
49895
+ flag: "--intent",
49896
+ unexpected_flags: [
49897
+ ...unexpectedFlags,
49898
+ ...hasExplicitProfile ? ["--profile"] : []
49899
+ ]
49900
+ });
49607
49901
  }
49608
- if (options.intent === undefined && !hasSearchInput) {
49609
- throw new ContextError(ExitCode.UserError, "context query requires --intent, --query, --scope, or a node_search filter", {
49610
- category: ErrorCategory.UserInputInvalid
49902
+ return true;
49903
+ }
49904
+ function assertNodeLookupOptions(options) {
49905
+ if (options.query === undefined) {
49906
+ throw new ContextError(ExitCode.UserError, "context query --intent node_lookup requires --query or a positional query", {
49907
+ category: ErrorCategory.UserInputInvalid,
49908
+ flag: "--query"
49909
+ });
49910
+ }
49911
+ if (hasStructuredFilter(options)) {
49912
+ const unexpectedFlags = presentFlags(options, ["node", "domain", "containsTree", "entityView", "tag", "kind", "refersTo", "scope"]);
49913
+ throw new ContextError(ExitCode.UserError, "node_lookup only resolves Node candidates from query text; use node_view for --scope, --node, --domain, --contains-tree, --entity-view, --tag, --kind, or --refers-to", {
49914
+ category: ErrorCategory.UserInputInvalid,
49915
+ flag: "--intent",
49916
+ unexpected_flags: unexpectedFlags
49611
49917
  });
49612
49918
  }
49613
- if (options.intent === "node_search" && !hasSearchInput) {
49614
- throw new ContextError(ExitCode.UserError, "context query --intent node_search requires --query, --scope, or a node_search filter", {
49919
+ }
49920
+ function assertNodeViewOptions(options) {
49921
+ const hasViewInput = options.scope !== undefined || hasNodeViewFilter(options);
49922
+ if (!hasViewInput) {
49923
+ throw new ContextError(ExitCode.UserError, "context query --intent node_view requires --scope, --node, --domain, --contains-tree, --entity-view, --tag, --kind, or --refers-to", {
49615
49924
  category: ErrorCategory.UserInputInvalid
49616
49925
  });
49617
49926
  }
49618
- if ((options.intent === "description_search" || options.intent === "recall") && hasNodeSearchFilter(options)) {
49619
- const unexpectedFlags = presentFlags(options, ["node", "domain", "containsTree", "entityView", "tag", "kind", "refersTo"]);
49620
- const hint = `to use ${unexpectedFlags.join(", ")}, switch to --intent node_search; use --scope to narrow ${options.intent}`;
49621
- throw new ContextError(ExitCode.UserError, `${options.intent} uses --scope for narrowing; --node, --domain, --contains-tree, --entity-view, --tag, --kind, and --refers-to are node_search filters; hint: ${hint}`, {
49927
+ if (options.query !== undefined) {
49928
+ throw new ContextError(ExitCode.UserError, "node_view opens an already-known structure target; use node_lookup to resolve query text or section_search to search Section content", {
49622
49929
  category: ErrorCategory.UserInputInvalid,
49623
- flag: "--scope",
49624
- expected_flag: "--scope",
49625
- unexpected_flags: unexpectedFlags,
49626
- hint
49930
+ flag: "--query"
49627
49931
  });
49628
49932
  }
49629
- if (options.intent === "impact_analysis" && (options.tag !== undefined || options.kind !== undefined || options.refersTo !== undefined || options.containsTree !== undefined)) {
49630
- const unexpectedFlags = presentFlags(options, ["tag", "kind", "refersTo", "containsTree"]);
49631
- throw new ContextError(ExitCode.UserError, "impact_analysis uses --scope, --node, --domain, or --entity-view as an impact anchor; --tag, --kind, --refers-to, and --contains-tree are node_search filters", {
49933
+ }
49934
+ function assertSectionOrRecallFilters(options) {
49935
+ if (options.intent !== "section_search" && options.intent !== "recall" || !hasNodeViewFilter(options))
49936
+ return;
49937
+ const unexpectedFlags = presentFlags(options, ["node", "domain", "containsTree", "entityView", "tag", "kind", "refersTo"]);
49938
+ const hint = `to use ${unexpectedFlags.join(", ")}, switch to --intent node_view; use --scope to narrow ${options.intent}`;
49939
+ throw new ContextError(ExitCode.UserError, `${options.intent} uses --scope for narrowing; --node, --domain, --contains-tree, --entity-view, --tag, --kind, and --refers-to are node_view filters; hint: ${hint}`, {
49940
+ category: ErrorCategory.UserInputInvalid,
49941
+ flag: "--scope",
49942
+ expected_flag: "--scope",
49943
+ unexpected_flags: unexpectedFlags,
49944
+ hint
49945
+ });
49946
+ }
49947
+ function assertImpactFilters(options) {
49948
+ if (options.intent !== "impact_analysis" || options.tag === undefined && options.kind === undefined && options.refersTo === undefined && options.containsTree === undefined)
49949
+ return;
49950
+ const unexpectedFlags = presentFlags(options, ["tag", "kind", "refersTo", "containsTree"]);
49951
+ throw new ContextError(ExitCode.UserError, "impact_analysis uses --scope, --node, --domain, or --entity-view as an impact anchor; --tag, --kind, --refers-to, and --contains-tree are node_view filters", {
49952
+ category: ErrorCategory.UserInputInvalid,
49953
+ flag: "--scope",
49954
+ expected_flag: "--scope",
49955
+ unexpected_flags: unexpectedFlags
49956
+ });
49957
+ }
49958
+ function assertParsedQueryOptions(options, hasExplicitProfile) {
49959
+ const hasSearchInput = options.query !== undefined || options.scope !== undefined || hasNodeViewFilter(options);
49960
+ if (assertOrientationOptions(options, hasExplicitProfile))
49961
+ return;
49962
+ if (options.intent === undefined && !hasSearchInput) {
49963
+ throw new ContextError(ExitCode.UserError, "context query requires --intent, --query, --scope, or a node_view filter", {
49964
+ category: ErrorCategory.UserInputInvalid
49965
+ });
49966
+ }
49967
+ if (options.intent === "node_lookup") {
49968
+ assertNodeLookupOptions(options);
49969
+ }
49970
+ if (options.intent === "node_view") {
49971
+ assertNodeViewOptions(options);
49972
+ }
49973
+ assertSectionOrRecallFilters(options);
49974
+ if (options.intent === "section_search" && options.query === undefined) {
49975
+ throw new ContextError(ExitCode.UserError, "context query --intent section_search requires --query or a positional query", {
49632
49976
  category: ErrorCategory.UserInputInvalid,
49633
- flag: "--scope",
49634
- expected_flag: "--scope",
49635
- unexpected_flags: unexpectedFlags
49977
+ flag: "--query"
49636
49978
  });
49637
49979
  }
49980
+ assertImpactFilters(options);
49638
49981
  if (hasExplicitProfile && options.intent !== "recall") {
49639
49982
  throw new ContextError(ExitCode.UserError, "--profile is only supported with --intent recall", {
49640
49983
  category: ErrorCategory.UserInputInvalid,
@@ -49647,12 +49990,6 @@ function assertParsedQueryOptions(options, hasExplicitProfile) {
49647
49990
  flag: "--query"
49648
49991
  });
49649
49992
  }
49650
- if (options.intent === "description_search" && options.query === undefined) {
49651
- throw new ContextError(ExitCode.UserError, "context query --intent description_search requires --query or a positional query", {
49652
- category: ErrorCategory.UserInputInvalid,
49653
- flag: "--query"
49654
- });
49655
- }
49656
49993
  if (options.scope !== undefined && hasExclusiveScopeTarget(options)) {
49657
49994
  throw new ContextError(ExitCode.UserError, "--scope is mutually exclusive with --node, --domain, --contains-tree, and --entity-view", { category: ErrorCategory.UserInputInvalid, flag: "--scope" });
49658
49995
  }
@@ -49670,7 +50007,8 @@ function parseQueryOptions(queryArg, options) {
49670
50007
  const domain = optionalString(options.domain);
49671
50008
  const entityView = optionalString(options.entityView);
49672
50009
  const scope = optionalString(options.scope);
49673
- const hasAnySearchInput = query !== undefined || node2 !== undefined || options.kind !== undefined || refersTo !== undefined || tag !== undefined || domain !== undefined || containsTree !== undefined || entityView !== undefined || scope !== undefined;
50010
+ const hasStructuredInput = node2 !== undefined || options.kind !== undefined || refersTo !== undefined || tag !== undefined || domain !== undefined || containsTree !== undefined || entityView !== undefined || scope !== undefined;
50011
+ const hasAnySearchInput = query !== undefined || hasStructuredInput;
49674
50012
  const effectiveIntent = intent ?? (hasAnySearchInput ? undefined : "orientation");
49675
50013
  const parsed = {
49676
50014
  format,
@@ -49805,6 +50143,7 @@ function sectionEntries(result) {
49805
50143
  node: section.node_slug,
49806
50144
  section: section.section_id,
49807
50145
  kind: section.kind,
50146
+ ...section.summary !== undefined ? { summary: section.summary } : {},
49808
50147
  content: section.content,
49809
50148
  ...section.refers_to_nodes.length > 0 ? { refers_to_nodes: section.refers_to_nodes } : {}
49810
50149
  }));
@@ -49834,6 +50173,17 @@ function edgeEntries(result) {
49834
50173
  function structuredData(result, options) {
49835
50174
  const sections = sectionEntries(result);
49836
50175
  const entries = result.sections.length > 0 ? sections : result.nodes.length > 0 ? nodeEntries(result) : edgeEntries(result);
50176
+ const nodeVisibility = result.fullNode === undefined ? undefined : {
50177
+ scope: "node",
50178
+ node: result.fullNode.slug,
50179
+ shown_sections: sections.length,
50180
+ total_sections: result.fullNode.sections.length,
50181
+ filtered: options.kind !== undefined || options.refersTo !== undefined,
50182
+ truncated: false,
50183
+ has_more: false,
50184
+ complete: true,
50185
+ next_action: "Answer from these visible Sections; run section_search only to narrow or rank evidence inside this Node."
50186
+ };
49837
50187
  const data = {
49838
50188
  ...options.query !== undefined ? { query: options.query } : {},
49839
50189
  ...options.scope !== undefined ? { scope: options.scope } : {},
@@ -49842,6 +50192,7 @@ function structuredData(result, options) {
49842
50192
  sections,
49843
50193
  edges: result.edges,
49844
50194
  ...result.fullNode !== undefined ? { fullNode: result.fullNode } : {},
50195
+ ...nodeVisibility !== undefined ? { visibility: nodeVisibility } : {},
49845
50196
  ...result.stats !== undefined ? { stats: result.stats } : {}
49846
50197
  };
49847
50198
  if (entries.length === 0) {
@@ -49887,6 +50238,25 @@ ${rows.map((row) => renderRow(headers.map((header) => stringifyCell(row[header])
49887
50238
  `)}
49888
50239
  `;
49889
50240
  }
50241
+ function renderVisibilityFooter(response) {
50242
+ const visibility = response.data.visibility;
50243
+ if (!visibility || typeof visibility !== "object" || Array.isArray(visibility))
50244
+ return "";
50245
+ const record = visibility;
50246
+ const scope = typeof record.scope === "string" ? record.scope : "scope";
50247
+ const slug = typeof record.node === "string" ? record.node : typeof record.slug === "string" ? record.slug : scope;
50248
+ const shown = typeof record.shown_sections === "number" ? record.shown_sections : undefined;
50249
+ const total = typeof record.total_sections === "number" ? record.total_sections : undefined;
50250
+ const filtered = record.filtered === true;
50251
+ const complete = record.complete === true && record.has_more !== true && record.truncated !== true;
50252
+ const visible = complete ? "complete" : "partial";
50253
+ const count = shown !== undefined && total !== undefined ? filtered ? ` — ${shown} matching sections shown for ${scope} ${slug} (${total} active sections total)` : ` — ${shown}/${total} sections shown for ${scope} ${slug}` : "";
50254
+ const next = typeof record.next_action === "string" ? `next: ${record.next_action}
50255
+ ` : "";
50256
+ return `
50257
+ visible: ${visible}${count}; ${complete ? "no pagination." : "more evidence may be hidden."}
50258
+ ${next}`;
50259
+ }
49890
50260
  function rowsForResponse(response) {
49891
50261
  if (response.state === "select")
49892
50262
  return rowsFromValue(response.data.candidates ?? []);
@@ -49926,6 +50296,9 @@ function renderMarkdown(response) {
49926
50296
  `;
49927
50297
  }
49928
50298
  lines.push("## Entries", "", renderTable(rowsForResponse(response)).trimEnd());
50299
+ const footer = renderVisibilityFooter(response).trimEnd();
50300
+ if (footer.length > 0)
50301
+ lines.push("", footer);
49929
50302
  return `${lines.join(`
49930
50303
  `)}
49931
50304
  `;
@@ -49937,7 +50310,11 @@ function writeQueryResult(response, format) {
49937
50310
  `);
49938
50311
  return;
49939
50312
  }
49940
- process.stdout.write(format === "md" ? renderMarkdown(agentResponse) : renderTable(rowsForResponse(agentResponse)));
50313
+ if (format === "md") {
50314
+ process.stdout.write(renderMarkdown(agentResponse));
50315
+ return;
50316
+ }
50317
+ process.stdout.write(renderTable(rowsForResponse(agentResponse)) + renderVisibilityFooter(agentResponse));
49941
50318
  }
49942
50319
 
49943
50320
  // src/commands/queryScope.ts
@@ -50022,7 +50399,7 @@ init_normalize();
50022
50399
  var QUERY_TEMPLATE = 'context query --scope <slug> --query "<question>"';
50023
50400
  var EXAMPLE_QUERY = "What does this node cover?";
50024
50401
  var TEXT_TOKEN_BUDGET = 2000;
50025
- var SUMMARY_TRUNCATION_NOTE = 'More: output was limited to about 2000 tokens. Deep map layers are folded first, then Summary entries are truncated. Drill down with scoped queries; there is no page-token pagination. Use: context query --intent node_search --scope <slug> or context query --intent description_search --scope <slug> --query "<question>".';
50402
+ var SUMMARY_TRUNCATION_NOTE = 'More: output was limited to about 2000 tokens. Deep map layers are folded first, then Summary entries are truncated. Drill down with scoped queries; there is no page-token pagination. Use: context query --intent node_view --scope <slug> or context query --intent section_search --scope <slug> --query "<question>".';
50026
50403
  var GROUPS = [
50027
50404
  { label: "Domains", classes: ["domain"] },
50028
50405
  { label: "Entities", classes: ["concrete_entity", "entity"] },
@@ -50188,8 +50565,8 @@ function renderOrientationText(output) {
50188
50565
  ` ${output.query_template}`,
50189
50566
  "",
50190
50567
  "Drill down:",
50191
- " context query --intent node_search --scope <slug>",
50192
- ' context query --intent description_search --scope <slug> --query "<question>"',
50568
+ " context query --intent node_view --scope <slug>",
50569
+ ' context query --intent section_search --scope <slug> --query "<question>"',
50193
50570
  "",
50194
50571
  "This structure is for choosing the next scope, not direct answer evidence."
50195
50572
  ];
@@ -50237,7 +50614,7 @@ function writeQueryOrientation(index, format, filters) {
50237
50614
 
50238
50615
  // src/commands/query.ts
50239
50616
  function searchPathForIntent(intent) {
50240
- if (intent === "description_search")
50617
+ if (intent === "section_search")
50241
50618
  return "section_search";
50242
50619
  if (intent === "impact_analysis")
50243
50620
  return "impact_graph";
@@ -50247,7 +50624,7 @@ function searchPathForIntent(intent) {
50247
50624
  }
50248
50625
  function chooseFallbackIntent(index, query) {
50249
50626
  const exact = findExactNodeMatches(index, query);
50250
- return exact.length > 0 ? "node_search" : "description_search";
50627
+ return exact.length > 0 ? "node_view" : "section_search";
50251
50628
  }
50252
50629
  function naturalLanguageFallback(index, options) {
50253
50630
  if (options.intent !== undefined || options.query === undefined || hasStructuredFilter(options))
@@ -50256,7 +50633,7 @@ function naturalLanguageFallback(index, options) {
50256
50633
  const anchors = mentionedQueryNodes(index, options.query);
50257
50634
  const onlyAnchor = anchors[0];
50258
50635
  if (shape === "definition" && anchors.length === 1 && onlyAnchor !== undefined) {
50259
- return { intent: "node_search", options: { ...options, node: onlyAnchor.slug } };
50636
+ return { intent: "node_view", options: { ...options, node: onlyAnchor.slug } };
50260
50637
  }
50261
50638
  return;
50262
50639
  }
@@ -50283,11 +50660,12 @@ function quoteCliArg(value) {
50283
50660
  return `'${value.replace(/'/g, "'\\''")}'`;
50284
50661
  }
50285
50662
  function suggestedQuery(intent, options, scope) {
50286
- const parts = [`context query --intent ${intent}`, `--scope ${quoteCliArg(scope)}`];
50287
- if (intent === "recall") {
50663
+ const targetIntent = intent === "node_lookup" ? "node_view" : intent;
50664
+ const parts = [`context query --intent ${targetIntent}`, `--scope ${quoteCliArg(scope)}`];
50665
+ if (targetIntent === "recall") {
50288
50666
  parts.push(`--profile ${options.profile}`);
50289
50667
  }
50290
- if ((intent === "description_search" || intent === "recall") && options.query !== undefined) {
50668
+ if ((targetIntent === "section_search" || targetIntent === "recall") && options.query !== undefined) {
50291
50669
  parts.push(`--query ${quoteCliArg(options.query)}`);
50292
50670
  }
50293
50671
  return parts.join(" ");
@@ -50330,6 +50708,7 @@ function candidateEntry(candidate) {
50330
50708
  node: candidate.node_slug,
50331
50709
  section: candidate.section_id,
50332
50710
  kind: candidate.kind,
50711
+ ...candidate.summary !== undefined ? { summary: candidate.summary } : {},
50333
50712
  content: candidate.content,
50334
50713
  ...candidate.refers_to_nodes.length > 0 ? { refers_to_nodes: candidate.refers_to_nodes } : {}
50335
50714
  };
@@ -50340,6 +50719,7 @@ function candidateEvidenceText(candidate) {
50340
50719
  candidate.node_title,
50341
50720
  candidate.section_id,
50342
50721
  candidate.kind,
50722
+ candidate.summary,
50343
50723
  candidate.content,
50344
50724
  candidate.detail,
50345
50725
  candidate.source_ref,
@@ -50428,7 +50808,7 @@ async function resolveStructuredNodeFilters(input) {
50428
50808
  if (references.length === 0)
50429
50809
  return { status: "ok", options: scoped };
50430
50810
  if (!input.index) {
50431
- throw new ContextError(ExitCode.WorkspaceStateError, "node_search filter resolution requires retrieval index", {
50811
+ throw new ContextError(ExitCode.WorkspaceStateError, "node_view filter resolution requires retrieval index", {
50432
50812
  category: ErrorCategory.Unknown
50433
50813
  });
50434
50814
  }
@@ -50450,7 +50830,7 @@ async function resolveStructuredNodeFilters(input) {
50450
50830
  }
50451
50831
  return { status: "ok", options: scoped };
50452
50832
  }
50453
- async function runNodeSearch(ctxDir, getIndex, options, intent, startedAt) {
50833
+ async function runNodeStructuredQuery(ctxDir, getIndex, options, intent, startedAt) {
50454
50834
  let scoped = options;
50455
50835
  if (options.scope === undefined && hasStructuredFilter(scoped)) {
50456
50836
  const exact = await mdriveQuery(structuredInput(ctxDir, scoped));
@@ -50467,7 +50847,7 @@ async function runNodeSearch(ctxDir, getIndex, options, intent, startedAt) {
50467
50847
  const scopeValue = scoped.scope ?? (!hasStructuredFilter(scoped) ? scoped.query : undefined);
50468
50848
  if (scopeValue !== undefined) {
50469
50849
  if (!index) {
50470
- throw new ContextError(ExitCode.WorkspaceStateError, "node_search scope resolution requires retrieval index", {
50850
+ throw new ContextError(ExitCode.WorkspaceStateError, "node_view scope resolution requires retrieval index", {
50471
50851
  category: ErrorCategory.Unknown
50472
50852
  });
50473
50853
  }
@@ -50534,7 +50914,7 @@ async function runRecallLikeSearch(index, options, intent, startedAt) {
50534
50914
  ...allowedNodeSlugs !== undefined ? { allowedNodeSlugs } : {},
50535
50915
  text: options.query
50536
50916
  });
50537
- const rawAssist = intent === "description_search" ? rawAssistedRecall({
50917
+ const rawAssist = intent === "section_search" ? rawAssistedRecall({
50538
50918
  index,
50539
50919
  text: options.query,
50540
50920
  ...allowedNodeSlugs !== undefined ? { allowedNodeSlugs } : {}
@@ -50547,7 +50927,7 @@ async function runRecallLikeSearch(index, options, intent, startedAt) {
50547
50927
  const queryTerms = options.query === undefined ? [] : tokenizeKnowledgeText(options.query);
50548
50928
  const mentionedNodesForQuery = mentionedQueryNodes(index, options.query);
50549
50929
  const support = evidenceTermCoverage({ queryTerms, candidates: mergedCandidates });
50550
- const unsupportedUnanchoredDescriptionHit = intent === "description_search" && options.query !== undefined && options.scope === undefined && mentionedNodesForQuery.length === 0 && queryTerms.length > 0 && mergedCandidates.length > 0 && support.missing.length > 0;
50930
+ const unsupportedUnanchoredDescriptionHit = intent === "section_search" && options.query !== undefined && options.scope === undefined && mentionedNodesForQuery.length === 0 && queryTerms.length > 0 && mergedCandidates.length > 0 && support.missing.length > 0;
50551
50931
  const candidates = mergedCandidates.map(candidateEntry);
50552
50932
  const data = {
50553
50933
  query: options.query,
@@ -50574,7 +50954,7 @@ async function runImpactSearch(ctxDir, index, options, intent, startedAt) {
50574
50954
  category: ErrorCategory.UserInputInvalid,
50575
50955
  flag: "--scope",
50576
50956
  expected_flag: "--scope",
50577
- hint: "Use context query --intent impact_analysis --scope <node_slug>, or use description_search for natural-language search."
50957
+ hint: "Use context query --intent impact_analysis --scope <node_slug>, or use section_search for natural-language search."
50578
50958
  });
50579
50959
  }
50580
50960
  const scopeValue = findImpactAnchor(options);
@@ -50614,7 +50994,7 @@ async function cmdQuery(ctxDir, options) {
50614
50994
  };
50615
50995
  const fallback = options.intent === undefined && options.query !== undefined ? naturalLanguageFallback(await getIndex(), options) : undefined;
50616
50996
  const scopedOptions = fallback?.options ?? options;
50617
- const intent = fallback?.intent ?? (options.intent !== undefined ? options.intent : hasStructuredFilter(options) || options.query === undefined ? "node_search" : chooseFallbackIntent(await getIndex(), options.query));
50997
+ const intent = fallback?.intent ?? (options.intent !== undefined ? options.intent : options.query !== undefined && options.scope !== undefined && !hasNodeViewFilter(options) ? "section_search" : hasStructuredFilter(options) || options.query === undefined ? "node_view" : chooseFallbackIntent(await getIndex(), options.query));
50618
50998
  if (intent === "orientation") {
50619
50999
  writeQueryOrientation(await getIndex(), options.format, {
50620
51000
  ...options.tag !== undefined ? { tag: options.tag } : {},
@@ -50622,11 +51002,11 @@ async function cmdQuery(ctxDir, options) {
50622
51002
  });
50623
51003
  return;
50624
51004
  }
50625
- const response = intent === "node_search" ? await runNodeSearch(ctxDir, nodeSearchNeedsRetrieval(scopedOptions) ? getIndex : undefined, scopedOptions, intent, startedAt) : intent === "impact_analysis" ? await runImpactSearch(ctxDir, hasImpactAnchor(scopedOptions) ? await getIndex() : undefined, scopedOptions, intent, startedAt) : await runRecallLikeSearch(await getIndex(), scopedOptions, intent, startedAt);
51005
+ const response = intent === "node_lookup" || intent === "node_view" ? await runNodeStructuredQuery(ctxDir, structuredQueryNeedsRetrieval(scopedOptions) ? getIndex : undefined, scopedOptions, intent, startedAt) : intent === "impact_analysis" ? await runImpactSearch(ctxDir, hasImpactAnchor(scopedOptions) ? await getIndex() : undefined, scopedOptions, intent, startedAt) : await runRecallLikeSearch(await getIndex(), scopedOptions, intent, startedAt);
50626
51006
  writeQueryResult(response, options.format);
50627
51007
  }
50628
51008
  function registerQueryCommand(program2) {
50629
- program2.command("query [query]").description("Query local knowledge with a hit/miss/select protocol").option("--intent <intent>", "query intent: orientation | node_search | description_search | impact_analysis | recall").option("--query <query>", "query text").option("--profile <profile>", "recall profile; only valid with --intent recall").option("--node <slug>", "single Node scope").option("--kind <kind>", "Section kind filter").option("--refers-to <slug>", "reverse Section reference lookup").option("--tag <tag>", "Node tag filter").option("--domain <slug>", "Domain subtree filter").option("--contains-tree [slug]", "contains tree filter").option("--entity-view <slug>", "aggregate view for one entity").option("--scope <slug>", "Node, title, alias, or Domain scope").option("--format <format>", "output format: json | md | table", "table").action(async (query, options) => {
51009
+ program2.command("query [query]").description("Query local knowledge with a hit/miss/select protocol").option("--intent <intent>", "query intent: orientation | node_lookup | node_view | section_search | impact_analysis | recall").option("--query <query>", "query text").option("--profile <profile>", "recall profile; only valid with --intent recall").option("--node <slug>", "single Node scope").option("--kind <kind>", "Section kind filter").option("--refers-to <slug>", "reverse Section reference lookup").option("--tag <tag>", "Node tag filter").option("--domain <slug>", "Domain subtree filter").option("--contains-tree [slug]", "contains tree filter").option("--entity-view <slug>", "aggregate view for one entity").option("--scope <slug>", "Node, title, alias, or Domain scope").option("--format <format>", "output format: json | md | table", "table").action(async (query, options) => {
50630
51010
  const parsed = parseQueryOptions(query, options);
50631
51011
  await runWithPrelude({
50632
51012
  name: "query",
@@ -50755,10 +51135,10 @@ function reviewAgentHints(input) {
50755
51135
  const contentShapeIssueCount = input.issues.filter((issue2) => issue2.severity === "error" && issue2.path.includes(".proposed") && issue2.path.endsWith(".content") && issue2.message.startsWith("content must")).length;
50756
51136
  if (contentShapeIssueCount > 0) {
50757
51137
  hints.push({
50758
- code: "review-content-detail-split",
51138
+ code: "review-content-shape",
50759
51139
  severity: "error",
50760
51140
  message: `${contentShapeIssueCount} proposed content field(s) contain long, multi-line, or code-block material.`,
50761
- next_action: "Keep proposed.content as one short single-line claim. Move code fences, examples, and extended prose into proposed.detail, then rerun review.",
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.",
50762
51142
  schema: "context schema semantic-decisions --format yaml"
50763
51143
  });
50764
51144
  }
@@ -50839,13 +51219,13 @@ function reviewAgentHints(input) {
50839
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."
50840
51220
  });
50841
51221
  }
50842
- const exampleDetailQuestionCount = input.questions.filter((question) => question.type === "example_detail_preservation").length;
51222
+ const exampleDetailQuestionCount = input.questions.filter((question) => question.type === "example_content_preservation").length;
50843
51223
  if (exampleDetailQuestionCount > 0) {
50844
51224
  hints.push({
50845
- code: "example-detail-preservation",
51225
+ code: "example-content-preservation",
50846
51226
  severity: "warning",
50847
- message: `${exampleDetailQuestionCount} example decision(s) cite code/config/command evidence without preserving it in detail.`,
50848
- next_action: "Move the relevant fenced block into proposed.detail and rerun review, or ask the user to confirm prose-only summary is acceptable. Auto mode or general permission to continue is not confirmation."
51227
+ 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."
50849
51229
  });
50850
51230
  }
50851
51231
  const urlReferenceQuestionCount = input.questions.filter((question) => question.type === "url_reference_preservation").length;
@@ -50854,7 +51234,7 @@ function reviewAgentHints(input) {
50854
51234
  code: "url-reference-preservation",
50855
51235
  severity: "warning",
50856
51236
  message: `${urlReferenceQuestionCount} decision(s) cite URL evidence without preserving the referenced link.`,
50857
- next_action: "Append each question's suggested_detail_append to proposed.detail and rerun review, or ask the user to confirm the link may be omitted. Auto mode or general permission to continue is not confirmation."
51237
+ next_action: "Append each question's suggested_content_append to proposed.content and rerun review, or ask the user to confirm the link may be omitted. Auto mode or general permission to continue is not confirmation."
50858
51238
  });
50859
51239
  }
50860
51240
  const unresolvedQuestionCount = input.questions.length;
@@ -50916,7 +51296,7 @@ function urlReferencePreservationQuestion(input) {
50916
51296
  detail: input.decision.proposed?.detail,
50917
51297
  citedText: citedTextForDecision(input.decision, input.item)
50918
51298
  });
50919
- const suggestedDetailAppend = diagnostic.missingUrls.map((url) => `- ${url}`).join(`
51299
+ const suggestedContentAppend = diagnostic.missingUrls.map((url) => `- ${url}`).join(`
50920
51300
  `);
50921
51301
  return {
50922
51302
  question_id: `q-${String(input.questionIndex + 1).padStart(3, "0")}`,
@@ -50927,11 +51307,11 @@ function urlReferencePreservationQuestion(input) {
50927
51307
  cited_urls: diagnostic.citedUrls,
50928
51308
  missing_urls: diagnostic.missingUrls,
50929
51309
  repair_options: [
50930
- "Append suggested_detail_append to proposed.detail and rerun review.",
51310
+ "Append suggested_content_append to proposed.content and rerun review.",
50931
51311
  "Ask the user only if the URL should intentionally be omitted; auto mode is not confirmation."
50932
51312
  ],
50933
- suggested_detail_append: suggestedDetailAppend,
50934
- prompt: "The cited raw evidence includes URL(s), but this document/link/reference claim does not preserve them in proposed content or detail. " + "Append suggested_detail_append to proposed.detail and rerun review, or ask the user to confirm that omitting the link is acceptable and mark the decision decided_by: user. Auto mode or general permission to continue is not confirmation."
51313
+ suggested_content_append: suggestedContentAppend,
51314
+ prompt: "The cited raw evidence includes URL(s), but this document/link/reference claim does not preserve them in proposed.content. " + "Append suggested_content_append to proposed.content and rerun review, or ask the user to confirm that omitting the link is acceptable and mark the decision decided_by: user. Auto mode or general permission to continue is not confirmation."
50935
51315
  };
50936
51316
  }
50937
51317
  function urlReferencePreservationQuestions(input) {
@@ -51197,7 +51577,195 @@ function semanticDecisionSchemaErrorReview(input) {
51197
51577
 
51198
51578
  // src/reconcile/review.ts
51199
51579
  init_compileJudge();
51200
- var FENCED_CODE_RE2 = /(^|\n)\s*(```|~~~)/u;
51580
+
51581
+ // src/reconcile/delegatedDecisions.ts
51582
+ var DELEGATED_DECIDED_BY = "delegated_agent";
51583
+ function isDelegatedWeakDefault(item) {
51584
+ const support = item.source_support;
51585
+ return support?.verdict === "weak" && (support.missing_hard_terms?.length ?? 0) === 0;
51586
+ }
51587
+ function allowsWeakSourceSupport(input) {
51588
+ return input.decision.decided_by === "user" || input.allowDelegatedDecisions === true && input.decision.decided_by === DELEGATED_DECIDED_BY;
51589
+ }
51590
+ function delegatedDecisionModeIssues(input) {
51591
+ if (input.allowDelegatedDecisions === true)
51592
+ return [];
51593
+ return input.decisions.filter((decision) => decision.decided_by === DELEGATED_DECIDED_BY).map((decision) => ({
51594
+ path: `${decision.item_id}.decided_by`,
51595
+ item_id: decision.item_id,
51596
+ severity: "error",
51597
+ code: "delegated-decision-without-workflow-mode",
51598
+ message: "delegated_agent decisions require a delegated compile workflow created with --delegated",
51599
+ next_action: "Do not mark decisions as delegated_agent manually. Restart the compile workflow with --delegated only when the user explicitly authorized delegated mode at the start of the conversation."
51600
+ }));
51601
+ }
51602
+ function assertDelegatedDecisionsAllowed(input) {
51603
+ if (input.allowDelegatedDecisions === true)
51604
+ return;
51605
+ if (input.decisions.some((decision) => decision.decided_by === DELEGATED_DECIDED_BY)) {
51606
+ throw new Error("delegated_agent decisions require a delegated workflow created with --delegated");
51607
+ }
51608
+ }
51609
+
51610
+ // src/reconcile/reviewSourceSupport.ts
51611
+ init_sourceSupport();
51612
+ var FENCED_CODE_RE = /(^|\n)\s*(```|~~~)/u;
51613
+ function preparedSupportContentMatches(input) {
51614
+ return typeof input.item?.proposed?.content === "string" && input.item.proposed.content === input.content || typeof input.item?.default_decision?.proposed?.content === "string" && input.item.default_decision.proposed.content === input.content;
51615
+ }
51616
+ function actionNeedsSourceSupport2(action) {
51617
+ return action === "keep_separate" || actionRequiresStrictSourceSupport(action);
51618
+ }
51619
+ function actionRequiresStrictSourceSupport(action) {
51620
+ return action === "merge_update" || action === "supersede" || action === "reanchor" || action === "split_then_reanchor";
51621
+ }
51622
+ function sourceRefChangedFromPreparedSupport(input) {
51623
+ const proposedSourceRef = input.decision.proposed?.source_ref;
51624
+ const preparedSourceRef = input.item?.source_support?.source_ref;
51625
+ return typeof proposedSourceRef === "string" && typeof preparedSourceRef === "string" && proposedSourceRef !== preparedSourceRef;
51626
+ }
51627
+ function supportKind(decision, item) {
51628
+ if (typeof decision.proposed?.kind === "string")
51629
+ return decision.proposed.kind;
51630
+ const target = decision.target ?? item?.target;
51631
+ if (target?.section_id !== undefined) {
51632
+ const targetCandidate = item?.candidates.find((candidate) => candidate.node === target.node && candidate.section_id === target.section_id);
51633
+ if (targetCandidate !== undefined)
51634
+ return targetCandidate.kind;
51635
+ }
51636
+ return typeof item?.proposed?.kind === "string" ? item.proposed.kind : undefined;
51637
+ }
51638
+ function supportDiagnosticFromItem(input) {
51639
+ if (input.decision.user_confirmation?.required === true)
51640
+ return null;
51641
+ if (input.decision.action === "ask_user")
51642
+ return null;
51643
+ if (!actionNeedsSourceSupport2(input.decision.action))
51644
+ return null;
51645
+ if (input.decision.judge_support_verdict !== undefined) {
51646
+ return {
51647
+ verdict: input.decision.judge_support_verdict,
51648
+ contentTermCount: 1,
51649
+ matchedTermCount: input.decision.judge_support_verdict === "unsupported" ? 0 : 1,
51650
+ requiredTermCount: 1,
51651
+ overlapRatio: input.decision.judge_support_verdict === "unsupported" ? 0 : 1,
51652
+ missingContentTerms: [],
51653
+ missingHardTerms: []
51654
+ };
51655
+ }
51656
+ const content = input.decision.proposed?.content;
51657
+ const sourceRef = input.decision.proposed?.source_ref;
51658
+ if (typeof content !== "string" || typeof sourceRef !== "string")
51659
+ return null;
51660
+ const kind = supportKind(input.decision, input.item);
51661
+ const detail = input.decision.proposed?.detail === null || typeof input.decision.proposed?.detail === "string" ? input.decision.proposed.detail : undefined;
51662
+ const itemSupport = input.item?.source_support;
51663
+ if (itemSupport !== undefined && itemSupport.source_ref === sourceRef && typeof itemSupport.cited_text === "string") {
51664
+ return sourceSectionSupportDiagnostic({ kind, content, detail, citedText: itemSupport.cited_text });
51665
+ }
51666
+ if (itemSupport !== undefined && itemSupport.source_ref === sourceRef && preparedSupportContentMatches({ content, item: input.item })) {
51667
+ return {
51668
+ verdict: itemSupport.verdict,
51669
+ contentTermCount: itemSupport.content_term_count,
51670
+ matchedTermCount: itemSupport.matched_term_count,
51671
+ requiredTermCount: itemSupport.required_term_count,
51672
+ overlapRatio: itemSupport.overlap_ratio,
51673
+ missingContentTerms: itemSupport.missing_content_terms,
51674
+ missingHardTerms: itemSupport.missing_hard_terms
51675
+ };
51676
+ }
51677
+ const citedText = (input.item?.evidence ?? []).filter((span) => span.source_ref === undefined || span.source_ref === sourceRef).map((span) => span.quote).filter((quote) => typeof quote === "string" && quote.trim().length > 0).join(`
51678
+ `);
51679
+ return citedText.length > 0 ? sourceSectionSupportDiagnostic({ kind, content, detail, citedText }) : null;
51680
+ }
51681
+ function sourceSupportIssue(input) {
51682
+ const action = input.decision.action;
51683
+ const strict = actionRequiresStrictSourceSupport(action);
51684
+ 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.";
51686
+ const splitHint = splitCandidates !== undefined && splitCandidates.length > 0 ? ` Candidate evidence blocks for split decisions: ${splitCandidates}.` : "";
51687
+ const splitCandidateCount = input.support?.evidence_block_candidates?.length ?? 0;
51688
+ return {
51689
+ path: `${input.decision.item_id}.proposed.source_ref`,
51690
+ message: strict ? `${action} requires direct support from cited evidence before apply: ${formatSupportDiagnostic(input.diagnostic)}. ${repairHint}${splitHint}` : `proposed keep_separate is not supported by its cited raw text: ${formatSupportDiagnostic(input.diagnostic)}. ${repairHint}${splitHint}`,
51691
+ severity: "error",
51692
+ ...splitCandidateCount > 1 ? { code: "source-support-split-by-evidence-blocks" } : {}
51693
+ };
51694
+ }
51695
+ function weakSupportWarning(input) {
51696
+ return {
51697
+ path: `${input.decision.item_id}.proposed.source_ref`,
51698
+ item_id: input.decision.item_id,
51699
+ severity: "warning",
51700
+ code: "weak-source-support",
51701
+ 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."
51703
+ };
51704
+ }
51705
+ function citedTextForDecision2(decision, item) {
51706
+ const sourceRef = decision.proposed?.source_ref;
51707
+ const itemSupport = item?.source_support;
51708
+ if (typeof sourceRef === "string" && itemSupport?.source_ref === sourceRef && typeof itemSupport.cited_text === "string") {
51709
+ return itemSupport.cited_text;
51710
+ }
51711
+ return (item?.evidence ?? []).filter((span) => span.source_ref === undefined || span.source_ref === sourceRef).map((span) => span.quote).filter((quote) => typeof quote === "string" && quote.trim().length > 0).join(`
51712
+ `);
51713
+ }
51714
+ function needsExampleDetailPreservationQuestion(input) {
51715
+ if (input.decision.user_confirmation?.required === true || input.decision.action === "ask_user")
51716
+ return false;
51717
+ if (input.decision.decided_by === "user")
51718
+ return false;
51719
+ if (!actionNeedsSourceSupport2(input.decision.action))
51720
+ return false;
51721
+ if (supportKind(input.decision, input.item) !== "example")
51722
+ return false;
51723
+ if (!FENCED_CODE_RE.test(citedTextForDecision2(input.decision, input.item)))
51724
+ return false;
51725
+ return !FENCED_CODE_RE.test(input.decision.proposed?.content ?? "");
51726
+ }
51727
+ function exampleDetailPreservationQuestion(input) {
51728
+ return {
51729
+ question_id: `q-${String(input.questionIndex + 1).padStart(3, "0")}`,
51730
+ item_id: input.decision.item_id,
51731
+ type: "example_content_preservation",
51732
+ ...input.summary !== undefined ? { candidate_summary: { active: input.summary.active, archive: input.summary.archive } } : {},
51733
+ ...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."
51735
+ };
51736
+ }
51737
+ function sourceSupportFromDiagnostic(input) {
51738
+ return {
51739
+ source_ref: input.sourceRef,
51740
+ cited_text: input.citedText,
51741
+ verdict: input.diagnostic.verdict,
51742
+ content_term_count: input.diagnostic.contentTermCount,
51743
+ matched_term_count: input.diagnostic.matchedTermCount,
51744
+ required_term_count: input.diagnostic.requiredTermCount,
51745
+ overlap_ratio: input.diagnostic.overlapRatio,
51746
+ missing_content_terms: input.diagnostic.missingContentTerms,
51747
+ missing_hard_terms: input.diagnostic.missingHardTerms,
51748
+ ...input.evidenceBlock !== undefined ? {
51749
+ evidence_block_source_ref: input.evidenceBlock.source_ref,
51750
+ evidence_block_line_range: input.evidenceBlock.line_range,
51751
+ evidence_block_locator_id: input.evidenceBlock.block_locator_id
51752
+ } : {},
51753
+ ...input.evidenceBlockCandidates !== undefined ? { evidence_block_candidates: input.evidenceBlockCandidates } : {}
51754
+ };
51755
+ }
51756
+ function needsWorkspaceSupportReview(decision, item) {
51757
+ if (decision.user_confirmation?.required === true || decision.action === "ask_user")
51758
+ return false;
51759
+ if (!actionNeedsSourceSupport2(decision.action))
51760
+ return false;
51761
+ const sourceRef = decision.proposed?.source_ref;
51762
+ const content = decision.proposed?.content;
51763
+ if (typeof sourceRef !== "string" || typeof content !== "string")
51764
+ return false;
51765
+ return item?.source_support?.source_ref !== sourceRef || item.source_support.cited_text === undefined;
51766
+ }
51767
+
51768
+ // src/reconcile/review.ts
51201
51769
  function isRecord21(value) {
51202
51770
  return typeof value === "object" && value !== null && !Array.isArray(value);
51203
51771
  }
@@ -51356,13 +51924,13 @@ function decisionItemIdIssues(input) {
51356
51924
  }];
51357
51925
  });
51358
51926
  }
51359
- function preparedDetail(item) {
51360
- const detail = item?.proposed?.detail;
51361
- return detail === null || typeof detail === "string" ? detail : undefined;
51927
+ function preparedOptionalText(item, field) {
51928
+ const value = item?.proposed?.[field];
51929
+ return value === null || typeof value === "string" ? value : undefined;
51362
51930
  }
51363
- function shouldPreservePreparedDetail(decision, item) {
51364
- const detail = preparedDetail(item);
51365
- if (detail === undefined || !decision.proposed || decision.proposed.detail !== undefined)
51931
+ function shouldPreservePreparedOptionalText(decision, item, field) {
51932
+ const value = preparedOptionalText(item, field);
51933
+ if (value === undefined || !decision.proposed || decision.proposed[field] !== undefined)
51366
51934
  return false;
51367
51935
  const prepared = item?.proposed;
51368
51936
  if (typeof prepared?.content === "string" && typeof decision.proposed.content === "string" && prepared.content !== decision.proposed.content) {
@@ -51373,23 +51941,29 @@ function shouldPreservePreparedDetail(decision, item) {
51373
51941
  }
51374
51942
  return true;
51375
51943
  }
51376
- function preservePreparedDetails(input) {
51944
+ function preservePreparedOptionalText(input) {
51377
51945
  const issues = [];
51378
51946
  const decisions = input.document.decisions.map((decision, index) => {
51379
51947
  const item = input.itemById.get(decision.item_id);
51380
- const detail = preparedDetail(item);
51381
- if (detail === undefined || !shouldPreservePreparedDetail(decision, item))
51948
+ const preserved = {};
51949
+ for (const field of ["summary", "raw"]) {
51950
+ const value = preparedOptionalText(item, field);
51951
+ if (value === undefined || !shouldPreservePreparedOptionalText(decision, item, field))
51952
+ continue;
51953
+ preserved[field] = value;
51954
+ issues.push({
51955
+ path: `decisions[${index}].proposed.${field}`,
51956
+ message: `decision omitted proposed.${field} from the prepared context; review apply_document preserves it for apply`,
51957
+ severity: "warning"
51958
+ });
51959
+ }
51960
+ if (Object.keys(preserved).length === 0)
51382
51961
  return decision;
51383
- issues.push({
51384
- path: `decisions[${index}].proposed.detail`,
51385
- message: "decision omitted proposed.detail from the prepared context; review apply_document preserves it for apply",
51386
- severity: "warning"
51387
- });
51388
51962
  return {
51389
51963
  ...decision,
51390
51964
  proposed: {
51391
51965
  ...decision.proposed,
51392
- detail
51966
+ ...preserved
51393
51967
  }
51394
51968
  };
51395
51969
  });
@@ -51405,6 +51979,8 @@ function proposedPatchFromItem(item) {
51405
51979
  kind: proposed.kind,
51406
51980
  content: proposed.content,
51407
51981
  source_ref: proposed.source_ref,
51982
+ ...typeof proposed.summary === "string" || proposed.summary === null ? { summary: proposed.summary } : {},
51983
+ ...typeof proposed.raw === "string" || proposed.raw === null ? { raw: proposed.raw } : {},
51408
51984
  ...typeof proposed.detail === "string" || proposed.detail === null ? { detail: proposed.detail } : {},
51409
51985
  ...typeof proposed.confidence === "string" ? { confidence: proposed.confidence } : {},
51410
51986
  ...Array.isArray(proposed.refers_to_nodes) && proposed.refers_to_nodes.every((value) => typeof value === "string") ? { refers_to_nodes: proposed.refers_to_nodes } : {}
@@ -51421,133 +51997,6 @@ function hydrateOmitDecisions(input) {
51421
51997
  })
51422
51998
  };
51423
51999
  }
51424
- function supportDiagnosticFromItem(input) {
51425
- if (input.decision.user_confirmation?.required === true)
51426
- return null;
51427
- if (input.decision.action === "ask_user")
51428
- return null;
51429
- if (!actionNeedsSourceSupport2(input.decision.action))
51430
- return null;
51431
- if (input.decision.judge_support_verdict !== undefined) {
51432
- return {
51433
- verdict: input.decision.judge_support_verdict,
51434
- contentTermCount: 1,
51435
- matchedTermCount: input.decision.judge_support_verdict === "unsupported" ? 0 : 1,
51436
- requiredTermCount: 1,
51437
- overlapRatio: input.decision.judge_support_verdict === "unsupported" ? 0 : 1,
51438
- missingContentTerms: [],
51439
- missingHardTerms: []
51440
- };
51441
- }
51442
- const content = input.decision.proposed?.content;
51443
- const sourceRef = input.decision.proposed?.source_ref;
51444
- if (typeof content !== "string" || typeof sourceRef !== "string")
51445
- return null;
51446
- const kind = supportKind(input.decision, input.item);
51447
- const detail = input.decision.proposed?.detail === null || typeof input.decision.proposed?.detail === "string" ? input.decision.proposed.detail : undefined;
51448
- const itemSupport = input.item?.source_support;
51449
- if (itemSupport !== undefined && itemSupport.source_ref === sourceRef && typeof itemSupport.cited_text === "string") {
51450
- return sourceSectionSupportDiagnostic({ kind, content, detail, citedText: itemSupport.cited_text });
51451
- }
51452
- if (itemSupport !== undefined && itemSupport.source_ref === sourceRef && preparedSupportContentMatches({
51453
- content,
51454
- item: input.item
51455
- })) {
51456
- return {
51457
- verdict: itemSupport.verdict,
51458
- contentTermCount: itemSupport.content_term_count,
51459
- matchedTermCount: itemSupport.matched_term_count,
51460
- requiredTermCount: itemSupport.required_term_count,
51461
- overlapRatio: itemSupport.overlap_ratio,
51462
- missingContentTerms: itemSupport.missing_content_terms,
51463
- missingHardTerms: itemSupport.missing_hard_terms
51464
- };
51465
- }
51466
- const citedText = (input.item?.evidence ?? []).filter((span) => span.source_ref === undefined || span.source_ref === sourceRef).map((span) => span.quote).filter((quote) => typeof quote === "string" && quote.trim().length > 0).join(`
51467
- `);
51468
- return citedText.length > 0 ? sourceSectionSupportDiagnostic({ kind, content, detail, citedText }) : null;
51469
- }
51470
- function preparedSupportContentMatches(input) {
51471
- return typeof input.item?.proposed?.content === "string" && input.item.proposed.content === input.content || typeof input.item?.default_decision?.proposed?.content === "string" && input.item.default_decision.proposed.content === input.content;
51472
- }
51473
- function actionNeedsSourceSupport2(action) {
51474
- return action === "keep_separate" || actionRequiresStrictSourceSupport(action);
51475
- }
51476
- function actionRequiresStrictSourceSupport(action) {
51477
- return action === "merge_update" || action === "supersede" || action === "reanchor" || action === "split_then_reanchor";
51478
- }
51479
- function sourceRefChangedFromPreparedSupport(input) {
51480
- const proposedSourceRef = input.decision.proposed?.source_ref;
51481
- const preparedSourceRef = input.item?.source_support?.source_ref;
51482
- return typeof proposedSourceRef === "string" && typeof preparedSourceRef === "string" && proposedSourceRef !== preparedSourceRef;
51483
- }
51484
- function supportKind(decision, item) {
51485
- if (typeof decision.proposed?.kind === "string")
51486
- return decision.proposed.kind;
51487
- const target = decision.target ?? item?.target;
51488
- if (target?.section_id !== undefined) {
51489
- const targetCandidate = item?.candidates.find((candidate) => candidate.node === target.node && candidate.section_id === target.section_id);
51490
- if (targetCandidate !== undefined)
51491
- return targetCandidate.kind;
51492
- }
51493
- return typeof item?.proposed?.kind === "string" ? item.proposed.kind : undefined;
51494
- }
51495
- function sourceSupportIssue(input) {
51496
- const action = input.decision.action;
51497
- const strict = actionRequiresStrictSourceSupport(action);
51498
- const splitCandidates = input.support?.evidence_block_candidates?.slice(0, 6).map((candidate) => `${candidate.source_ref} (${candidate.block_locator_id})`).join("; ");
51499
- 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.";
51500
- const splitHint = splitCandidates !== undefined && splitCandidates.length > 0 ? ` Candidate evidence blocks for split decisions: ${splitCandidates}.` : "";
51501
- const splitCandidateCount = input.support?.evidence_block_candidates?.length ?? 0;
51502
- return {
51503
- path: `${input.decision.item_id}.proposed.source_ref`,
51504
- message: strict ? `${action} requires direct support from cited evidence before apply: ${formatSupportDiagnostic(input.diagnostic)}. ${repairHint}${splitHint}` : `proposed keep_separate is not supported by its cited raw text: ${formatSupportDiagnostic(input.diagnostic)}. ${repairHint}${splitHint}`,
51505
- severity: "error",
51506
- ...splitCandidateCount > 1 ? { code: "source-support-split-by-evidence-blocks" } : {}
51507
- };
51508
- }
51509
- function weakSupportWarning(input) {
51510
- return {
51511
- path: `${input.decision.item_id}.proposed.source_ref`,
51512
- item_id: input.decision.item_id,
51513
- severity: "warning",
51514
- code: "weak-source-support",
51515
- message: `proposed keep_separate is weakly supported by its cited raw text: ${formatSupportDiagnostic(input.diagnostic)}.`,
51516
- next_action: "Review the cited evidence if the summary looks surprising; missing hard facts still remain errors."
51517
- };
51518
- }
51519
- function citedTextForDecision2(decision, item) {
51520
- const sourceRef = decision.proposed?.source_ref;
51521
- const itemSupport = item?.source_support;
51522
- if (typeof sourceRef === "string" && itemSupport?.source_ref === sourceRef && typeof itemSupport.cited_text === "string") {
51523
- return itemSupport.cited_text;
51524
- }
51525
- return (item?.evidence ?? []).filter((span) => span.source_ref === undefined || span.source_ref === sourceRef).map((span) => span.quote).filter((quote) => typeof quote === "string" && quote.trim().length > 0).join(`
51526
- `);
51527
- }
51528
- function needsExampleDetailPreservationQuestion(input) {
51529
- if (input.decision.user_confirmation?.required === true || input.decision.action === "ask_user")
51530
- return false;
51531
- if (input.decision.decided_by === "user")
51532
- return false;
51533
- if (!actionNeedsSourceSupport2(input.decision.action))
51534
- return false;
51535
- if (supportKind(input.decision, input.item) !== "example")
51536
- return false;
51537
- if (!FENCED_CODE_RE2.test(citedTextForDecision2(input.decision, input.item)))
51538
- return false;
51539
- return !FENCED_CODE_RE2.test(input.decision.proposed?.detail ?? "");
51540
- }
51541
- function exampleDetailPreservationQuestion(input) {
51542
- return {
51543
- question_id: `q-${String(input.questionIndex + 1).padStart(3, "0")}`,
51544
- item_id: input.decision.item_id,
51545
- type: "example_detail_preservation",
51546
- ...input.summary !== undefined ? { candidate_summary: { active: input.summary.active, archive: input.summary.archive } } : {},
51547
- ...input.decision.proposed?.content !== undefined ? { proposed_content: input.decision.proposed.content } : {},
51548
- prompt: "The cited raw evidence contains a fenced code/config/command block, but this example decision does not preserve it in proposed.detail. " + "Add the relevant fenced block to proposed.detail 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."
51549
- };
51550
- }
51551
52000
  function itemRequiresDecision(item) {
51552
52001
  return item.status === undefined || item.status === "pending";
51553
52002
  }
@@ -51582,7 +52031,7 @@ function reviewSemanticDecisions(input) {
51582
52031
  candidateSummary
51583
52032
  });
51584
52033
  }
51585
- const preserved = preservePreparedDetails({
52034
+ const preserved = preservePreparedOptionalText({
51586
52035
  document: validation.document ?? { schema_version: "1.0", decisions: [] },
51587
52036
  itemById
51588
52037
  });
@@ -51598,7 +52047,11 @@ function reviewSemanticDecisions(input) {
51598
52047
  ...validation.issues.map(semanticValidationIssueToReviewIssue),
51599
52048
  ...preserved.issues,
51600
52049
  ...contextIssues,
51601
- ...itemIdIssues
52050
+ ...itemIdIssues,
52051
+ ...delegatedDecisionModeIssues({
52052
+ decisions: document2.decisions,
52053
+ allowDelegatedDecisions: input.allowDelegatedDecisions
52054
+ })
51602
52055
  ];
51603
52056
  const candidateSummaryByItem = new Map(candidateSummary.map((summary) => [summary.item_id, summary]));
51604
52057
  const leakageReview = applyLeakageReview({ document: document2, itemById });
@@ -51706,41 +52159,16 @@ function reviewSemanticDecisions(input) {
51706
52159
  })
51707
52160
  };
51708
52161
  }
51709
- function sourceSupportFromDiagnostic(input) {
51710
- return {
51711
- source_ref: input.sourceRef,
51712
- cited_text: input.citedText,
51713
- verdict: input.diagnostic.verdict,
51714
- content_term_count: input.diagnostic.contentTermCount,
51715
- matched_term_count: input.diagnostic.matchedTermCount,
51716
- required_term_count: input.diagnostic.requiredTermCount,
51717
- overlap_ratio: input.diagnostic.overlapRatio,
51718
- missing_content_terms: input.diagnostic.missingContentTerms,
51719
- missing_hard_terms: input.diagnostic.missingHardTerms,
51720
- ...input.evidenceBlock !== undefined ? {
51721
- evidence_block_source_ref: input.evidenceBlock.source_ref,
51722
- evidence_block_line_range: input.evidenceBlock.line_range,
51723
- evidence_block_locator_id: input.evidenceBlock.block_locator_id
51724
- } : {},
51725
- ...input.evidenceBlockCandidates !== undefined ? { evidence_block_candidates: input.evidenceBlockCandidates } : {}
51726
- };
51727
- }
51728
- function needsWorkspaceSupportReview(decision, item) {
51729
- if (decision.user_confirmation?.required === true || decision.action === "ask_user")
51730
- return false;
51731
- if (!actionNeedsSourceSupport2(decision.action))
51732
- return false;
51733
- const sourceRef = decision.proposed?.source_ref;
51734
- const content = decision.proposed?.content;
51735
- if (typeof sourceRef !== "string" || typeof content !== "string")
51736
- return false;
51737
- return item?.source_support?.source_ref !== sourceRef || item.source_support.cited_text === undefined;
51738
- }
51739
52162
  async function reviewSemanticDecisionsWithWorkspace(input) {
51740
52163
  const itemById = new Map(input.context.items.map((item) => [item.item_id, item]));
51741
52164
  const validation = validateSemanticDecisionDocument(hydrateRawPreparedDefaults(input.decisions, itemById));
51742
- if (!validation.document)
51743
- return reviewSemanticDecisions({ context: input.context, decisions: input.decisions });
52165
+ if (!validation.document) {
52166
+ return reviewSemanticDecisions({
52167
+ context: input.context,
52168
+ decisions: input.decisions,
52169
+ ...input.allowDelegatedDecisions !== undefined ? { allowDelegatedDecisions: input.allowDelegatedDecisions } : {}
52170
+ });
52171
+ }
51744
52172
  const sources = await loadSources(input.ctxDir);
51745
52173
  const sourceById = new Map(sources.sources.map((source2) => [source2.id, source2]));
51746
52174
  const nodes = await readWorkspaceNodeFiles(input.ctxDir).then(flattenWorkspaceNodes).catch(() => []);
@@ -51768,6 +52196,7 @@ async function reviewSemanticDecisionsWithWorkspace(input) {
51768
52196
  section: {
51769
52197
  kind: supportKind(decision, item),
51770
52198
  content,
52199
+ ...typeof decision.proposed?.summary === "string" || decision.proposed?.summary === null ? { summary: decision.proposed.summary } : {},
51771
52200
  ...typeof decision.proposed?.detail === "string" || decision.proposed?.detail === null ? { detail: decision.proposed.detail } : {},
51772
52201
  source_ref: sourceRef
51773
52202
  }
@@ -51804,7 +52233,8 @@ async function reviewSemanticDecisionsWithWorkspace(input) {
51804
52233
  return reviewSemanticDecisions({
51805
52234
  context: { ...input.context, items: enrichedItems },
51806
52235
  decisions: input.decisions,
51807
- knownSlugs
52236
+ knownSlugs,
52237
+ ...input.allowDelegatedDecisions !== undefined ? { allowDelegatedDecisions: input.allowDelegatedDecisions } : {}
51808
52238
  });
51809
52239
  }
51810
52240
 
@@ -51823,7 +52253,7 @@ function safeDefaultBlockers(item) {
51823
52253
  }
51824
52254
  return [...blockers].sort();
51825
52255
  }
51826
- function isSafeDefaultDecision(item, mode) {
52256
+ function isSafeDefaultDecision(item, mode, delegated) {
51827
52257
  const decision = item.default_decision;
51828
52258
  if (decision === undefined)
51829
52259
  return false;
@@ -51840,7 +52270,7 @@ function isSafeDefaultDecision(item, mode) {
51840
52270
  return false;
51841
52271
  if (decision.user_confirmation?.required === true)
51842
52272
  return false;
51843
- if (item.source_support?.verdict !== "supported")
52273
+ if (item.source_support?.verdict !== "supported" && !(delegated && isDelegatedWeakDefault(item)))
51844
52274
  return false;
51845
52275
  if (decision.target?.section_id !== undefined)
51846
52276
  return false;
@@ -51850,14 +52280,15 @@ function isSafeDefaultDecision(item, mode) {
51850
52280
  return false;
51851
52281
  return true;
51852
52282
  }
51853
- function safeDefaultDecisionDocument(context) {
52283
+ function safeDefaultDecisionDocument(context, options = {}) {
52284
+ const delegated = options.delegated === true;
51854
52285
  return {
51855
52286
  schema_version: "1.0",
51856
52287
  mode: context.mode,
51857
- decisions: context.items.filter((item) => isSafeDefaultDecision(item, context.mode)).map((item) => ({
52288
+ decisions: context.items.filter((item) => isSafeDefaultDecision(item, context.mode, delegated)).map((item) => ({
51858
52289
  ...item.default_decision,
51859
52290
  item_id: item.item_id,
51860
- decided_by: item.default_decision.decided_by === "cli_reviewed_no_write" ? "cli_reviewed_no_write" : "cli_safe_default"
52291
+ decided_by: item.default_decision.decided_by === "cli_reviewed_no_write" ? "cli_reviewed_no_write" : delegated && isDelegatedWeakDefault(item) ? DELEGATED_DECIDED_BY : "cli_safe_default"
51861
52292
  }))
51862
52293
  };
51863
52294
  }
@@ -51865,12 +52296,8 @@ function safeDefaultDecisionDocument(context) {
51865
52296
  // src/reconcile/apply.ts
51866
52297
  init_verify();
51867
52298
  init_shared();
51868
- init_node2();
51869
52299
  init_section();
51870
52300
  init_sources();
51871
- init_knowledge();
51872
- init_compileContext();
51873
- init_compileNode();
51874
52301
  init_coverage();
51875
52302
  init_evidence();
51876
52303
  init_ledger();
@@ -51878,9 +52305,19 @@ import { existsSync as existsSync46 } from "node:fs";
51878
52305
  import { cp as cp3, mkdir as mkdir25, mkdtemp as mkdtemp2, rm as rm14 } from "node:fs/promises";
51879
52306
  import { tmpdir as tmpdir2 } from "node:os";
51880
52307
  import { dirname as dirname22, join as join55 } from "node:path";
51881
- init_sourceSupport();
52308
+
52309
+ // src/reconcile/applyPreflight.ts
52310
+ init_sources();
52311
+ init_shared();
52312
+ init_compileContext();
52313
+
52314
+ // src/reconcile/applyOps.ts
52315
+ init_node2();
52316
+ init_shared();
52317
+ init_knowledge();
52318
+ init_compileNode();
52319
+ init_compileContext();
51882
52320
  init_locatedNodeSources();
51883
- init_types3();
51884
52321
  function requireTarget(decision) {
51885
52322
  if (!decision.target?.node || !decision.target.section_id) {
51886
52323
  throw new Error(`${decision.action} requires target.node and target.section_id`);
@@ -51898,7 +52335,9 @@ function sectionInput(proposed) {
51898
52335
  }
51899
52336
  return {
51900
52337
  kind: proposed.kind,
52338
+ ...typeof proposed.summary === "string" ? { summary: proposed.summary } : {},
51901
52339
  content: proposed.content,
52340
+ ...typeof proposed.raw === "string" ? { raw: proposed.raw } : {},
51902
52341
  source_ref: proposed.source_ref,
51903
52342
  ...typeof proposed.detail === "string" ? { detail: proposed.detail } : {},
51904
52343
  ...typeof proposed.confidence === "string" ? { confidence: proposed.confidence } : {},
@@ -51909,8 +52348,12 @@ function updatePatch(proposed) {
51909
52348
  if (!proposed)
51910
52349
  throw new Error("merge_update requires proposed payload");
51911
52350
  const patch = {};
52351
+ if (proposed.summary !== undefined)
52352
+ patch.summary = proposed.summary;
51912
52353
  if (proposed.content !== undefined)
51913
52354
  patch.content = proposed.content;
52355
+ if (proposed.raw !== undefined)
52356
+ patch.raw = proposed.raw;
51914
52357
  if (proposed.detail !== undefined)
51915
52358
  patch.detail = proposed.detail;
51916
52359
  if (proposed.confidence !== undefined)
@@ -51921,98 +52364,6 @@ function updatePatch(proposed) {
51921
52364
  patch.refers_to_nodes = proposed.refers_to_nodes;
51922
52365
  return patch;
51923
52366
  }
51924
- function assertMergeUpdatePersisted(input) {
51925
- const mismatches = [];
51926
- if (input.patch.content !== undefined && input.section.content !== input.patch.content) {
51927
- mismatches.push("content");
51928
- }
51929
- if (input.patch.source_ref !== undefined && input.section.source_ref !== input.patch.source_ref) {
51930
- mismatches.push("source_ref");
51931
- }
51932
- if (input.patch.detail !== undefined) {
51933
- const expected = input.patch.detail === null ? undefined : input.patch.detail;
51934
- if (input.section.detail !== expected)
51935
- mismatches.push("detail");
51936
- }
51937
- if (input.patch.confidence !== undefined && input.section.confidence !== input.patch.confidence) {
51938
- mismatches.push("confidence");
51939
- }
51940
- if (mismatches.length > 0) {
51941
- throw new Error(`merge_update did not persist ${mismatches.join(", ")} for ${input.target.node}#${input.target.section_id}`);
51942
- }
51943
- }
51944
- function rejectUnresolved(document2) {
51945
- const unresolved = document2.decisions.filter((decision) => decision.action === "ask_user" || decision.user_confirmation?.required === true);
51946
- if (unresolved.length > 0) {
51947
- throw new Error(`cannot apply unresolved ask_user decisions: ${unresolved.map((decision) => decision.item_id).join(", ")}`);
51948
- }
51949
- }
51950
- function countResolvedQuestions(document2) {
51951
- return document2.decisions.filter((decision) => decision.decided_by === "user").length;
51952
- }
51953
- async function propagateOmitCoverageSkips(input) {
51954
- for (const decision of input.document.decisions) {
51955
- if (decision.action !== "omit")
51956
- continue;
51957
- const sourceRef = decision.proposed?.source_ref;
51958
- if (typeof sourceRef !== "string" || sourceRef.length === 0)
51959
- continue;
51960
- await skipCoverageCandidatesBySourceRefs({
51961
- ctxDir: input.ctxDir,
51962
- ...decision.target?.node !== undefined ? { nodeSlug: decision.target.node } : {},
51963
- sourceRefs: [sourceRef],
51964
- reason: `propagated from semantic omit decision ${decision.item_id}`,
51965
- ...input.now !== undefined ? { now: input.now } : {}
51966
- });
51967
- }
51968
- }
51969
- async function createApplyRollbackSnapshot(ctxDir) {
51970
- const tmpDir = await mkdtemp2(join55(tmpdir2(), "c4a-reconcile-apply-"));
51971
- const knowledgePath2 = join55(ctxDir, "knowledge");
51972
- const ledgerPath = semanticLedgerPath2(ctxDir);
51973
- const knowledgeBackup = join55(tmpDir, "knowledge");
51974
- const ledgerBackup = join55(tmpDir, "semantic.yaml");
51975
- const knowledgeExisted = existsSync46(knowledgePath2);
51976
- const ledgerExisted = existsSync46(ledgerPath);
51977
- if (knowledgeExisted)
51978
- await cp3(knowledgePath2, knowledgeBackup, { recursive: true });
51979
- if (ledgerExisted)
51980
- await cp3(ledgerPath, ledgerBackup);
51981
- return { ctxDir, tmpDir, knowledgeExisted, ledgerExisted, knowledgeBackup, ledgerBackup };
51982
- }
51983
- async function restoreApplyRollbackSnapshot(snapshot) {
51984
- const knowledgePath2 = join55(snapshot.ctxDir, "knowledge");
51985
- if (snapshot.knowledgeExisted) {
51986
- await rm14(knowledgePath2, { recursive: true, force: true });
51987
- await cp3(snapshot.knowledgeBackup, knowledgePath2, { recursive: true });
51988
- } else {
51989
- await rm14(knowledgePath2, { recursive: true, force: true });
51990
- }
51991
- const ledgerPath = semanticLedgerPath2(snapshot.ctxDir);
51992
- if (snapshot.ledgerExisted) {
51993
- await mkdir25(dirname22(ledgerPath), { recursive: true });
51994
- await cp3(snapshot.ledgerBackup, ledgerPath);
51995
- } else {
51996
- await rm14(ledgerPath, { force: true });
51997
- }
51998
- }
51999
- async function discardApplyRollbackSnapshot(snapshot) {
52000
- await rm14(snapshot.tmpDir, { recursive: true, force: true });
52001
- }
52002
- function errorMessage(err2) {
52003
- return err2 instanceof Error ? err2.message : String(err2);
52004
- }
52005
- function isRecord22(value) {
52006
- return typeof value === "object" && value !== null && !Array.isArray(value);
52007
- }
52008
- function applyInputDocument(input) {
52009
- if (!isRecord22(input) || !("apply_document" in input))
52010
- return input;
52011
- if (input.ready_to_apply !== true) {
52012
- throw new Error("cannot apply review output that is not ready_to_apply");
52013
- }
52014
- return input.apply_document;
52015
- }
52016
52367
  function findNode(nodes, nodeSlug, action) {
52017
52368
  const node2 = nodes.find((candidate) => candidate.parsed.node.id === nodeSlug);
52018
52369
  if (!node2)
@@ -52050,76 +52401,14 @@ function findTargetSection(node2, sectionId, action) {
52050
52401
  throw new Error(`${action} target section "${node2.parsed.node.id}#${sectionId}" does not exist`);
52051
52402
  return section;
52052
52403
  }
52053
- function snapshotSourceRef2(value) {
52054
- return isRecord22(value) && typeof value.source_ref === "string" ? value.source_ref : undefined;
52055
- }
52056
- function evidenceRefsForDecision(input) {
52057
- const node2 = input.decision.target?.node;
52058
- if (!node2)
52059
- return [];
52060
- if (input.decision.action === "split_then_reanchor") {
52061
- return (input.decision.proposed?.sections ?? []).map((section) => ({ node: node2, sourceRef: section.source_ref, kind: "proposed" }));
52062
- }
52063
- const proposedRef = input.decision.proposed?.source_ref;
52064
- if (typeof proposedRef === "string")
52065
- return [{ node: node2, sourceRef: proposedRef, kind: "proposed" }];
52066
- const targetRef = snapshotSourceRef2(input.targetAfter) ?? snapshotSourceRef2(input.targetBefore);
52067
- return targetRef ? [{
52068
- node: node2,
52069
- sourceRef: targetRef,
52070
- ...input.decision.target?.section_id !== undefined ? { sectionId: input.decision.target.section_id } : {},
52071
- kind: "target"
52072
- }] : [];
52073
- }
52074
- async function collectEvidenceByItem(input) {
52075
- const nodes = flattenWorkspaceNodes(await readWorkspaceNodeFiles(input.ctxDir));
52076
- const sources = await loadSources(input.ctxDir);
52077
- const sourceById = new Map(sources.sources.map((source2) => [source2.id, source2]));
52078
- const out2 = new Map;
52079
- for (const decision of input.document.decisions) {
52080
- const refs = evidenceRefsForDecision({
52081
- decision,
52082
- targetBefore: input.targetBeforeByItem.get(decision.item_id),
52083
- targetAfter: input.targetAfterByItem.get(decision.item_id)
52084
- });
52085
- const spans = [];
52086
- for (const ref of refs) {
52087
- const node2 = decision.action === "omit" ? await findNodeOrCompileTarget(input.ctxDir, nodes, ref.node, decision.action) : findNode(nodes, ref.node, decision.action);
52088
- if (ref.kind === "target" && ref.sectionId !== undefined) {
52089
- spans.push(...await baselineEvidenceSpansForTargetSection({
52090
- ctxDir: input.ctxDir,
52091
- nodeSlug: ref.node,
52092
- sectionId: ref.sectionId,
52093
- nodeSources: node2.parsed.node.sources,
52094
- sourceRef: ref.sourceRef,
52095
- sourceById,
52096
- action: decision.action
52097
- }));
52098
- } else {
52099
- const nodeSources = sourceRefNodeSources({
52100
- sources: node2.parsed.node.sources,
52101
- contextSources: node2.parsed.node.context_sources,
52102
- includeContextSources: decision.action === "omit"
52103
- });
52104
- spans.push(...await evidenceSpansForNodeSourceRef({
52105
- ctxDir: input.ctxDir,
52106
- nodeSources,
52107
- sourceRef: ref.sourceRef,
52108
- sourceById,
52109
- action: decision.action
52110
- }));
52111
- }
52112
- }
52113
- out2.set(decision.item_id, spans);
52114
- }
52115
- return out2;
52116
- }
52117
52404
  function sectionFromProposed(input) {
52118
52405
  return {
52119
52406
  id: input.sectionId,
52120
52407
  anchor_slug: input.node.parsed.node.id,
52121
52408
  kind: input.proposed.kind,
52409
+ ...typeof input.proposed.summary === "string" ? { summary: input.proposed.summary } : {},
52122
52410
  content: input.proposed.content,
52411
+ ...typeof input.proposed.raw === "string" ? { raw: input.proposed.raw } : {},
52123
52412
  source_ref: input.proposed.source_ref,
52124
52413
  status: input.proposed.status ?? SectionStatus.active,
52125
52414
  confidence: input.proposed.confidence ?? "confirmed",
@@ -52129,8 +52418,20 @@ function sectionFromProposed(input) {
52129
52418
  }
52130
52419
  function finalSectionFromPatch(section, patch) {
52131
52420
  const next = { ...section };
52421
+ if (patch.summary !== undefined) {
52422
+ if (patch.summary === null)
52423
+ delete next.summary;
52424
+ else
52425
+ next.summary = patch.summary;
52426
+ }
52132
52427
  if (patch.content !== undefined)
52133
52428
  next.content = patch.content;
52429
+ if (patch.raw !== undefined) {
52430
+ if (patch.raw === null)
52431
+ delete next.raw;
52432
+ else
52433
+ next.raw = patch.raw;
52434
+ }
52134
52435
  if (patch.detail !== undefined) {
52135
52436
  if (patch.detail === null)
52136
52437
  delete next.detail;
@@ -52151,6 +52452,33 @@ function finalSectionFromPatch(section, patch) {
52151
52452
  }
52152
52453
  return next;
52153
52454
  }
52455
+ async function ensureKeepSeparateTargetNode(ctxDir, nodeSlug) {
52456
+ const exists = flattenWorkspaceNodes(await readWorkspaceNodeFiles(ctxDir)).some((candidate) => candidate.parsed.node.id === nodeSlug);
52457
+ if (exists)
52458
+ return;
52459
+ await ensureCompileNodeExists(ctxDir, await nodeGetContext(ctxDir, nodeSlug));
52460
+ }
52461
+ async function ensureNoWriteTargetNode(ctxDir, nodeSlug) {
52462
+ const exists = flattenWorkspaceNodes(await readWorkspaceNodeFiles(ctxDir)).some((candidate) => candidate.parsed.node.id === nodeSlug);
52463
+ if (exists)
52464
+ return;
52465
+ await ensureCompileNoWriteNodeExists(ctxDir, await nodeGetContext(ctxDir, nodeSlug));
52466
+ }
52467
+ function validateSectionForApply(input) {
52468
+ validateMountAndShape(input.node, input.section, input.action);
52469
+ validateRefersToNodes(input.section, input.knownSlugs, input.action);
52470
+ }
52471
+
52472
+ // src/reconcile/applyPreflight.ts
52473
+ init_sourceSupport();
52474
+ async function collectApplyKnownSlugs(ctxDir, nodes) {
52475
+ const slugs = new Set(nodes.map((node2) => node2.parsed.node.id));
52476
+ try {
52477
+ for (const slug of await collectKnownNodeSlugs(ctxDir))
52478
+ slugs.add(slug);
52479
+ } catch {}
52480
+ return slugs;
52481
+ }
52154
52482
  async function preflightSemanticDecisions(input) {
52155
52483
  const nodes = flattenWorkspaceNodes(await readWorkspaceNodeFiles(input.ctxDir));
52156
52484
  const knownSlugs = await collectApplyKnownSlugs(input.ctxDir, nodes);
@@ -52170,8 +52498,7 @@ async function preflightSemanticDecisions(input) {
52170
52498
  throw new Error("merge_update proposed.kind must match the target section kind; use supersede for kind changes");
52171
52499
  }
52172
52500
  const finalSection = finalSectionFromPatch(current, updatePatch(decision.proposed));
52173
- validateMountAndShape(node2, finalSection, decision.action);
52174
- validateRefersToNodes(finalSection, knownSlugs, decision.action);
52501
+ validateSectionForApply({ node: node2, section: finalSection, knownSlugs, action: decision.action });
52175
52502
  await validateSectionSourceSupport({ ctxDir: input.ctxDir, nodeSources: node2.parsed.node.sources, section: finalSection, sourceById, action: decision.action, strict: true });
52176
52503
  continue;
52177
52504
  }
@@ -52180,23 +52507,24 @@ async function preflightSemanticDecisions(input) {
52180
52507
  const node2 = await findNodeWithCurrentSources(input.ctxDir, nodes, target.node, decision.action);
52181
52508
  findTargetSection(node2, target.section_id, decision.action);
52182
52509
  const section = sectionFromProposed({ node: node2, sectionId: "section-1", proposed: sectionInput(decision.proposed) });
52183
- validateMountAndShape(node2, section, decision.action);
52184
- validateRefersToNodes(section, knownSlugs, decision.action);
52510
+ validateSectionForApply({ node: node2, section, knownSlugs, action: decision.action });
52185
52511
  await validateSectionSourceSupport({ ctxDir: input.ctxDir, nodeSources: node2.parsed.node.sources, section, sourceById, action: decision.action, strict: true });
52186
52512
  continue;
52187
52513
  }
52188
52514
  if (decision.action === "keep_separate") {
52189
52515
  const node2 = await findNodeOrCompileTarget(input.ctxDir, nodes, requireNode(decision), decision.action);
52190
52516
  const section = sectionFromProposed({ node: node2, sectionId: "section-1", proposed: sectionInput(decision.proposed) });
52191
- validateMountAndShape(node2, section, decision.action);
52192
- validateRefersToNodes(section, knownSlugs, decision.action);
52517
+ validateSectionForApply({ node: node2, section, knownSlugs, action: decision.action });
52193
52518
  await validateSectionSourceSupport({
52194
52519
  ctxDir: input.ctxDir,
52195
52520
  nodeSources: node2.parsed.node.sources,
52196
52521
  section,
52197
52522
  sourceById,
52198
52523
  action: decision.action,
52199
- allowWeak: decision.decided_by === "user"
52524
+ allowWeak: allowsWeakSourceSupport({
52525
+ decision,
52526
+ allowDelegatedDecisions: input.allowDelegatedDecisions
52527
+ })
52200
52528
  });
52201
52529
  continue;
52202
52530
  }
@@ -52211,14 +52539,12 @@ async function preflightSemanticDecisions(input) {
52211
52539
  const target = requireTarget(decision);
52212
52540
  const node2 = await findNodeWithCurrentSources(input.ctxDir, nodes, target.node, decision.action);
52213
52541
  const current = findTargetSection(node2, target.section_id, decision.action);
52214
- const reanchorPatch = {};
52215
- if (decision.proposed?.source_ref !== undefined)
52216
- reanchorPatch.source_ref = decision.proposed.source_ref;
52217
- if (decision.proposed?.confidence !== undefined)
52218
- reanchorPatch.confidence = decision.proposed.confidence;
52542
+ const reanchorPatch = {
52543
+ ...decision.proposed?.source_ref !== undefined ? { source_ref: decision.proposed.source_ref } : {},
52544
+ ...decision.proposed?.confidence !== undefined ? { confidence: decision.proposed.confidence } : {}
52545
+ };
52219
52546
  const finalSection = finalSectionFromPatch(current, reanchorPatch);
52220
- validateMountAndShape(node2, finalSection, decision.action);
52221
- validateRefersToNodes(finalSection, knownSlugs, decision.action);
52547
+ validateSectionForApply({ node: node2, section: finalSection, knownSlugs, action: decision.action });
52222
52548
  await validateSectionSourceSupport({ ctxDir: input.ctxDir, nodeSources: node2.parsed.node.sources, section: finalSection, sourceById, action: decision.action, strict: true });
52223
52549
  continue;
52224
52550
  }
@@ -52235,26 +52561,170 @@ async function preflightSemanticDecisions(input) {
52235
52561
  throw new Error("split_then_reanchor requires proposed.sections");
52236
52562
  for (const [index, proposed] of decision.proposed.sections.entries()) {
52237
52563
  const section = sectionFromProposed({ node: node2, sectionId: `section-${index + 1}`, proposed });
52238
- validateMountAndShape(node2, section, decision.action);
52239
- validateRefersToNodes(section, knownSlugs, decision.action);
52564
+ validateSectionForApply({ node: node2, section, knownSlugs, action: decision.action });
52240
52565
  await validateSectionSourceSupport({ ctxDir: input.ctxDir, nodeSources: node2.parsed.node.sources, section, sourceById, action: decision.action, strict: true });
52241
52566
  }
52242
52567
  }
52243
52568
  }
52244
52569
  }
52245
- async function collectApplyKnownSlugs(ctxDir, nodes) {
52246
- const slugs = new Set(nodes.map((node2) => node2.parsed.node.id));
52247
- try {
52248
- for (const slug of await collectKnownNodeSlugs(ctxDir))
52249
- slugs.add(slug);
52250
- } catch {}
52251
- return slugs;
52570
+
52571
+ // src/reconcile/apply.ts
52572
+ init_types3();
52573
+ function assertMergeUpdatePersisted(input) {
52574
+ const mismatches = [];
52575
+ if (input.patch.content !== undefined && input.section.content !== input.patch.content) {
52576
+ mismatches.push("content");
52577
+ }
52578
+ if (input.patch.source_ref !== undefined && input.section.source_ref !== input.patch.source_ref) {
52579
+ mismatches.push("source_ref");
52580
+ }
52581
+ if (input.patch.detail !== undefined) {
52582
+ const expected = input.patch.detail === null ? undefined : input.patch.detail;
52583
+ if (input.section.detail !== expected)
52584
+ mismatches.push("detail");
52585
+ }
52586
+ if (input.patch.confidence !== undefined && input.section.confidence !== input.patch.confidence) {
52587
+ mismatches.push("confidence");
52588
+ }
52589
+ if (mismatches.length > 0) {
52590
+ throw new Error(`merge_update did not persist ${mismatches.join(", ")} for ${input.target.node}#${input.target.section_id}`);
52591
+ }
52252
52592
  }
52253
- async function ensureKeepSeparateTargetNode(ctxDir, nodeSlug) {
52254
- const exists = flattenWorkspaceNodes(await readWorkspaceNodeFiles(ctxDir)).some((candidate) => candidate.parsed.node.id === nodeSlug);
52255
- if (exists)
52256
- return;
52257
- await ensureCompileNodeExists(ctxDir, await nodeGetContext(ctxDir, nodeSlug));
52593
+ function rejectUnresolved(document2) {
52594
+ const unresolved = document2.decisions.filter((decision) => decision.action === "ask_user" || decision.user_confirmation?.required === true);
52595
+ if (unresolved.length > 0) {
52596
+ throw new Error(`cannot apply unresolved ask_user decisions: ${unresolved.map((decision) => decision.item_id).join(", ")}`);
52597
+ }
52598
+ }
52599
+ function countResolvedQuestions(document2) {
52600
+ return document2.decisions.filter((decision) => decision.decided_by === "user").length;
52601
+ }
52602
+ async function propagateOmitCoverageSkips(input) {
52603
+ for (const decision of input.document.decisions) {
52604
+ if (decision.action !== "omit")
52605
+ continue;
52606
+ const sourceRef = decision.proposed?.source_ref;
52607
+ if (typeof sourceRef !== "string" || sourceRef.length === 0)
52608
+ continue;
52609
+ await skipCoverageCandidatesBySourceRefs({
52610
+ ctxDir: input.ctxDir,
52611
+ ...decision.target?.node !== undefined ? { nodeSlug: decision.target.node } : {},
52612
+ sourceRefs: [sourceRef],
52613
+ reason: `propagated from semantic omit decision ${decision.item_id}`,
52614
+ ...input.now !== undefined ? { now: input.now } : {}
52615
+ });
52616
+ }
52617
+ }
52618
+ async function createApplyRollbackSnapshot(ctxDir) {
52619
+ const tmpDir = await mkdtemp2(join55(tmpdir2(), "c4a-reconcile-apply-"));
52620
+ const knowledgePath2 = join55(ctxDir, "knowledge");
52621
+ const ledgerPath = semanticLedgerPath2(ctxDir);
52622
+ const knowledgeBackup = join55(tmpDir, "knowledge");
52623
+ const ledgerBackup = join55(tmpDir, "semantic.yaml");
52624
+ const knowledgeExisted = existsSync46(knowledgePath2);
52625
+ const ledgerExisted = existsSync46(ledgerPath);
52626
+ if (knowledgeExisted)
52627
+ await cp3(knowledgePath2, knowledgeBackup, { recursive: true });
52628
+ if (ledgerExisted)
52629
+ await cp3(ledgerPath, ledgerBackup);
52630
+ return { ctxDir, tmpDir, knowledgeExisted, ledgerExisted, knowledgeBackup, ledgerBackup };
52631
+ }
52632
+ async function restoreApplyRollbackSnapshot(snapshot) {
52633
+ const knowledgePath2 = join55(snapshot.ctxDir, "knowledge");
52634
+ if (snapshot.knowledgeExisted) {
52635
+ await rm14(knowledgePath2, { recursive: true, force: true });
52636
+ await cp3(snapshot.knowledgeBackup, knowledgePath2, { recursive: true });
52637
+ } else {
52638
+ await rm14(knowledgePath2, { recursive: true, force: true });
52639
+ }
52640
+ const ledgerPath = semanticLedgerPath2(snapshot.ctxDir);
52641
+ if (snapshot.ledgerExisted) {
52642
+ await mkdir25(dirname22(ledgerPath), { recursive: true });
52643
+ await cp3(snapshot.ledgerBackup, ledgerPath);
52644
+ } else {
52645
+ await rm14(ledgerPath, { force: true });
52646
+ }
52647
+ }
52648
+ async function discardApplyRollbackSnapshot(snapshot) {
52649
+ await rm14(snapshot.tmpDir, { recursive: true, force: true });
52650
+ }
52651
+ function errorMessage(err2) {
52652
+ return err2 instanceof Error ? err2.message : String(err2);
52653
+ }
52654
+ function isRecord22(value) {
52655
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52656
+ }
52657
+ function applyInputDocument(input) {
52658
+ if (!isRecord22(input) || !("apply_document" in input))
52659
+ return input;
52660
+ if (input.ready_to_apply !== true) {
52661
+ throw new Error("cannot apply review output that is not ready_to_apply");
52662
+ }
52663
+ return input.apply_document;
52664
+ }
52665
+ function snapshotSourceRef2(value) {
52666
+ return isRecord22(value) && typeof value.source_ref === "string" ? value.source_ref : undefined;
52667
+ }
52668
+ function evidenceRefsForDecision(input) {
52669
+ const node2 = input.decision.target?.node;
52670
+ if (!node2)
52671
+ return [];
52672
+ if (input.decision.action === "split_then_reanchor") {
52673
+ return (input.decision.proposed?.sections ?? []).map((section) => ({ node: node2, sourceRef: section.source_ref, kind: "proposed" }));
52674
+ }
52675
+ const proposedRef = input.decision.proposed?.source_ref;
52676
+ if (typeof proposedRef === "string")
52677
+ return [{ node: node2, sourceRef: proposedRef, kind: "proposed" }];
52678
+ const targetRef = snapshotSourceRef2(input.targetAfter) ?? snapshotSourceRef2(input.targetBefore);
52679
+ return targetRef ? [{
52680
+ node: node2,
52681
+ sourceRef: targetRef,
52682
+ ...input.decision.target?.section_id !== undefined ? { sectionId: input.decision.target.section_id } : {},
52683
+ kind: "target"
52684
+ }] : [];
52685
+ }
52686
+ async function collectEvidenceByItem(input) {
52687
+ const nodes = flattenWorkspaceNodes(await readWorkspaceNodeFiles(input.ctxDir));
52688
+ const sources = await loadSources(input.ctxDir);
52689
+ const sourceById = new Map(sources.sources.map((source2) => [source2.id, source2]));
52690
+ const out2 = new Map;
52691
+ for (const decision of input.document.decisions) {
52692
+ const refs = evidenceRefsForDecision({
52693
+ decision,
52694
+ targetBefore: input.targetBeforeByItem.get(decision.item_id),
52695
+ targetAfter: input.targetAfterByItem.get(decision.item_id)
52696
+ });
52697
+ const spans = [];
52698
+ for (const ref of refs) {
52699
+ const node2 = decision.action === "omit" ? await findNodeOrCompileTarget(input.ctxDir, nodes, ref.node, decision.action) : findNode(nodes, ref.node, decision.action);
52700
+ if (ref.kind === "target" && ref.sectionId !== undefined) {
52701
+ spans.push(...await baselineEvidenceSpansForTargetSection({
52702
+ ctxDir: input.ctxDir,
52703
+ nodeSlug: ref.node,
52704
+ sectionId: ref.sectionId,
52705
+ nodeSources: node2.parsed.node.sources,
52706
+ sourceRef: ref.sourceRef,
52707
+ sourceById,
52708
+ action: decision.action
52709
+ }));
52710
+ } else {
52711
+ const nodeSources = sourceRefNodeSources({
52712
+ sources: node2.parsed.node.sources,
52713
+ contextSources: node2.parsed.node.context_sources,
52714
+ includeContextSources: decision.action === "omit"
52715
+ });
52716
+ spans.push(...await evidenceSpansForNodeSourceRef({
52717
+ ctxDir: input.ctxDir,
52718
+ nodeSources,
52719
+ sourceRef: ref.sourceRef,
52720
+ sourceById,
52721
+ action: decision.action
52722
+ }));
52723
+ }
52724
+ }
52725
+ out2.set(decision.item_id, spans);
52726
+ }
52727
+ return out2;
52258
52728
  }
52259
52729
  async function assertSemanticLedgerAppendable(ctxDir) {
52260
52730
  const ledger = await readSemanticLedger(ctxDir);
@@ -52358,10 +52828,15 @@ async function applySplitThenReanchorPrimitive(input) {
52358
52828
  async function applySemanticDecisions(input) {
52359
52829
  const document2 = parseSemanticDecisionDocument(applyInputDocument(input.document));
52360
52830
  rejectUnresolved(document2);
52831
+ assertDelegatedDecisionsAllowed({ decisions: document2.decisions, allowDelegatedDecisions: input.allowDelegatedDecisions });
52361
52832
  if (input.verify === false && input.recordLedger !== false) {
52362
52833
  throw new Error("semantic ledger cannot be recorded when verify is disabled");
52363
52834
  }
52364
- await preflightSemanticDecisions({ ctxDir: input.ctxDir, document: document2 });
52835
+ await preflightSemanticDecisions({
52836
+ ctxDir: input.ctxDir,
52837
+ document: document2,
52838
+ allowDelegatedDecisions: input.allowDelegatedDecisions
52839
+ });
52365
52840
  if (input.recordLedger !== false) {
52366
52841
  await assertSemanticLedgerAppendable(input.ctxDir);
52367
52842
  }
@@ -52438,6 +52913,9 @@ async function applySemanticDecisions(input) {
52438
52913
  continue;
52439
52914
  }
52440
52915
  if (decision.action === "omit") {
52916
+ if (decision.decided_by === "cli_reviewed_no_write" && decision.target?.node !== undefined) {
52917
+ await ensureNoWriteTargetNode(input.ctxDir, decision.target.node);
52918
+ }
52441
52919
  result.omitted += 1;
52442
52920
  continue;
52443
52921
  }
@@ -53296,8 +53774,8 @@ function registerReconcileCommand(program2) {
53296
53774
  " relation: <exact_duplicate|strong_equivalent|complement|conflicts|unsupported>",
53297
53775
  " action: <duplicate_skip|merge_update|keep_separate|ask_user|remove_unsupported>",
53298
53776
  " target: { node: <slug>, section_id: <section-id> }",
53299
- " proposed: { kind: <section-kind>, content: <single-line claim>, source_ref: <src-N#anchor Lx-y@hash> }",
53300
- "For examples/code blocks, keep proposed.content short and put long prose or fenced code in proposed.detail.",
53777
+ " proposed: { kind: <section-kind>, content: <section text>, summary: <optional short summary>, source_ref: <src-N#anchor Lx-y@hash> }",
53778
+ "For long examples/code blocks, keep fenced code in proposed.content and add proposed.summary when a short reader/query aid is useful.",
53301
53779
  "Compile draft cites raw via source_ref/source_refs copied from raw_snippets[].source_ref.",
53302
53780
  "Review reads the current prepare workflow payload by default; --prepare-digest is only a stale guard.",
53303
53781
  "When several node-scoped prepare payloads exist, pass --scope <node-run-scope> from workflow show/status.",
@@ -53318,6 +53796,7 @@ function registerReconcileCommand(program2) {
53318
53796
  throw new Error("reconcile review requires --decisions - unless --accept-safe-defaults is used");
53319
53797
  }
53320
53798
  const currentForPrepare = await readReadyCurrentWorkflow(ctxDir, "reconcile review");
53799
+ const delegatedWorkflow = isDelegatedWorkflow(currentForPrepare);
53321
53800
  const requestedScope = typeof options.scope === "string" ? options.scope : workflowScope(currentForPrepare);
53322
53801
  const preparePayload = await readWorkflowPayloadByDigest({
53323
53802
  ctxDir,
@@ -53341,14 +53820,15 @@ function registerReconcileCommand(program2) {
53341
53820
  if (reviewScope !== undefined)
53342
53821
  await assertReconcileWorkflowWritable({ ctxDir, mode: reconcileContext.mode, scope: reviewScope });
53343
53822
  const explicitDecisions = decisionsInput === undefined ? undefined : await readStructuredInput(resolveStdinOnlyInput(decisionsInput, "--decisions"));
53344
- const decisions = options.acceptSafeDefaults === true ? explicitDecisions === undefined ? safeDefaultDecisionDocument(reconcileContext) : mergeSafeDefaultDecisions({
53345
- safeDefaults: safeDefaultDecisionDocument(reconcileContext),
53823
+ const decisions = options.acceptSafeDefaults === true ? explicitDecisions === undefined ? safeDefaultDecisionDocument(reconcileContext, { delegated: delegatedWorkflow }) : mergeSafeDefaultDecisions({
53824
+ safeDefaults: safeDefaultDecisionDocument(reconcileContext, { delegated: delegatedWorkflow }),
53346
53825
  explicit: explicitDecisions
53347
53826
  }) : explicitDecisions;
53348
53827
  let result = await reviewSemanticDecisionsWithWorkspace({
53349
53828
  ctxDir,
53350
53829
  context: reconcileContext,
53351
- decisions
53830
+ decisions,
53831
+ allowDelegatedDecisions: delegatedWorkflow
53352
53832
  });
53353
53833
  result = { mode: reconcileContext.mode, ...result };
53354
53834
  const workflowState = reviewScope === undefined ? undefined : await ensureReconcileWorkflow({
@@ -53408,6 +53888,7 @@ function registerReconcileCommand(program2) {
53408
53888
  run: async (ctx) => {
53409
53889
  const ctxDir = requireWorkspace2(ctx.ctxDir, "reconcile apply");
53410
53890
  const currentArtifact = await readUniqueReadyReviewArtifactForCurrent(ctxDir);
53891
+ const currentWorkflow = await readReadyCurrentWorkflow(ctxDir, "reconcile apply");
53411
53892
  const documentInput = currentArtifact.artifact;
53412
53893
  const mode = semanticApplyMode(documentInput);
53413
53894
  const applyScope = mode !== undefined ? {
@@ -53418,7 +53899,8 @@ function registerReconcileCommand(program2) {
53418
53899
  await assertReconcileWorkflowWritable({ ctxDir, mode, scope: applyScope });
53419
53900
  const result = await applySemanticDecisions({
53420
53901
  ctxDir,
53421
- document: documentInput
53902
+ document: documentInput,
53903
+ allowDelegatedDecisions: isDelegatedWorkflow(currentWorkflow)
53422
53904
  });
53423
53905
  if (mode !== undefined && applyScope !== undefined) {
53424
53906
  await advanceReconcileWorkflow({
@@ -54747,6 +55229,7 @@ var DETAIL_OUTPUT_CHARS_PER_TOKEN = 4;
54747
55229
  var DEFAULT_DETAIL_TOKEN_BUDGET = 1200;
54748
55230
  var SUMMARY_PREVIEW_LIMIT = 4;
54749
55231
  var WINDOW_PREVIEW_LIMIT = 1;
55232
+ var SOURCE_WINDOW_SELECTOR_RE = /^(src-[1-9]\d*):([1-9]\d*)$/u;
54750
55233
  function blockSignalLabels(block, textPreview) {
54751
55234
  const haystack = [
54752
55235
  typeof textPreview === "string" ? textPreview : "",
@@ -54826,6 +55309,33 @@ function alignWindowRows(value) {
54826
55309
  };
54827
55310
  }));
54828
55311
  }
55312
+ function windowSelectorOptions(windows) {
55313
+ const sourceOrdinals = new Map;
55314
+ return windows.map((window2) => {
55315
+ const ordinal = (sourceOrdinals.get(window2.source_alias) ?? 0) + 1;
55316
+ sourceOrdinals.set(window2.source_alias, ordinal);
55317
+ return {
55318
+ selector: `${window2.source_alias}:${ordinal}`,
55319
+ window_id: window2.window_id,
55320
+ source_alias: window2.source_alias,
55321
+ source_id: window2.source_id,
55322
+ heading_path: window2.heading_path,
55323
+ ...window2.block_range !== undefined ? { block_range: window2.block_range } : {},
55324
+ ...window2.block_count !== undefined ? { block_count: window2.block_count } : {}
55325
+ };
55326
+ });
55327
+ }
55328
+ function resolveWindowSelector(windows, selector) {
55329
+ const exact = windows.find((window2) => window2.window_id === selector);
55330
+ if (exact !== undefined)
55331
+ return exact;
55332
+ const match = SOURCE_WINDOW_SELECTOR_RE.exec(selector);
55333
+ if (match === null)
55334
+ return;
55335
+ const [, sourceAlias3, rawOrdinal] = match;
55336
+ const ordinal = Number(rawOrdinal);
55337
+ return windows.filter((window2) => window2.source_alias === sourceAlias3)[ordinal - 1];
55338
+ }
54829
55339
  function windowSummaryRow(window2) {
54830
55340
  const row = { ...window2 };
54831
55341
  delete row.block_ids;
@@ -55033,12 +55543,14 @@ function compactAlignSegmentBlocks(record, value, options) {
55033
55543
  if (!hasAlignBlockDrilldown(options))
55034
55544
  return compactAlignSegmentBlocksSummary(record, value);
55035
55545
  const windows = alignWindowRows(value);
55036
- const selectedWindow = typeof options.windowId === "string" ? windows.find((window2) => window2.window_id === options.windowId) : undefined;
55546
+ const selectedWindow = typeof options.windowId === "string" ? resolveWindowSelector(windows, options.windowId) : undefined;
55037
55547
  if (typeof options.windowId === "string" && selectedWindow === undefined) {
55038
55548
  throw new ContextError(ExitCode.UserError, `workflow blocks window "${options.windowId}" was not found`, {
55039
55549
  category: ErrorCategory.UserInputInvalid,
55040
55550
  window: options.windowId,
55041
- available_windows: windows.map((window2) => window2.window_id).filter((item) => typeof item === "string")
55551
+ accepted_window_selectors: ["window_id", "src-N:M"],
55552
+ available_windows: windows.map((window2) => window2.window_id).filter((item) => typeof item === "string"),
55553
+ available_window_selectors: windowSelectorOptions(windows)
55042
55554
  });
55043
55555
  }
55044
55556
  const windowBlockIds = selectedWindow === undefined ? undefined : new Set(selectedWindow.block_ids);
@@ -55091,7 +55603,9 @@ function compactAlignSegmentBlocks(record, value, options) {
55091
55603
  };
55092
55604
  }
55093
55605
  function compactAlignSegmentWindows(value, options) {
55094
- const windows = alignWindowRows(value).filter((window2) => (options.sourceId === undefined || window2.source_id === options.sourceId) && (options.windowId === undefined || window2.window_id === options.windowId) && headingMatches(window2.heading_path, options.heading));
55606
+ const allWindows = alignWindowRows(value);
55607
+ const selectedWindow = typeof options.windowId === "string" ? resolveWindowSelector(allWindows, options.windowId) : undefined;
55608
+ const windows = allWindows.filter((window2) => (options.sourceId === undefined || window2.source_id === options.sourceId) && (options.windowId === undefined || window2.window_id === selectedWindow?.window_id) && headingMatches(window2.heading_path, options.heading));
55095
55609
  const detail = options.windowId !== undefined;
55096
55610
  const rows = detail ? windows : windows.map(windowSummaryRow);
55097
55611
  return {
@@ -55937,7 +56451,7 @@ function workflowPayloadViewGuide(record) {
55937
56451
  }));
55938
56452
  const payloadSpecificCommands = record.payload === "align-segments" ? {
55939
56453
  content_slice: workflowPayloadShowCommand(record, ["--view blocks --token-budget 1200", "--unwrap", "--format json"]),
55940
- block_window: workflowPayloadShowCommand(record, ["--view blocks --window <window-id>", "--unwrap", "--format json"]),
56454
+ block_window: workflowPayloadShowCommand(record, ["--view blocks --window <window-id|src-N:M>", "--unwrap", "--format json"]),
55941
56455
  block_heading: workflowPayloadShowCommand(record, ["--view blocks --heading <heading-prefix>", "--unwrap", "--format json"]),
55942
56456
  block_range: workflowPayloadShowCommand(record, ["--view blocks --range 1:20", "--unwrap", "--format json"]),
55943
56457
  ledger_status: "context workflow show --payload align-candidate-ledger --view ledger --status <status> --unwrap --format json",
@@ -56064,7 +56578,7 @@ function registerWorkflowStateCommands(program2) {
56064
56578
  }
56065
56579
  });
56066
56580
  });
56067
- workflow.command("show").description("Read a workflow payload by name").requiredOption("--payload <name>", "payload name, for example node-context or prepare").option("--scope <scope>", "workflow scope id; defaults to active node/source/workspace").option("--digest <digest>", "expected payload digest").option("--metadata-only", "print workflow/payload metadata without the payload value").option("--digest-only", "print only the payload digest").option("--unwrap", "print only the payload value or compact view, without workflow metadata wrapper").option("--view <view>", "compact payload view: summary | segment | blocks | windows | ledger | aggregate | structure-decision | coverage | source-refs | unowned | by-node | issues").option("--source <source-id>", "narrow compact views to one source id").option("--window <window-id>", "narrow align-segments views to one window id").option("--heading <heading-prefix>", "narrow compact views to a heading path prefix or heading text").option("--range <start:end>", "narrow ordinal-based compact views to an inclusive range").option("--node <slug>", "narrow node-oriented compact views to one node slug").option("--status <status>", "narrow compact views by view-specific status").option("--candidate-id <id>", "narrow candidate-oriented compact views to one candidate id").option("--item-id <id>", "narrow item-oriented compact views to one item id").option("--page-size <n>", "limit detail rows for large filtered views").option("--page-token <offset>", "continue a paged detail view from an offset returned as next_token").option("--token-budget <n>", "limit text-rich detail views by approximate source token budget").option("--format <format>", "output format: json | yaml | text", "json").action(async (options) => {
56581
+ workflow.command("show").description("Read a workflow payload by name").requiredOption("--payload <name>", "payload name, for example node-context or prepare").option("--scope <scope>", "workflow scope id; defaults to active node/source/workspace").option("--digest <digest>", "expected payload digest").option("--metadata-only", "print workflow/payload metadata without the payload value").option("--digest-only", "print only the payload digest").option("--unwrap", "print only the payload value or compact view, without workflow metadata wrapper").option("--view <view>", "compact payload view: summary | segment | blocks | windows | ledger | aggregate | structure-decision | coverage | source-refs | unowned | by-node | issues").option("--source <source-id>", "narrow compact views to one source id").option("--window <window-selector>", "narrow align-segments views to one window id or src-N:M alias").option("--heading <heading-prefix>", "narrow compact views to a heading path prefix or heading text").option("--range <start:end>", "narrow ordinal-based compact views to an inclusive range").option("--node <slug>", "narrow node-oriented compact views to one node slug").option("--status <status>", "narrow compact views by view-specific status").option("--candidate-id <id>", "narrow candidate-oriented compact views to one candidate id").option("--item-id <id>", "narrow item-oriented compact views to one item id").option("--page-size <n>", "limit detail rows for large filtered views").option("--page-token <offset>", "continue a paged detail view from an offset returned as next_token").option("--token-budget <n>", "limit text-rich detail views by approximate source token budget").option("--format <format>", "output format: json | yaml | text", "json").action(async (options) => {
56068
56582
  await runWithPrelude({
56069
56583
  name: "workflow show",
56070
56584
  stateRequirement: "state-required",
@@ -56522,8 +57036,8 @@ function plannedSectionSourceRefGroups(context, rows) {
56522
57036
  op: "add",
56523
57037
  kind: plan.section_kind,
56524
57038
  content: "<write supported section content>",
56525
- source_refs: sourceRefs,
56526
- basis_spans: sourceRefs.map((source_ref) => ({ source_ref }))
57039
+ summary: "<optional short summary when content is long>",
57040
+ source_refs: sourceRefs
56527
57041
  }
56528
57042
  };
56529
57043
  });
@@ -58247,8 +58761,7 @@ function blockSourceIds(sources) {
58247
58761
  function mergeSectionPlans(input) {
58248
58762
  const currentSourceIds = new Set(input.current.sources.map((source2) => source2.source_id));
58249
58763
  const previousBlockSource = blockSourceIds(input.previous.sources);
58250
- const currentSectionIds = new Set((input.current.section_plans ?? []).map((plan) => plan.section_id));
58251
- const previous = (input.previous.section_plans ?? []).filter((plan) => input.activeSlugs.has(plan.owner) && !currentSectionIds.has(plan.section_id) && !plan.block_ids.some((blockId) => currentSourceIds.has(previousBlockSource.get(blockId) ?? "")));
58764
+ const previous = (input.previous.section_plans ?? []).filter((plan) => input.activeSlugs.has(plan.owner) && !plan.block_ids.some((blockId) => currentSourceIds.has(previousBlockSource.get(blockId) ?? "")));
58252
58765
  const current = (input.current.section_plans ?? []).filter((plan) => input.activeSlugs.has(plan.owner));
58253
58766
  return [...previous, ...current];
58254
58767
  }
@@ -58400,7 +58913,11 @@ var plannedSectionsUniqueConstraint = {
58400
58913
  return [{
58401
58914
  code: "planned-sections-duplicate-kinds",
58402
58915
  path: `nodes[${nodeIndex}].planned_sections`,
58403
- message: `planned_sections must not contain duplicate section kind(s): ${duplicates.join(", ")}`
58916
+ message: `planned_sections is the distinct set of Section kinds planned for this node; remove duplicate kind(s): ${duplicates.join(", ")}`,
58917
+ diagnostics: {
58918
+ duplicate_kinds: duplicates,
58919
+ next_action: "List each Section kind at most once in nodes[].planned_sections. Do not mirror every sections[] row; sections[] carries per-section detail."
58920
+ }
58404
58921
  }];
58405
58922
  }
58406
58923
  };
@@ -58743,7 +59260,8 @@ function applyAlignOwnershipPatch(input) {
58743
59260
  };
58744
59261
  if (ids.context.length > 0)
58745
59262
  return { ...next, context_sources: ids.context };
58746
- const { context_sources: _contextSources, ...rest } = next;
59263
+ const rest = { ...next };
59264
+ delete rest.context_sources;
58747
59265
  return rest;
58748
59266
  });
58749
59267
  return {
@@ -58912,8 +59430,8 @@ var SCHEMAS = {
58912
59430
  generation_policy: {
58913
59431
  language: "Chinese",
58914
59432
  source: "workspace.language",
58915
- applies_to: ["node.title", "node.summary", "section.content", "section.detail", "user_facing_report"],
58916
- instruction: "Generate knowledge titles, summaries, and user-facing reports in Chinese; keep node.summary concise (target <15 tokens, never >30 tokens); for source-bound Section content/detail, 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."
59433
+ applies_to: ["node.title", "node.summary", "section.summary", "section.content", "user_facing_report"],
59434
+ instruction: "Generate knowledge titles, node summaries, Section summaries, and user-facing reports in Chinese; keep node.summary concise (target <15 tokens, never >30 tokens); 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."
58917
59435
  },
58918
59436
  sources: [{
58919
59437
  source_id: "local:demo",
@@ -59298,6 +59816,8 @@ var SCHEMAS = {
59298
59816
  "Incremental finalize may reference previous active Nodes from nodes[].contains_parent, edges[].from/to, domain_gate.child_refs, and block_ownership owners/visible_to/primary_owner. Do not redeclare an old parent/domain just to attach new children.",
59299
59817
  "sections[].owner is intentionally limited to nodes declared in the current align-structure-decision payload; do not plan new Sections for previous finalized Nodes in an incremental payload.",
59300
59818
  "Existing and previously removed Node slugs cannot change node_type in either incremental or full finalize. context align --scan --full controls replacement/removal semantics, not type-drift bypass. Use a new slug for a different type, or retire the old slug through context drop or explicit structure correction first.",
59819
+ "nodes[].planned_sections is the distinct set of Section kinds expected for that Node. List each kind at most once; do not mirror every sections[] row or repeat example/spec/etc. for multiple Sections.",
59820
+ "Navigation-only or placeholder-only Nodes may intentionally use planned_sections: [] and no owned citation evidence. Keep relation/placeholder blocks context_only or ignored; the align graph preserves structure and compile close materializes an empty placeholder Node when useful.",
59301
59821
  "action_probe is the only place for action qualification booleans; action_gate accepts inference_sources only.",
59302
59822
  "If the current candidate ledger already has action_probe for this action node and the digest matches, finalize may hydrate it. Directly new action nodes must provide action_probe explicitly.",
59303
59823
  "action_gate.inference_sources needs actor, outcome_or_goal, repeatability_or_plan, and answerability. source_type must be one of explicit-block / heading-and-block / ref-node / inferred-from-block. explicit-block and heading-and-block entries may omit rationale; ref-node and inferred-from-block mappings must explain rationale.",
@@ -61043,7 +61563,7 @@ async function runAlignScanCommand(input) {
61043
61563
  blocks: "context workflow show --payload align-segments --view blocks --unwrap --format json",
61044
61564
  windows: "context workflow show --payload align-segments --view windows --unwrap --format json",
61045
61565
  content_slice: "context workflow show --payload align-segments --view blocks --token-budget 1200 --unwrap --format json",
61046
- block_detail: "context workflow show --payload align-segments --view blocks --window <window-id> --unwrap --format json",
61566
+ block_detail: "context workflow show --payload align-segments --view blocks --window <window-id|src-N:M> --unwrap --format json",
61047
61567
  full_payload_explicit: "context workflow show --payload align-segments --unwrap --format json"
61048
61568
  },
61049
61569
  summary: summary3,
@@ -61518,7 +62038,7 @@ function registerAlignWorkflowCommand(program2) {
61518
62038
  "Flow:",
61519
62039
  " context align --scan --format json",
61520
62040
  " context workflow show --payload align-segments --view blocks --unwrap --format json",
61521
- " context workflow show --payload align-segments --view blocks --window <window-id> --unwrap --format json",
62041
+ " context workflow show --payload align-segments --view blocks --window <window-id|src-N:M> --unwrap --format json",
61522
62042
  " context align --coarse-read - --format json",
61523
62043
  " context align --ops - --format json",
61524
62044
  " context workflow show --payload align-candidate-ledger --view ledger --unwrap --format json",
@@ -61717,6 +62237,24 @@ function prepareIssueViewCommand(input) {
61717
62237
  }
61718
62238
  function nodeCycleReviewRequiredHint(input) {
61719
62239
  const command = prepareIssueViewCommand(input);
62240
+ if (input.partialApply !== undefined) {
62241
+ const sourceSupportSuffix = input.sourceSupportStatus === undefined ? "" : ` The remaining set includes ${input.sourceSupportStatus} source_support item(s).`;
62242
+ return {
62243
+ code: "node-cycle-partial-applied-review-required",
62244
+ severity: input.sourceSupportStatus === "unsupported" ? "error" : input.sourceSupportStatus === "weak" ? "warning" : "info",
62245
+ message: `Node-cycle applied ${input.partialApply.applied} active write(s) from ${input.safeDecisionCount ?? 0} safe decision(s); ${input.remainingItems ?? 0} item(s) still need semantic decisions.${sourceSupportSuffix}`,
62246
+ next_action: input.sourceSupportStatus === "unsupported" ? "Inspect the unsupported issue view and repair the draft/source_refs or split the claim before accepting defaults. The already-applied safe subset is recorded; continue only with the remaining items." : input.sourceSupportStatus === "weak" ? "Inspect the weak issue view and confirm the evidence boundary before accepting defaults or asking the user. The already-applied safe subset is recorded; continue only with the remaining items." : "Load the prepare issue view, decide only the remaining items, then run context reconcile review/apply for this scope.",
62247
+ command,
62248
+ diagnostics: {
62249
+ safe_decisions: input.safeDecisionCount ?? 0,
62250
+ active_writes: input.partialApply.applied,
62251
+ skipped: input.partialApply.skipped,
62252
+ omitted: input.partialApply.omitted,
62253
+ remaining_items: input.remainingItems ?? 0,
62254
+ ...input.sourceSupportStatus !== undefined ? { source_support_status: input.sourceSupportStatus } : {}
62255
+ }
62256
+ };
62257
+ }
61720
62258
  if (input.sourceSupportStatus !== undefined) {
61721
62259
  return {
61722
62260
  code: "node-cycle-review-required-source-support",
@@ -61795,13 +62333,15 @@ async function runCompileNodeCycle(input) {
61795
62333
  value: context,
61796
62334
  format: "json"
61797
62335
  });
61798
- const decisions = safeDefaultDecisionDocument(context);
62336
+ const delegatedWorkflow = isDelegatedWorkflow(preparedWorkflow);
62337
+ const decisions = safeDefaultDecisionDocument(context, { delegated: delegatedWorkflow });
61799
62338
  const review = {
61800
62339
  mode: "compile",
61801
62340
  ...await reviewSemanticDecisionsWithWorkspace({
61802
62341
  ctxDir: input.ctxDir,
61803
62342
  context,
61804
- decisions
62343
+ decisions,
62344
+ allowDelegatedDecisions: delegatedWorkflow
61805
62345
  })
61806
62346
  };
61807
62347
  const reviewStage = review.ready_to_apply ? "node_review_ready" : "node_prepared";
@@ -61834,7 +62374,8 @@ async function runCompileNodeCycle(input) {
61834
62374
  try {
61835
62375
  partialApply = await applySemanticDecisions({
61836
62376
  ctxDir: input.ctxDir,
61837
- document: review.apply_document
62377
+ document: review.apply_document,
62378
+ allowDelegatedDecisions: delegatedWorkflow
61838
62379
  });
61839
62380
  } catch (error) {
61840
62381
  throw new ContextError(ExitCode.WorkspaceStateError, `node-cycle partial apply failed after safe-default review: ${errorMessage2(error)}`, {
@@ -61863,7 +62404,7 @@ async function runCompileNodeCycle(input) {
61863
62404
  review: workflowPayloadReceipt(reviewPayload),
61864
62405
  safe_defaults: {
61865
62406
  auto_decided: decisions.decisions.length,
61866
- auto_applied: partialApplied ? partialDecisionCount : 0,
62407
+ auto_applied: partialApply?.applied ?? 0,
61867
62408
  remaining_items: remaining,
61868
62409
  applied: partialApplied,
61869
62410
  complete: false
@@ -61876,7 +62417,12 @@ async function runCompileNodeCycle(input) {
61876
62417
  next_command: issueViewCommand,
61877
62418
  agent_hints: [nodeCycleReviewRequiredHint({
61878
62419
  preparePayload,
61879
- ...supportStatus !== undefined ? { sourceSupportStatus: supportStatus } : {}
62420
+ ...supportStatus !== undefined ? { sourceSupportStatus: supportStatus } : {},
62421
+ ...partialApply !== undefined ? {
62422
+ partialApply,
62423
+ safeDecisionCount: partialDecisionCount,
62424
+ remainingItems: remaining
62425
+ } : {}
61880
62426
  })]
61881
62427
  };
61882
62428
  return input.format === "json" ? `${JSON.stringify(output2, null, 2)}
@@ -61906,7 +62452,8 @@ async function runCompileNodeCycle(input) {
61906
62452
  try {
61907
62453
  apply = await applySemanticDecisions({
61908
62454
  ctxDir: input.ctxDir,
61909
- document: artifact.artifact
62455
+ document: artifact.artifact,
62456
+ allowDelegatedDecisions: delegatedWorkflow
61910
62457
  });
61911
62458
  } catch (error) {
61912
62459
  throw new ContextError(ExitCode.WorkspaceStateError, `node-cycle apply failed after review became ready: ${errorMessage2(error)}`, {
@@ -61948,7 +62495,7 @@ async function runCompileNodeCycle(input) {
61948
62495
  },
61949
62496
  safe_defaults: {
61950
62497
  auto_decided: decisions.decisions.length,
61951
- auto_applied: decisions.decisions.length,
62498
+ auto_applied: apply.applied,
61952
62499
  remaining_items: 0,
61953
62500
  applied: true,
61954
62501
  complete: true
@@ -62052,7 +62599,7 @@ function rejectRetiredDraftPatchFields(patchInput, slug) {
62052
62599
  code: "compile-draft-patch-retired-field",
62053
62600
  severity: "error",
62054
62601
  message: first.message,
62055
- next_action: "Patch actions must use body plus source_refs[]; remove retired content/detail and extractive fields.",
62602
+ next_action: "Patch actions must use content plus optional summary and source_refs[]; remove retired body/detail/raw and extractive fields.",
62056
62603
  command: `context compile --draft-patch ${slug} --input - --plan`,
62057
62604
  target_node: slug,
62058
62605
  path: first.path,
@@ -62078,7 +62625,7 @@ init_prepare();
62078
62625
  init_exitCode();
62079
62626
  init_currentWorkflow();
62080
62627
  init_workflowPayloadStore();
62081
- function prepareReviewNextCommand(payload) {
62628
+ function prepareReviewNextCommand() {
62082
62629
  return [
62083
62630
  "context reconcile review",
62084
62631
  "--decisions -",
@@ -62091,7 +62638,7 @@ function withPayloadMeta2(value, payload) {
62091
62638
  return {
62092
62639
  ...value,
62093
62640
  workflow_payload: workflowPayloadReceipt(payload, {
62094
- nextCommand: prepareReviewNextCommand(payload)
62641
+ nextCommand: prepareReviewNextCommand()
62095
62642
  })
62096
62643
  };
62097
62644
  }
@@ -62143,7 +62690,7 @@ async function prepareCompileDraftWorkflow(input) {
62143
62690
  `prepare payload digest: ${preparePayload.digest} (optional stale guard)`,
62144
62691
  `prepare payload show: ${workflowPayloadShowCommand(preparePayload)}`
62145
62692
  ],
62146
- next: `run context:skill-compile-judge with the prepare payload, then ${prepareReviewNextCommand(preparePayload)}`
62693
+ next: `run context:skill-compile-judge with the prepare payload, then ${prepareReviewNextCommand()}`
62147
62694
  });
62148
62695
  }
62149
62696
 
@@ -62458,6 +63005,8 @@ function coverageSkipMissingOptionsError(missingFields, mode = "single") {
62458
63005
  });
62459
63006
  }
62460
63007
  function compileStageForOptions(options) {
63008
+ if (options.scanChanges === true && options.delegated === true)
63009
+ return "planned";
62461
63010
  if (typeof options.nodeCycle === "string")
62462
63011
  return "node_draft_ready";
62463
63012
  if (typeof options.context === "string")
@@ -62480,7 +63029,7 @@ function compileScopeForOptions(options) {
62480
63029
  function registerWorkflowCommands(program2) {
62481
63030
  registerWorkflowStateCommands(program2);
62482
63031
  registerAlignWorkflowCommand(program2);
62483
- 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("--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) => {
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) => {
62484
63033
  if (options.scan === true) {
62485
63034
  throw new ContextError(ExitCode.UserError, "context compile --scan was removed; use context compile --scan-changes", {
62486
63035
  category: ErrorCategory.UserInputInvalid,
@@ -62496,7 +63045,7 @@ function registerWorkflowCommands(program2) {
62496
63045
  const wantsChanges = options.scanChanges === true;
62497
63046
  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);
62498
63047
  if (actionCount !== 1) {
62499
- throw new ContextError(ExitCode.UserError, "usage: context compile --scan-changes | --context <slug> | --source-refs <slug> | --node-cycle <slug> --input - --accept-safe-defaults | --draft <slug> --input - --plan [--prepare] | --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 });
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 });
62500
63049
  }
62501
63050
  const viewWasExplicit = typeof options.view === "string";
62502
63051
  const view = compileOutputView(options.view);
@@ -62525,6 +63074,20 @@ function registerWorkflowCommands(program2) {
62525
63074
  }
62526
63075
  const requestFullTextBlockIds = ignoredSourceIds(options.requestFullText);
62527
63076
  const coverUncoveredOnly = options.coverUncoveredOnly === true;
63077
+ const delegated = options.delegated === true;
63078
+ const delegatedAllowed = wantsChanges || typeof options.context === "string" || typeof options.nodeCycle === "string" || typeof options.draft === "string";
63079
+ if (delegated && !delegatedAllowed) {
63080
+ throw new ContextError(ExitCode.UserError, "--delegated is only valid when starting or advancing a compile workflow", {
63081
+ category: ErrorCategory.UserInputInvalid,
63082
+ agent_hints: [{
63083
+ code: "compile-delegated-workflow-entry-only",
63084
+ severity: "error",
63085
+ message: "--delegated records conversation-level user authorization on the compile workflow; it is not a per-command override.",
63086
+ next_action: "Use --delegated only on the initial context compile --scan-changes, --context, --draft, or --node-cycle command after explicit user authorization.",
63087
+ command: "context compile --scan-changes --delegated --format json"
63088
+ }]
63089
+ });
63090
+ }
62528
63091
  assertNodeCycleOptions({
62529
63092
  slug: options.nodeCycle,
62530
63093
  inputFlag: options.input,
@@ -62621,6 +63184,13 @@ function registerWorkflowCommands(program2) {
62621
63184
  family: "compile",
62622
63185
  stage,
62623
63186
  scopeId: scope.scopeId,
63187
+ ...delegated ? {
63188
+ executionMode: "delegated",
63189
+ delegatedAuthority: {
63190
+ source: "user_conversation",
63191
+ scope: "low_risk_review_decisions"
63192
+ }
63193
+ } : {},
62624
63194
  ...scope.activeNodeRun !== undefined ? { activeNodeRun: scope.activeNodeRun } : {}
62625
63195
  }) : undefined;
62626
63196
  if (wantsChanges) {
@@ -64172,9 +64742,9 @@ function reconcileSchemaExample(name) {
64172
64742
  "Run context reconcile review before apply. Review persists the ready artifact; apply reads it from the current workflow scope.",
64173
64743
  "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.",
64174
64744
  "Do not use decisions: [] to accept all defaults; an empty decisions array means no decisions were made.",
64175
- "Keep proposed.content short and single-line; put long prose, code fences, and extended examples in proposed.detail.",
64176
- "Do not copy basis/evidence text into proposed.detail 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.",
64177
- "For kind=example, preserving the cited fenced code/config/command block in proposed.detail is active example detail, not raw-evidence echo.",
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.",
64746
+ "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
+ "For kind=example, preserving the cited fenced code/config/command block in proposed.content is active example knowledge, not raw-evidence echo.",
64178
64748
  '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.',
64179
64749
  "Only set decided_by: user after an actual user or tester confirmed the answer. Auto mode or broad permission to continue is not user confirmation; if no confirmation exists, fix/split evidence or leave ask_user unresolved.",
64180
64750
  "When prepare/review returns agent_hints[], follow those result-specific hints before retrying."
@@ -64192,7 +64762,7 @@ function reconcileSchemaExample(name) {
64192
64762
  proposed: {
64193
64763
  kind: "description",
64194
64764
  content: "Runtime Service owns request routing and retry policy.",
64195
- detail: "Longer explanation or fenced code belongs here, not in content.",
64765
+ summary: "Runtime Service owns routing and retry policy.",
64196
64766
  confidence: "confirmed",
64197
64767
  source_ref: "src-1#runtime-service L3-8@7a6f4c9d2e10",
64198
64768
  refers_to_nodes: ["deployment-pipeline"]
@@ -64253,7 +64823,9 @@ function compileSchemaExample(name) {
64253
64823
  ownership_requested_role: ["owned", "shared"]
64254
64824
  },
64255
64825
  limits: {
64256
- internal_claim_max_chars: 256
64826
+ summary_recommended_when_content_over_chars: 160,
64827
+ summary_required_when_content_over_chars: 200,
64828
+ summary_target: "about content length / 10, minimum 10 characters, recommended maximum 120; warnings are advisory"
64257
64829
  },
64258
64830
  section_kind_priority: SECTION_DRAFT_PRIORITY,
64259
64831
  section_kind_triggers: {
@@ -64272,14 +64844,15 @@ function compileSchemaExample(name) {
64272
64844
  notes: [
64273
64845
  "Use the NodeContext payload prepared by context compile --context <slug> --format json to choose source_refs[] from raw_snippets[].source_ref.",
64274
64846
  "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.",
64275
- "Every add/update/supersede Section write uses body plus source_refs[]. body may be long and may contain fenced code.",
64276
- "rewrite=false means preserve clear source wording; omit rewrite for the normal concise rewrite path.",
64847
+ "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
+ "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.",
64849
+ "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.",
64277
64850
  "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.",
64278
- "For description/spec, a concise summary plus cited descriptive bullets is allowed when the bullets themselves are useful knowledge. For code/config/command samples, prefer kind=example and keep the sample in body.",
64279
- "The CLI derives the internal short content/detail split before writing. Do not emit content, detail, content_mode, paraphrase_reason, basis_spans, or section_id for new Sections.",
64851
+ "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.",
64852
+ "Do not emit body, detail, raw, rewrite, content_mode, paraphrase_reason, basis_spans, or section_id for new Sections. If content differs from cited raw, the CLI derives a debug-only raw block during prepare/apply.",
64280
64853
  "source_refs is the only compile-draft citation input. For a single citation, use an array with one source_ref string.",
64281
- "For dense raw material where one Section summarizes multiple contiguous blocks, provide only the source_refs actually consumed by that Section body. Contiguous means the selected source_refs resolve to consecutive evidence-manifest blocks from the same source, snapshot, and alias; if an intervening citation-eligible block is skipped, split the action or include that block's source_ref.",
64282
- "If multiple source_refs are over-cited but the body/detail is fully supported by a smaller contiguous subset, the CLI may narrow the citation and report compile-source-refs-auto-narrowed; removed refs remain uncovered for later draft/skip actions.",
64854
+ "For dense raw material where one Section summarizes multiple contiguous blocks, provide only the source_refs actually consumed by that Section content. Contiguous means the selected source_refs resolve to consecutive evidence-manifest blocks from the same source, snapshot, and alias; if an intervening citation-eligible block is skipped, split the action or include that block's source_ref.",
64855
+ "If multiple source_refs are over-cited but the content is fully supported by a smaller contiguous subset, the CLI may narrow the citation and report compile-source-refs-auto-narrowed; removed refs remain uncovered for later draft/skip actions.",
64283
64856
  "If NodeContext only contains navigation or placeholder evidence (Parent/Children/Related/Relations, no detailed content), use op: skip; do not create a low-value description Section from those lines.",
64284
64857
  "Use structure_challenge for missing action nodes, extra action nodes, wrong shared block splits, missing depends_on edges, or wrong parents.",
64285
64858
  "Use pending_ownership_challenge for visible context_only blocks or secondary shared blocks that should become owned/shared evidence.",
@@ -64291,14 +64864,14 @@ function compileSchemaExample(name) {
64291
64864
  actions: [{
64292
64865
  op: "add",
64293
64866
  kind: "description",
64294
- body: "Runtime Service owns request routing and retry policy.",
64295
- rewrite: false,
64867
+ content: "Runtime Service owns request routing and retry policy.",
64296
64868
  source_refs: ["src-1#runtime-service L3-8@7a6f4c9d2e10"],
64297
64869
  refers_to_nodes: ["deployment-pipeline"]
64298
64870
  }, {
64299
64871
  op: "add",
64300
64872
  kind: "example",
64301
- body: [
64873
+ summary: "Runtime Service supports retry backoff configuration.",
64874
+ content: [
64302
64875
  "Runtime Service can be configured with retry limits and exponential backoff.",
64303
64876
  "",
64304
64877
  "```ts",
@@ -64312,7 +64885,7 @@ function compileSchemaExample(name) {
64312
64885
  }, {
64313
64886
  op: "update",
64314
64887
  target_section_id: "section-1",
64315
- body: "Runtime Service owns request routing and retry policy.",
64888
+ content: "Runtime Service owns request routing and retry policy.",
64316
64889
  source_refs: ["src-1#runtime-service L3-8@7a6f4c9d2e10"]
64317
64890
  }, {
64318
64891
  op: "supersede",
@@ -64320,7 +64893,7 @@ function compileSchemaExample(name) {
64320
64893
  reason: "Source now describes the replacement behavior.",
64321
64894
  new: {
64322
64895
  kind: "spec",
64323
- body: "Retries use exponential backoff.",
64896
+ content: "Retries use exponential backoff.",
64324
64897
  source_refs: ["src-1#retry-policy L12-16@c0d4e5f61728"]
64325
64898
  }
64326
64899
  }, {
@@ -64412,7 +64985,7 @@ function compileSchemaExample(name) {
64412
64985
  action: {
64413
64986
  op: "add",
64414
64987
  kind: "description",
64415
- body: "Runtime Service owns request routing.",
64988
+ content: "Runtime Service owns request routing.",
64416
64989
  source_refs: ["src-1#runtime-service L3-8@7a6f4c9d2e10"]
64417
64990
  }
64418
64991
  }, {
@@ -64467,11 +65040,11 @@ function compileSchemaExample(name) {
64467
65040
  applies_to: [
64468
65041
  "node.title",
64469
65042
  "node.summary",
65043
+ "section.summary",
64470
65044
  "section.content",
64471
- "section.detail",
64472
65045
  "user_facing_report"
64473
65046
  ],
64474
- instruction: "Generate knowledge titles, summaries, and user-facing reports in Chinese; keep node.summary concise (target <15 tokens, never >30 tokens); for source-bound Section content/detail, 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."
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."
64475
65048
  },
64476
65049
  existing: {
64477
65050
  sections: [{