@c4a/context-cli 0.6.1-beta.5 → 0.6.1-beta.6

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/README.md CHANGED
@@ -150,7 +150,7 @@ context source add file [YYYYMMDD] --module <module> --local <file-or-folder>
150
150
  context source add lark [YYYYMMDD] --module <module> --url <lark-url>
151
151
  context source add batch [YYYYMMDD] --input <yaml-or-json>
152
152
  context source remove <source-id> --format json # preview
153
- context source remove <source-id> --yes --format json # apply after reference checks
153
+ context source remove <source-id> --yes --plan-digest <preview-digest> --format json
154
154
  context source ensure [source]
155
155
  context source inspect [source]
156
156
 
package/README.zh-CN.md CHANGED
@@ -116,7 +116,7 @@ context source add file [YYYYMMDD] --module <module> --local <file-or-folder>
116
116
  context source add lark [YYYYMMDD] --module <module> --url <lark-url>
117
117
  context source add batch [YYYYMMDD] --input <yaml-or-json>
118
118
  context source remove <source-id> --format json # 预览
119
- context source remove <source-id> --yes --format json # 引用检查通过后执行
119
+ context source remove <source-id> --yes --plan-digest <预览摘要> --format json
120
120
  context source ensure [source]
121
121
  context source inspect [source]
122
122
 
package/cli.js CHANGED
@@ -17936,6 +17936,7 @@ function renderAgents(projectName, language) {
17936
17936
  "- 来源边界、正文读取、提取范围、语义分类、结构确认、Review 和包形态均是显式门禁,除非当前 Route 已证明可以继续。",
17937
17937
  "- 不得根据目录、文件名、URL、示例或旧会话推断用户决定;没有明确授权时不得对来源仓库执行 clone、fetch、checkout、install、build、test 或脚本。",
17938
17938
  "- 生命周期写入必须使用 Context CLI,不得用临时脚本修改 `sources/`、`knowledge/`、`dist/` 或 `.tmp/context-runtime/`。",
17939
+ "- 飞书采集只在 `evidence_status: error` 时停止;`projection_status: generic|warning` 表示原始 XML 已保留且可继续。不得在用户工作区修补 CLI 或手改快照来增加 renderer。",
17939
17940
  "- Agent 编写的临时输入优先放在 `.tmp/agent-payloads/`;这是推荐而非强制。不要自行创建 `inputs/` 等顶层临时目录。",
17940
17941
  "- 普通 Review 使用用户原样提供的 Payload;托管批准只能使用托管 status 返回的 revision-bound 原子命令。",
17941
17942
  "- Route 允许时,只读证据可以并行;注册、stage、confirm、Review apply、close 和 build 必须串行。",
@@ -17972,6 +17973,7 @@ function renderAgents(projectName, language) {
17972
17973
  "- Source boundaries, source-body reads, extraction scope, semantic classification, structure confirmation, Review, and package shape remain explicit gates unless the current route proves otherwise.",
17973
17974
  "- Never infer source or review decisions from repository layout, filenames, URLs, examples, or prior conversations. Never clone, fetch, checkout, install, build, test, or run source-repository scripts without explicit authority.",
17974
17975
  "- Use Context CLI for lifecycle writes. Do not inspect or repair `sources/`, `knowledge/`, `dist/`, or `.tmp/context-runtime/` with ad hoc scripts.",
17976
+ "- Stop Lark capture only for `evidence_status: error`. A `projection_status` of `generic` or `warning` means the original XML is preserved and the route may continue. Never patch the CLI or edit snapshots in a user workspace to add a renderer.",
17975
17977
  "- Prefer `.tmp/agent-payloads/` for Agent-authored transient command inputs. This is a recommendation, not a CLI requirement; explicit custom paths remain valid. Avoid inventing top-level scratch directories such as `inputs/`.",
17976
17978
  "- Review uses the user's exact Payload in ordinary mode. Managed approval uses only the revision-bound atomic command returned by managed status.",
17977
17979
  "- Evidence reads may be parallel when the route says so; registry, stage, confirm, Review apply, close, and build mutations are serial.",
@@ -45952,6 +45954,7 @@ function fidelityIssues(value, field) {
45952
45954
  }
45953
45955
  return {
45954
45956
  severity: item.severity,
45957
+ impact: item.impact === "evidence" || item.impact === "projection" ? item.impact : item.severity === "error" ? "evidence" : "projection",
45955
45958
  code: requiredString(item.code, `${field}[${index}].code`),
45956
45959
  block_type: requiredString(item.block_type, `${field}[${index}].block_type`),
45957
45960
  count: item.count,
@@ -45983,11 +45986,29 @@ function parseDocumentCaptureFidelity(value, field) {
45983
45986
  throw new TypeError(`${field} does not close for ${blockType}: discovered ${discoveredCount}, converted ${converted[blockType] ?? 0}, skipped ${skippedCount}`);
45984
45987
  }
45985
45988
  }
45986
- const status = issues.some((issue) => issue.severity === "error") ? "error" : issues.length > 0 ? "warning" : "complete";
45989
+ const evidenceStatus = issues.some((issue) => issue.impact === "evidence" && issue.severity === "error") ? "error" : "complete";
45990
+ const projectionIssues = issues.filter((issue) => issue.impact === "projection");
45991
+ const inferredProjectionStatus = projectionIssues.some((issue) => issue.severity === "error") ? "error" : projectionIssues.some((issue) => issue.code === "lark.capture.generic-projection") ? "generic" : projectionIssues.length > 0 ? "warning" : "complete";
45992
+ const projectionStatus = value.projection_status === "complete" || value.projection_status === "generic" || value.projection_status === "warning" || value.projection_status === "error" ? value.projection_status : inferredProjectionStatus;
45993
+ const status = evidenceStatus === "error" || projectionStatus === "error" ? "error" : issues.length > 0 ? "warning" : "complete";
45987
45994
  if (value.status !== status) {
45988
45995
  throw new TypeError(`${field}.status must be ${status} for its issues`);
45989
45996
  }
45990
- return { status, discovered, converted, skipped, issues };
45997
+ if (value.evidence_status !== undefined && value.evidence_status !== evidenceStatus) {
45998
+ throw new TypeError(`${field}.evidence_status must be ${evidenceStatus} for its issues`);
45999
+ }
46000
+ if (value.projection_status !== undefined && value.projection_status !== inferredProjectionStatus) {
46001
+ throw new TypeError(`${field}.projection_status must be ${inferredProjectionStatus} for its issues`);
46002
+ }
46003
+ return {
46004
+ status,
46005
+ evidence_status: evidenceStatus,
46006
+ projection_status: projectionStatus,
46007
+ discovered,
46008
+ converted,
46009
+ skipped,
46010
+ issues
46011
+ };
45991
46012
  }
45992
46013
 
45993
46014
  // ../extract/src/documentEvidence.ts
@@ -62934,7 +62955,7 @@ function batchIdentity(sourceName) {
62934
62955
  return;
62935
62956
  return { batch, module };
62936
62957
  }
62937
- function parseBatchManifest(value) {
62958
+ function parseDocumentSnapshotBatchManifest(value) {
62938
62959
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
62939
62960
  throw new TypeError("document snapshot batch manifest must be an object");
62940
62961
  }
@@ -62958,7 +62979,7 @@ function findDocumentSnapshotForSource(value, sourceName) {
62958
62979
  const identity = batchIdentity(sourceName);
62959
62980
  if (identity === undefined)
62960
62981
  throw new TypeError(`batch manifest cannot resolve non-batch source: ${sourceName}`);
62961
- const batch = parseBatchManifest(value);
62982
+ const batch = parseDocumentSnapshotBatchManifest(value);
62962
62983
  const snapshot2 = batch.sources[identity.module];
62963
62984
  if (snapshot2 === undefined)
62964
62985
  return null;
@@ -62991,7 +63012,7 @@ function updateDocumentManifestFile(input) {
62991
63012
  const identity = batchIdentity(input.snapshot.source_name);
62992
63013
  if (identity === undefined)
62993
63014
  return input.snapshot;
62994
- const existing = input.current === null ? undefined : parseBatchManifest(input.current);
63015
+ const existing = input.current === null ? undefined : parseDocumentSnapshotBatchManifest(input.current);
62995
63016
  if (existing !== undefined && (existing.source_type !== input.snapshot.source_type || existing.batch !== identity.batch)) {
62996
63017
  throw new TypeError(`document snapshot batch identity mismatch for ${input.snapshot.source_name}`);
62997
63018
  }
@@ -63005,6 +63026,36 @@ function updateDocumentManifestFile(input) {
63005
63026
  }).sort(([left], [right]) => left.localeCompare(right)))
63006
63027
  };
63007
63028
  }
63029
+ function removeDocumentSnapshotFromManifestFile(input) {
63030
+ if (input.current === null) {
63031
+ return { snapshot: null, next: null, remainingSources: 0 };
63032
+ }
63033
+ if (input.current !== null && typeof input.current === "object" && !Array.isArray(input.current) && input.current.schema_version === DOCUMENT_SNAPSHOT_BATCH_SCHEMA_VERSION) {
63034
+ const identity = batchIdentity(input.sourceName);
63035
+ if (identity === undefined)
63036
+ throw new TypeError(`batch manifest cannot resolve non-batch source: ${input.sourceName}`);
63037
+ const batch = parseDocumentSnapshotBatchManifest(input.current);
63038
+ if (batch.batch !== identity.batch) {
63039
+ throw new TypeError(`document snapshot batch identity mismatch for ${input.sourceName}`);
63040
+ }
63041
+ const snapshot2 = batch.sources[identity.module] ?? null;
63042
+ if (snapshot2 !== null && snapshot2.source_name !== input.sourceName) {
63043
+ throw new TypeError(`document snapshot batch source entry does not match ${input.sourceName}`);
63044
+ }
63045
+ const sources = Object.fromEntries(Object.entries(batch.sources).filter(([module]) => module !== identity.module).sort(([left], [right]) => left.localeCompare(right)));
63046
+ const remainingSources = Object.keys(sources).length;
63047
+ return {
63048
+ snapshot: snapshot2,
63049
+ next: remainingSources === 0 ? null : { ...batch, sources },
63050
+ remainingSources
63051
+ };
63052
+ }
63053
+ const snapshot = parseDocumentSnapshotManifest(input.current);
63054
+ if (snapshot.source_name !== input.sourceName) {
63055
+ throw new TypeError(`document snapshot is for ${snapshot.source_name}, expected ${input.sourceName}`);
63056
+ }
63057
+ return { snapshot, next: null, remainingSources: 0 };
63058
+ }
63008
63059
  function renderDocumentManifestFile(value) {
63009
63060
  return `${JSON.stringify(value, null, 2)}
63010
63061
  `;
@@ -63520,11 +63571,11 @@ function documentSnapshotFidelityState(manifest) {
63520
63571
  const report = manifest.metadata?.capture?.fidelity;
63521
63572
  if (report === undefined)
63522
63573
  return { blocking: [], warnings: [] };
63523
- const render = (severity) => report.issues.filter((issue) => issue.severity === severity).map((issue) => `${issue.code}: ${issue.block_type} × ${issue.count}: ${issue.reason}`);
63574
+ const render = (predicate) => report.issues.filter(predicate).map((issue) => `${issue.code}: ${issue.block_type} × ${issue.count}: ${issue.reason}`);
63524
63575
  return {
63525
63576
  report,
63526
- blocking: render("error"),
63527
- warnings: render("warning")
63577
+ blocking: render((issue) => issue.impact === "evidence" && issue.severity === "error"),
63578
+ warnings: render((issue) => issue.impact === "projection" || issue.severity === "warning")
63528
63579
  };
63529
63580
  }
63530
63581
 
@@ -84000,7 +84051,7 @@ async function documentSourceStatus(projectRoot, type, source3) {
84000
84051
  source: source3,
84001
84052
  snapshotConfigured: readiness.snapshotConfigured
84002
84053
  }) : null;
84003
- const agentHints = readiness.ready ? [] : [readiness.captureFidelity?.status === "error" ? "document-capture-fidelity-blocked" : "document-source-not-captured"];
84054
+ const agentHints = readiness.ready ? [] : [readiness.captureFidelity?.evidence_status === "error" ? "document-capture-fidelity-recapture-required" : "document-source-not-captured"];
84004
84055
  if (documentSiteHint !== null)
84005
84056
  agentHints.push(documentSiteHint);
84006
84057
  return {
@@ -84097,7 +84148,7 @@ function documentSnapshotReadiness(input) {
84097
84148
  return {
84098
84149
  ready: false,
84099
84150
  diagnostics: fidelity.blocking,
84100
- workspaceDiagnostics: fidelity.blocking,
84151
+ workspaceDiagnostics: [],
84101
84152
  ...fidelity.report !== undefined ? { captureFidelity: fidelity.report } : {}
84102
84153
  };
84103
84154
  }
@@ -84112,7 +84163,7 @@ function documentSnapshotReadiness(input) {
84112
84163
  }
84113
84164
  return {
84114
84165
  ready: true,
84115
- diagnostics: [],
84166
+ diagnostics: fidelity.warnings,
84116
84167
  workspaceDiagnostics: [],
84117
84168
  snapshotHash: manifest.snapshot_hash,
84118
84169
  ...fidelity.report !== undefined ? { captureFidelity: fidelity.report } : {},
@@ -84318,6 +84369,8 @@ function workspaceStateValid(observation) {
84318
84369
  return true;
84319
84370
  }
84320
84371
  function blockingVerificationClear(observation) {
84372
+ if (observation.capturedDocumentSources < observation.documentSources.length)
84373
+ return true;
84321
84374
  if (observation.verifyErrors === 0)
84322
84375
  return true;
84323
84376
  if (verifyErrorsAreCloseRepairable(observation.verifyIssues))
@@ -84325,6 +84378,8 @@ function blockingVerificationClear(observation) {
84325
84378
  return onlySourceDriftErrors(observation.verifyIssues);
84326
84379
  }
84327
84380
  function evidenceMaintenanceClear(observation) {
84381
+ if (observation.capturedDocumentSources < observation.documentSources.length)
84382
+ return true;
84328
84383
  return observation.evidenceWarnings !== "orphaned" && observation.evidenceWarnings !== "stale";
84329
84384
  }
84330
84385
  function proseDeclarationsComplete(observation) {
@@ -84877,7 +84932,7 @@ function rootDiagnostics(observation) {
84877
84932
  message: "Approved knowledge changed and its deterministic structure projection must be refreshed.",
84878
84933
  count: observation.projectionRefreshIssues
84879
84934
  });
84880
- } else if (observation.verifyErrors > 0) {
84935
+ } else if (observation.verifyErrors > 0 && observation.capturedDocumentSources === observation.documentSources.length) {
84881
84936
  diagnostics.push({
84882
84937
  code: "diagnostic.verify-failed",
84883
84938
  severity: "error",
@@ -85282,8 +85337,8 @@ function renderSources(status) {
85282
85337
  const repositories = status.sources.map((source3) => `${inline(source3.name)} — ready=${inline(source3.ready)}, ref=${inline(source3.ref)}, scope-match=${inline(source3.scopeMatches)}` + (source3.subpath === undefined ? "" : `, subpath=${inline(source3.subpath)}`));
85283
85338
  const documents = status.documentSources.map((source3) => {
85284
85339
  const fidelity = source3.captureFidelity;
85285
- const summary = fidelity === undefined ? "fidelity=unavailable" : `fidelity=${inline(fidelity.status)}, discovered=${Object.values(fidelity.discovered).reduce((sum, count) => sum + count, 0)}, converted=${Object.values(fidelity.converted).reduce((sum, count) => sum + count, 0)}, skipped=${fidelity.skipped.reduce((sum, item) => sum + item.count, 0)}`;
85286
- const issues = fidelity?.issues.map((issue) => `${inline(issue.severity)} ${inline(issue.code)} ${inline(issue.block_type)} (${issue.count}) — ${issue.reason}`) ?? [];
85340
+ const summary = fidelity === undefined ? "fidelity=unavailable" : `fidelity=${inline(fidelity.status)}, evidence=${inline(fidelity.evidence_status)}, projection=${inline(fidelity.projection_status)}, discovered=${Object.values(fidelity.discovered).reduce((sum, count) => sum + count, 0)}, converted=${Object.values(fidelity.converted).reduce((sum, count) => sum + count, 0)}, skipped=${fidelity.skipped.reduce((sum, item) => sum + item.count, 0)}`;
85341
+ const issues = fidelity?.issues.map((issue) => `${inline(issue.severity)} ${inline(issue.impact)} ${inline(issue.code)} ${inline(issue.block_type)} (${issue.count}) — ${issue.reason}`) ?? [];
85287
85342
  return [
85288
85343
  `${inline(`${source3.type}:${source3.name}`)} — captured=${inline(source3.snapshotReady)}, manifest=${inline(source3.manifest)}, ${summary}`,
85289
85344
  ...issues.map((issue) => ` - ${issue}`)
@@ -85717,7 +85772,7 @@ function compileStatusRouting(input) {
85717
85772
  };
85718
85773
  }
85719
85774
  function pendingDocumentCaptureCommands(input) {
85720
- const pendingSources = input.documentSources.filter((source3) => !source3.snapshotReady && source3.captureFidelity?.status !== "error");
85775
+ const pendingSources = input.documentSources.filter((source3) => !source3.snapshotReady);
85721
85776
  const phaseIds = [];
85722
85777
  const missingSources = [];
85723
85778
  for (const source3 of pendingSources) {
@@ -86187,7 +86242,15 @@ function receiptSetPath(receipts) {
86187
86242
  async function writeReceiptContinuation(input) {
86188
86243
  const path4 = receiptSetPath(input.receipts);
86189
86244
  await writeJsonAtomic(join46(input.projectRoot, path4), input.receipts);
86190
- const command2 = [
86245
+ const command2 = input.managed ? [
86246
+ "context",
86247
+ "--workflow-resource-receipts",
86248
+ shellQuote6(`@${path4}`),
86249
+ "run",
86250
+ authorityCommandOptions(input.authorities, "resource").trim(),
86251
+ "--until blocked-or-complete",
86252
+ "--format json"
86253
+ ].filter((item) => item.length > 0).join(" ") : [
86191
86254
  "context status",
86192
86255
  authorityCommandOptions(input.authorities, "resource").trim(),
86193
86256
  "--resource-receipts",
@@ -86261,6 +86324,7 @@ async function materializeContextWorkflowResource(input) {
86261
86324
  const afterReadReceipts = mergedReceipts(input.resourceReceipts, receiptCandidate);
86262
86325
  const continuation = await writeReceiptContinuation({
86263
86326
  projectRoot: found.projectRoot,
86327
+ managed: input.managed === true,
86264
86328
  authorities,
86265
86329
  receipts: afterReadReceipts
86266
86330
  });
@@ -86313,6 +86377,7 @@ async function acknowledgeCurrentWorkflowResources(input) {
86313
86377
  };
86314
86378
  const continuation = await writeReceiptContinuation({
86315
86379
  projectRoot: found.projectRoot,
86380
+ managed: input.managed === true,
86316
86381
  authorities,
86317
86382
  receipts: normalizedReceipts
86318
86383
  });
@@ -86467,9 +86532,11 @@ async function existingFileKind(path4) {
86467
86532
  const stats = await lstat2(path4);
86468
86533
  if (stats.isFile())
86469
86534
  return "file";
86535
+ if (stats.isDirectory())
86536
+ return "directory";
86470
86537
  if (stats.isSymbolicLink())
86471
86538
  return "symlink";
86472
- throw new TypeError(`atomic file batch target is not a file: ${path4}`);
86539
+ throw new TypeError(`atomic file batch target has an unsupported filesystem kind: ${path4}`);
86473
86540
  } catch (error) {
86474
86541
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
86475
86542
  return "missing";
@@ -90046,6 +90113,7 @@ class XMLParser {
90046
90113
  }
90047
90114
 
90048
90115
  // src/lib/larkDocxXml.ts
90116
+ var LARK_EMPTY_SUB_PAGE_LIST_CODE = "lark.capture.sub-page-list-empty";
90049
90117
  var parser = new XMLParser({
90050
90118
  preserveOrder: true,
90051
90119
  ignoreAttributes: false,
@@ -90090,22 +90158,25 @@ class FidelityTracker {
90090
90158
  reason,
90091
90159
  count: (current2?.count ?? 0) + 1
90092
90160
  });
90093
- const issueKey = `${severity}\x00${blockType}\x00${reason}`;
90161
+ const code3 = severity === "error" ? "lark.capture.fidelity-loss" : "lark.capture.unsupported-empty-block";
90162
+ const issueKey = `${severity}\x00${code3}\x00${blockType}\x00${reason}`;
90094
90163
  const issue = this.issues.get(issueKey);
90095
90164
  this.issues.set(issueKey, {
90096
90165
  severity,
90097
- code: severity === "error" ? "lark.capture.fidelity-loss" : "lark.capture.unsupported-empty-block",
90166
+ impact: "projection",
90167
+ code: code3,
90098
90168
  block_type: blockType,
90099
90169
  count: (issue?.count ?? 0) + 1,
90100
90170
  reason
90101
90171
  });
90102
90172
  }
90103
- flag(blockType, reason, severity) {
90104
- const issueKey = `${severity}\x00${blockType}\x00${reason}`;
90173
+ flag(blockType, reason, severity, code3 = severity === "error" ? "lark.capture.fidelity-loss" : "lark.capture.fidelity-warning", impact = severity === "error" ? "evidence" : "projection") {
90174
+ const issueKey = `${severity}\x00${impact}\x00${code3}\x00${blockType}\x00${reason}`;
90105
90175
  const issue = this.issues.get(issueKey);
90106
90176
  this.issues.set(issueKey, {
90107
90177
  severity,
90108
- code: severity === "error" ? "lark.capture.fidelity-loss" : "lark.capture.fidelity-warning",
90178
+ impact,
90179
+ code: code3,
90109
90180
  block_type: blockType,
90110
90181
  count: (issue?.count ?? 0) + 1,
90111
90182
  reason
@@ -90130,8 +90201,19 @@ class FidelityTracker {
90130
90201
  }
90131
90202
  }
90132
90203
  const issues = [...this.issues.values()].sort((left, right) => left.severity.localeCompare(right.severity) || left.block_type.localeCompare(right.block_type));
90133
- const status = issues.some((issue) => issue.severity === "error") ? "error" : issues.length > 0 ? "warning" : "complete";
90134
- return { status, discovered, converted, skipped, issues };
90204
+ const evidenceStatus = issues.some((issue) => issue.impact === "evidence" && issue.severity === "error") ? "error" : "complete";
90205
+ const projectionIssues = issues.filter((issue) => issue.impact === "projection");
90206
+ const projectionStatus = projectionIssues.some((issue) => issue.severity === "error") ? "error" : projectionIssues.some((issue) => issue.code === "lark.capture.generic-projection") ? "generic" : projectionIssues.length > 0 ? "warning" : "complete";
90207
+ const status = evidenceStatus === "error" || projectionStatus === "error" ? "error" : issues.length > 0 ? "warning" : "complete";
90208
+ return {
90209
+ status,
90210
+ evidence_status: evidenceStatus,
90211
+ projection_status: projectionStatus,
90212
+ discovered,
90213
+ converted,
90214
+ skipped,
90215
+ issues
90216
+ };
90135
90217
  }
90136
90218
  }
90137
90219
  function sortedCounts(counts2) {
@@ -90190,7 +90272,16 @@ function stableDocumentUrl(sourceUrl, fileType, docId) {
90190
90272
  return `lark:${fileType ?? "document"}:${docId}`;
90191
90273
  }
90192
90274
  function resourceIdentity(kind, attrs) {
90193
- const kindCandidates = kind === "cite" ? [attrs["doc-id"]] : kind === "base" || kind === "sheet" ? [attrs["table-id"], attrs["sheet-id"]] : [];
90275
+ if (kind === "bookmark")
90276
+ return;
90277
+ if (kind === "poll")
90278
+ return attrs.id;
90279
+ if (kind === "synced-reference") {
90280
+ const sourceToken = attrs["src-token"];
90281
+ const sourceBlockId = attrs["src-block-id"];
90282
+ return sourceToken !== undefined && sourceBlockId !== undefined ? `${sourceToken}#${sourceBlockId}` : undefined;
90283
+ }
90284
+ const kindCandidates = kind === "cite" || kind === "document" ? [attrs["doc-id"]] : kind === "base" || kind === "sheet" ? [attrs["table-id"], attrs["sheet-id"]] : [];
90194
90285
  const candidates = [
90195
90286
  attrs.token,
90196
90287
  attrs["file-token"],
@@ -90199,7 +90290,7 @@ function resourceIdentity(kind, attrs) {
90199
90290
  attrs["obj-token"],
90200
90291
  attrs["source-id"],
90201
90292
  attrs["chat-id"],
90202
- attrs.id,
90293
+ ...kind === "document" ? [] : [attrs.id],
90203
90294
  ...kindCandidates,
90204
90295
  attrs.src
90205
90296
  ];
@@ -90366,21 +90457,157 @@ ${safeFence(source3, attrs.type === "mermaid" ? "mermaid" : "text")}
90366
90457
 
90367
90458
  > ${title}${details.length > 0 ? ` — ${details}` : ""} (${resource.locator})
90368
90459
 
90460
+ `;
90461
+ }
90462
+ function renderSubPage(nodes, attrs, ctx) {
90463
+ const title = attrs.title ?? (normalizeInline(textContent(nodes)) || "Untitled subpage");
90464
+ const docId = attrs["doc-id"] ?? attrs.token;
90465
+ const resource = addResource(ctx, "sub-page", "document", attrs, title);
90466
+ if (docId === undefined) {
90467
+ ctx.tracker.flag("sub-page", "sub-page has no doc-id or token", "error");
90468
+ return `${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->`;
90469
+ }
90470
+ const target = stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId);
90471
+ return `[${escapeMarkdownLabel(title)}](${target}) <!-- ${resource.locator} -->`;
90472
+ }
90473
+ function renderSubPageList(nodes, ctx) {
90474
+ if (!nodes.some((node3) => elementName(node3) === "sub-page")) {
90475
+ ctx.tracker.flag("sub-page-list", "sub-page-list returned no sub-page entries; child navigation completeness cannot be verified", "error", LARK_EMPTY_SUB_PAGE_LIST_CODE);
90476
+ }
90477
+ const lines = [];
90478
+ for (const node3 of nodes) {
90479
+ const name2 = elementName(node3);
90480
+ if (name2 === undefined)
90481
+ continue;
90482
+ const rendered = normalizeMarkdown2(renderNode(node3, { ...ctx, mode: "inline" }));
90483
+ if (rendered.length === 0)
90484
+ continue;
90485
+ lines.push(name2 === "sub-page" ? `- ${rendered}` : rendered);
90486
+ }
90487
+ return lines.length === 0 ? "" : `
90488
+
90489
+ ${lines.join(`
90490
+ `)}
90491
+
90492
+ `;
90493
+ }
90494
+ function renderBookmark(nodes, attrs, ctx) {
90495
+ const href = attrs.href ?? attrs.url;
90496
+ const title = attrs.name ?? attrs.title ?? (normalizeInline(textContent(nodes)) || href) ?? "Bookmark";
90497
+ const resource = addResource(ctx, "bookmark", "bookmark", attrs, title);
90498
+ if (href === undefined || isTransientMediaUrl(href)) {
90499
+ ctx.tracker.flag("bookmark", "bookmark has no stable non-transient URL", "error");
90500
+ return `
90501
+
90502
+ > Bookmark: ${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->
90503
+
90504
+ `;
90505
+ }
90506
+ return `
90507
+
90508
+ > Bookmark: [${escapeMarkdownLabel(title)}](${href}) <!-- ${resource.locator} -->
90509
+
90510
+ `;
90511
+ }
90512
+ function renderSyncedReference(attrs, ctx) {
90513
+ const sourceToken = attrs["src-token"];
90514
+ const sourceBlockId = attrs["src-block-id"];
90515
+ const title = attrs.title ?? attrs.name ?? "Synced reference";
90516
+ const resource = addResource(ctx, "synced_reference", "synced-reference", attrs, title);
90517
+ if (sourceToken === undefined || sourceBlockId === undefined) {
90518
+ ctx.tracker.flag("synced_reference", "synced_reference requires both src-token and src-block-id", "error");
90519
+ return `
90520
+
90521
+ > ${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->
90522
+
90523
+ `;
90524
+ }
90525
+ const target = `${stableDocumentUrl(ctx.sourceUrl, "docx", sourceToken)}#${encodeURIComponent(sourceBlockId)}`;
90526
+ return `
90527
+
90528
+ > [${escapeMarkdownLabel(title)}](${target}) <!-- ${resource.locator} -->
90529
+
90530
+ `;
90531
+ }
90532
+ function renderChecklistItem(blockType, nodes, attrs, ctx) {
90533
+ const done = attrs.done;
90534
+ const checked = attrs.checked;
90535
+ const state = done ?? checked;
90536
+ const stateIsValid = (state === "true" || state === "false") && (done === undefined || checked === undefined || done === checked);
90537
+ const body = normalizeInline(renderChildren(nodes, { ...ctx, mode: "inline" }));
90538
+ if (!stateIsValid) {
90539
+ ctx.tracker.flag(blockType, `${blockType} requires one unambiguous boolean done or checked attribute`, "warning", "lark.capture.checkbox-state-invalid", "projection");
90540
+ return `
90541
+ - [?] ${body}
90542
+ `;
90543
+ }
90544
+ return `
90545
+ - [${state === "true" ? "x" : " "}] ${body}
90546
+ `;
90547
+ }
90548
+ function meaningfulAttributes(attrs, excluded) {
90549
+ return Object.entries(attrs).filter(([key, value]) => value.length > 0 && !PRESENTATION_ONLY_ATTRIBUTES.has(key) && !excluded.has(key)).sort(([left], [right]) => left.localeCompare(right));
90550
+ }
90551
+ function auditableAttributes(attrs) {
90552
+ return Object.entries(attrs).filter(([, value]) => value.length > 0).map(([key, value]) => [key, isTransientMediaUrl(value) ? "[redacted-transient-url]" : value]).sort(([left], [right]) => left.localeCompare(right));
90553
+ }
90554
+ function renderPollOption(blockType, nodes, attrs, ctx) {
90555
+ const body = normalizeInline(renderChildren(nodes, { ...ctx, mode: "inline" }));
90556
+ const label3 = body || attrs.name || attrs.title || attrs.label || attrs.value;
90557
+ const details = meaningfulAttributes(attrs, new Set(["name", "title", "label", "value"])).map(([key, value]) => `${key}=${value}`);
90558
+ if (label3 === undefined && details.length === 0) {
90559
+ ctx.tracker.flag(blockType, `${blockType} has no visible label or metadata`, "warning", "lark.capture.poll-option-empty", "projection");
90560
+ }
90561
+ const rendered = [label3 ?? "Unnamed option", ...details.length > 0 ? [`(${details.join(", ")})`] : []].join(" ");
90562
+ return `
90563
+ - ${rendered}
90564
+ `;
90565
+ }
90566
+ function renderPoll(nodes, attrs, ctx) {
90567
+ const title = attrs.name ?? attrs.title ?? "Untitled poll";
90568
+ const resource = addResource(ctx, "poll", "poll", attrs, title);
90569
+ const href = attrs.href ?? attrs.url;
90570
+ const label3 = href !== undefined && !isTransientMediaUrl(href) ? `[${escapeMarkdownLabel(title)}](${href})` : escapeMarkdownLabel(title);
90571
+ const details = meaningfulAttributes(attrs, new Set(["name", "title", "href", "url"])).map(([key, value]) => `${key}=${value}`);
90572
+ const children = normalizeMarkdown2(renderChildren(nodes, { ...ctx, mode: "block" }));
90573
+ const lines = [
90574
+ `> Lark poll (non-interactive): ${label3} <!-- ${resource.locator} -->`,
90575
+ ...details.length > 0 ? [`> Exported attributes: ${details.join(", ")}`] : [],
90576
+ ...children.length === 0 ? ["> Options and results are not present in the exported XML."] : [children]
90577
+ ];
90578
+ return `
90579
+
90580
+ ${lines.join(`
90581
+ `)}
90582
+
90369
90583
  `;
90370
90584
  }
90371
90585
  function renderUnknown(name2, nodes, attrs, ctx) {
90372
90586
  const body = normalizeMarkdown2(renderChildren(nodes, ctx));
90373
- const meaningfulAttrs = Object.keys(attrs).filter((key) => !PRESENTATION_ONLY_ATTRIBUTES.has(key));
90374
- if (body.length === 0 && meaningfulAttrs.length === 0) {
90587
+ const exportedAttrs = auditableAttributes(attrs);
90588
+ if (body.length === 0 && exportedAttrs.length === 0) {
90375
90589
  ctx.tracker.skip(name2, "unknown empty block omitted", "warning");
90376
90590
  return "";
90377
90591
  }
90378
- ctx.tracker.skip(name2, "unknown non-empty block requires an explicit deterministic renderer", "error");
90592
+ ctx.tracker.convert(name2);
90593
+ ctx.tracker.flag(name2, "block was preserved through the generic non-interactive projection; inspect source.xml for the original structure", "warning", "lark.capture.generic-projection", "projection");
90594
+ const digest4 = createHash20("sha256").update(JSON.stringify({ name: name2, attributes: exportedAttrs, text: normalizeInline(textContent(nodes)) }), "utf8").digest("hex").slice(0, 12);
90595
+ const locator = `lark:block:${name2}:${digest4}`;
90596
+ ctx.resources.push({
90597
+ kind: "embed",
90598
+ locator,
90599
+ title: name2,
90600
+ attributes: Object.fromEntries(exportedAttrs)
90601
+ });
90602
+ const lines = [
90603
+ `> Lark block (generic projection): \`${name2}\` <!-- ${locator} -->`,
90604
+ ...exportedAttrs.length > 0 ? [`> Exported attributes: ${JSON.stringify(Object.fromEntries(exportedAttrs))}`] : [],
90605
+ ...body.length > 0 ? [body] : []
90606
+ ];
90379
90607
  return `
90380
90608
 
90381
- > Unsupported Lark block \`${name2}\`; review the captured XML evidence before continuing.${body.length > 0 ? `
90382
- > ${body.replace(/\n/gu, `
90383
- > `)}` : ""}
90609
+ ${lines.join(`
90610
+ `)}
90384
90611
 
90385
90612
  `;
90386
90613
  }
@@ -90397,6 +90624,22 @@ function renderNode(node3, ctx) {
90397
90624
  ctx.tracker.convert(name2);
90398
90625
  return renderResource(name2, nodes, attrs, ctx);
90399
90626
  }
90627
+ if (name2 === "sub-page") {
90628
+ ctx.tracker.convert(name2);
90629
+ return renderSubPage(nodes, attrs, ctx);
90630
+ }
90631
+ if (name2 === "sub-page-list") {
90632
+ ctx.tracker.convert(name2);
90633
+ return renderSubPageList(nodes, ctx);
90634
+ }
90635
+ if (name2 === "bookmark") {
90636
+ ctx.tracker.convert(name2);
90637
+ return renderBookmark(nodes, attrs, ctx);
90638
+ }
90639
+ if (name2 === "synced_reference") {
90640
+ ctx.tracker.convert(name2);
90641
+ return renderSyncedReference(attrs, ctx);
90642
+ }
90400
90643
  if (name2 === "title" || /^h[1-9]$/u.test(name2) || name2 === "heading") {
90401
90644
  ctx.tracker.convert(name2);
90402
90645
  const level = name2 === "title" ? 1 : name2 === "heading" ? Number(attrs.level ?? 2) : Number(name2.slice(1));
@@ -90506,12 +90749,21 @@ ${body.split(`
90506
90749
  ctx.tracker.convert(name2);
90507
90750
  return "";
90508
90751
  }
90509
- if (name2 === "todo") {
90752
+ if (name2 === "checkbox" || name2 === "todo") {
90510
90753
  ctx.tracker.convert(name2);
90511
- const checked = attrs.done === "true" || attrs.checked === "true";
90512
- return `
90513
- - [${checked ? "x" : " "}] ${normalizeInline(renderChildren(nodes, { ...ctx, mode: "inline" }))}
90514
- `;
90754
+ return renderChecklistItem(name2, nodes, attrs, ctx);
90755
+ }
90756
+ if (name2 === "poll") {
90757
+ if (nodes.length === 0 && Object.keys(attrs).length === 0) {
90758
+ ctx.tracker.skip(name2, "empty poll omitted because the export contains no identity or content", "warning");
90759
+ return "";
90760
+ }
90761
+ ctx.tracker.convert(name2);
90762
+ return renderPoll(nodes, attrs, ctx);
90763
+ }
90764
+ if (["option", "poll-option", "poll_option", "choice"].includes(name2)) {
90765
+ ctx.tracker.convert(name2);
90766
+ return renderPollOption(name2, nodes, attrs, ctx);
90515
90767
  }
90516
90768
  if (["mention", "person", "emoji"].includes(name2)) {
90517
90769
  ctx.tracker.convert(name2);
@@ -90553,6 +90805,7 @@ function projectLarkDocxXml(input) {
90553
90805
  // src/lib/feishu.ts
90554
90806
  var LARK_BIN = "lark-cli";
90555
90807
  var MAX_FETCH_PAGES = 50;
90808
+ var MAX_STRUCTURAL_FETCH_ATTEMPTS = 2;
90556
90809
 
90557
90810
  class LarkCliNotInstalledError extends Error {
90558
90811
  constructor() {
@@ -90754,6 +91007,8 @@ function extractDocsFetchAssets(payload) {
90754
91007
  function emptyFidelityReport() {
90755
91008
  return {
90756
91009
  status: "complete",
91010
+ evidence_status: "complete",
91011
+ projection_status: "complete",
90757
91012
  discovered: {},
90758
91013
  converted: {},
90759
91014
  skipped: [],
@@ -90785,37 +91040,35 @@ function fidelityReportAsset(report) {
90785
91040
  return {
90786
91041
  path: "capture-fidelity.json",
90787
91042
  bytes: Buffer.from(`${JSON.stringify({
90788
- schema_version: "context.lark-capture-fidelity.v1",
91043
+ schema_version: "context.lark-capture-fidelity.v2",
90789
91044
  ...report
90790
91045
  }, null, 2)}
90791
91046
  `, "utf8"),
90792
91047
  mediaType: "application/vnd.context.lark-capture-fidelity+json",
90793
91048
  source: {
90794
91049
  kind: "capture-fidelity",
90795
- status: report.status
91050
+ status: report.status,
91051
+ evidence_status: report.evidence_status,
91052
+ projection_status: report.projection_status
90796
91053
  }
90797
91054
  };
90798
91055
  }
90799
- async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
91056
+ async function fetchDocsResponse(input, docsFetchPlan, runner2) {
90800
91057
  const chunks = [];
90801
91058
  let contentFormat;
90802
91059
  let title;
90803
91060
  let revisionId;
90804
91061
  let unsupportedShape;
90805
91062
  const assets = [];
90806
- const docsFetchPlan = await resolveDocsFetchPlan(input.docsApiVersion ?? "auto", runner2);
90807
91063
  let nextOffset;
90808
91064
  for (let page = 0;page < MAX_FETCH_PAGES; page++) {
90809
91065
  const args = ["docs", "+fetch", "--as", "user", "--doc", input.url, "--detail", "full", "--format", "json"];
90810
- if (docsFetchPlan.apiVersion === "v2") {
91066
+ if (docsFetchPlan.apiVersion === "v2")
90811
91067
  args.push("--api-version", "v2");
90812
- }
90813
- if (docsFetchPlan.docFormat === "xml") {
91068
+ if (docsFetchPlan.docFormat === "xml")
90814
91069
  args.push("--doc-format", "xml");
90815
- }
90816
- if (nextOffset !== undefined) {
91070
+ if (nextOffset !== undefined)
90817
91071
  args.push("--offset", String(nextOffset));
90818
- }
90819
91072
  const result = await runner2(args);
90820
91073
  if (result.exitCode !== 0) {
90821
91074
  throw new LarkCliError(docsFetchFailureMessage(result.stderr, docsFetchPlan.apiVersion), result.exitCode, result.stderr);
@@ -90826,9 +91079,8 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
90826
91079
  throw new LarkCliError("lark-cli docs +fetch changed content format between pages", 0, "");
90827
91080
  }
90828
91081
  contentFormat = extracted.format;
90829
- if (page === 0 && extracted.title !== undefined) {
91082
+ if (page === 0 && extracted.title !== undefined)
90830
91083
  title = extracted.title;
90831
- }
90832
91084
  revisionId ??= extractDocsFetchRevisionId(payload);
90833
91085
  assets.push(...extractDocsFetchAssets(payload));
90834
91086
  if (extracted.body !== undefined && extracted.body.length > 0) {
@@ -90846,19 +91098,42 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
90846
91098
  throw new LarkCliError(`${LARK_BIN} docs +fetch exceeded ${MAX_FETCH_PAGES} pagination calls; likely a server-side issue`, 0, "");
90847
91099
  }
90848
91100
  }
90849
- const fetchedBody = chunks.join(`
91101
+ const body = chunks.join(`
90850
91102
 
90851
91103
  `);
90852
- if (fetchedBody.trim().length === 0 && (title === undefined || title.length === 0)) {
91104
+ if (body.trim().length === 0 && (title === undefined || title.length === 0)) {
90853
91105
  if (unsupportedShape !== undefined) {
90854
91106
  throw new LarkCliError(`${LARK_BIN} docs +fetch returned an unsupported payload shape (${unsupportedShape}). Expected data.markdown or data.document.content; this is a format adapter issue, not a permission error.`, 0, "");
90855
91107
  }
90856
91108
  throw new LarkCliError("document is empty — it may not exist or you lack permission", 0, "");
90857
91109
  }
90858
- let body = fetchedBody;
91110
+ return {
91111
+ body,
91112
+ contentFormat,
91113
+ ...title !== undefined ? { title } : {},
91114
+ ...revisionId !== undefined ? { revisionId } : {},
91115
+ assets
91116
+ };
91117
+ }
91118
+ function hasEmptySubPageList(projection) {
91119
+ return projection.fidelity.issues.some((issue) => issue.code === LARK_EMPTY_SUB_PAGE_LIST_CODE);
91120
+ }
91121
+ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
91122
+ const docsFetchPlan = await resolveDocsFetchPlan(input.docsApiVersion ?? "auto", runner2);
91123
+ let fetched = await fetchDocsResponse(input, docsFetchPlan, runner2);
91124
+ let projection;
91125
+ for (let attempt = 0;attempt < MAX_STRUCTURAL_FETCH_ATTEMPTS && fetched.contentFormat === "xml"; attempt++) {
91126
+ projection = projectLarkDocxXml({ xml: fetched.body, sourceUrl: input.url });
91127
+ if (!hasEmptySubPageList(projection) || attempt === MAX_STRUCTURAL_FETCH_ATTEMPTS - 1)
91128
+ break;
91129
+ fetched = await fetchDocsResponse(input, docsFetchPlan, runner2);
91130
+ }
91131
+ let body = fetched.body;
91132
+ let title = fetched.title;
91133
+ const revisionId = fetched.revisionId;
91134
+ const assets = [...fetched.assets];
90859
91135
  let fidelity = emptyFidelityReport();
90860
- if (contentFormat === "xml") {
90861
- const projection = projectLarkDocxXml({ xml: fetchedBody, sourceUrl: input.url });
91136
+ if (projection !== undefined) {
90862
91137
  body = projection.markdown;
90863
91138
  title ??= projection.title;
90864
91139
  fidelity = projection.fidelity;
@@ -91206,6 +91481,21 @@ async function runCaptureLarkPhaseUnlocked(input) {
91206
91481
  next: `fix write permissions or restore ${materializedAt}, then rerun context run capture:lark:${resolved.sourceName}`
91207
91482
  });
91208
91483
  }
91484
+ if (fetched.fidelity.evidence_status === "error") {
91485
+ throw new ContextError(ExitCode.WorkspaceStateError, `lark source ${resolved.sourceName} audit snapshot was preserved, but capture fidelity validation failed`, {
91486
+ category: ErrorCategory.PartialFailure,
91487
+ code: "lark.capture.fidelity-loss",
91488
+ sourceName: resolved.sourceName,
91489
+ snapshot: {
91490
+ manifest: manifestPath,
91491
+ materializedAt,
91492
+ snapshot_hash: manifest.snapshot_hash,
91493
+ changed
91494
+ },
91495
+ issues: fetched.fidelity.issues.filter((issue) => issue.impact === "evidence"),
91496
+ next: "context status --format json"
91497
+ });
91498
+ }
91209
91499
  return {
91210
91500
  kind: "document.capture.lark.result",
91211
91501
  source: {
@@ -97480,6 +97770,13 @@ function selectAutomaticWorkflowCommand(status) {
97480
97770
  stop: blockedStop(status, "workflow.until.route-not-immediate", "The current route requires a decision or authority that is not resolved.")
97481
97771
  };
97482
97772
  }
97773
+ const unreadRequired = route.resources.required.filter((resource) => resource.read_state === "read-required");
97774
+ if (unreadRequired.length > 0) {
97775
+ return {
97776
+ state: "blocked",
97777
+ stop: blockedStop(status, "workflow.until.agent-context-required", `Read the current route's required resources before automatic execution: ${unreadRequired.map((resource) => resource.id).join(", ")}.`)
97778
+ };
97779
+ }
97483
97780
  const commands = route.commands.filter((item) => item.availability === "immediate");
97484
97781
  if (commands.length !== 1) {
97485
97782
  return {
@@ -98375,10 +98672,10 @@ async function documentSnapshotState(input) {
98375
98672
  if (fidelity.blocking.length > 0) {
98376
98673
  return {
98377
98674
  snapshotReady: false,
98378
- state: "workspace-state-invalid",
98675
+ state: "needs-capture",
98379
98676
  manifest,
98380
98677
  diagnostics: fidelity.blocking,
98381
- next: `context source inspect ${input.source.name} --format json`,
98678
+ next: `context run capture:${input.sourceType}:${input.source.name}`,
98382
98679
  ...fidelity.report !== undefined ? { captureFidelity: fidelity.report } : {}
98383
98680
  };
98384
98681
  }
@@ -98913,6 +99210,7 @@ async function registerSourceBatch(input) {
98913
99210
 
98914
99211
  // src/project/sourceRemoval.ts
98915
99212
  import { existsSync as existsSync38 } from "node:fs";
99213
+ import { createHash as createHash25 } from "node:crypto";
98916
99214
  import { readFile as readFile42, readdir as readdir18, rm as rm15 } from "node:fs/promises";
98917
99215
  import { isAbsolute as isAbsolute10, join as join61, relative as relative15, resolve as resolve25, sep as sep3 } from "node:path";
98918
99216
  var import_yaml34 = __toESM(require_dist3(), 1);
@@ -99009,7 +99307,8 @@ async function resolveRemovableSource(projectRoot, selector) {
99009
99307
  name: source3.name,
99010
99308
  ...source3.namespace !== undefined ? { namespace: source3.namespace } : {},
99011
99309
  ...source3.module !== undefined ? { module: source3.module } : {},
99012
- materializedAt: source3.materializedAt
99310
+ materializedAt: source3.materializedAt,
99311
+ ...source3.snapshot?.manifest !== undefined ? { manifest: source3.snapshot.manifest } : {}
99013
99312
  })),
99014
99313
  ...registry2.larks.map((source3) => ({
99015
99314
  type: "lark",
@@ -99017,7 +99316,8 @@ async function resolveRemovableSource(projectRoot, selector) {
99017
99316
  name: source3.name,
99018
99317
  ...source3.namespace !== undefined ? { namespace: source3.namespace } : {},
99019
99318
  ...source3.module !== undefined ? { module: source3.module } : {},
99020
- materializedAt: source3.materializedAt
99319
+ materializedAt: source3.materializedAt,
99320
+ ...source3.snapshot?.manifest !== undefined ? { manifest: source3.snapshot.manifest } : {}
99021
99321
  }))
99022
99322
  ].filter((source3) => source3.id === selector || source3.name === selector);
99023
99323
  if (matches.length === 0) {
@@ -99056,18 +99356,14 @@ function removeDocumentEntry(document4, source3) {
99056
99356
  });
99057
99357
  return { ...record, sources: nextSources };
99058
99358
  }
99059
- async function removeRegistryEntry(projectRoot, source3) {
99060
- if (source3.type === "repo") {
99061
- const registry2 = await readRepoRegistry(projectRoot);
99062
- await writeRepoRegistry(projectRoot, {
99063
- repos: registry2.repos.filter((entry) => entry.name !== source3.name && entry.id !== source3.id)
99064
- });
99065
- return;
99066
- }
99359
+ async function registryRemovalWrite(projectRoot, source3) {
99067
99360
  const path4 = registryPath2(source3.type);
99068
99361
  const absolutePath = join61(projectRoot, path4);
99069
99362
  const document4 = existsSync38(absolutePath) ? import_yaml34.default.parse(await readFile42(absolutePath, "utf8")) : { sources: [] };
99070
- await atomicWriteFile(absolutePath, import_yaml34.default.stringify(removeDocumentEntry(document4, source3)));
99363
+ return {
99364
+ path: absolutePath,
99365
+ bytes: import_yaml34.default.stringify(removeDocumentEntry(document4, source3))
99366
+ };
99071
99367
  }
99072
99368
  function safeManagedMaterializedPath(projectRoot, source3) {
99073
99369
  if (isAbsolute10(source3.materializedAt))
@@ -99080,6 +99376,158 @@ function safeManagedMaterializedPath(projectRoot, source3) {
99080
99376
  }
99081
99377
  return absolute;
99082
99378
  }
99379
+ function safeManagedManifestPath(projectRoot, source3) {
99380
+ const manifest = source3.manifest ?? join61(source3.materializedAt, "manifest.json");
99381
+ if (isAbsolute10(manifest))
99382
+ throw unsafeOwnership(source3, manifest);
99383
+ const absolute = resolve25(projectRoot, manifest);
99384
+ const expectedRoot = resolve25(projectRoot, "sources", source3.type);
99385
+ const rel = relative15(expectedRoot, absolute);
99386
+ if (rel.length === 0 || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute10(rel)) {
99387
+ throw unsafeOwnership(source3, manifest);
99388
+ }
99389
+ return absolute;
99390
+ }
99391
+ function safeManagedSnapshotChild(projectRoot, source3, path4) {
99392
+ const materializedRoot = safeManagedMaterializedPath(projectRoot, source3);
99393
+ if (materializedRoot === undefined || isAbsolute10(path4))
99394
+ throw unsafeOwnership(source3, path4);
99395
+ const absolute = resolve25(materializedRoot, path4);
99396
+ const rel = relative15(materializedRoot, absolute);
99397
+ if (rel.length === 0 || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute10(rel)) {
99398
+ throw unsafeOwnership(source3, path4);
99399
+ }
99400
+ return absolute;
99401
+ }
99402
+ function unsafeOwnership(source3, path4) {
99403
+ return new ContextError(ExitCode.WorkspaceStateError, `source '${source3.name}' has an unsafe managed path`, {
99404
+ category: ErrorCategory.WorkspaceStateInvalid,
99405
+ code: "source-remove-ownership-invalid",
99406
+ source: `${source3.type}:${source3.name}`,
99407
+ path: path4,
99408
+ next: "Repair the source registry or snapshot manifest, then preview source removal again."
99409
+ });
99410
+ }
99411
+ function projectRelative(projectRoot, path4) {
99412
+ return relative15(projectRoot, path4).split(sep3).join("/");
99413
+ }
99414
+ function digest4(value) {
99415
+ return `sha256:${createHash25("sha256").update(JSON.stringify(value)).digest("hex")}`;
99416
+ }
99417
+ async function sharedMaterializedOwners(projectRoot, source3) {
99418
+ const target = safeManagedMaterializedPath(projectRoot, source3);
99419
+ if (target === undefined)
99420
+ return [];
99421
+ const registry2 = await loadSourcesRegistry({ rootDir: projectRoot });
99422
+ const entries = source3.type === "repo" ? registry2.repos : source3.type === "file" ? registry2.files : registry2.larks;
99423
+ return entries.filter((entry) => entry.name !== source3.name && safeManagedMaterializedPath(projectRoot, {
99424
+ type: source3.type,
99425
+ id: entry.id,
99426
+ name: entry.name,
99427
+ materializedAt: entry.materializedAt
99428
+ }) === target).map((entry) => `${source3.type}:${entry.name}`).sort();
99429
+ }
99430
+ function documentOwnedPaths(projectRoot, source3, snapshot) {
99431
+ return [...new Set([
99432
+ ...snapshot.files.map((entry) => entry.path),
99433
+ ...snapshot.assets?.map((entry) => entry.path) ?? [],
99434
+ ...snapshot.metadata?.capture?.routeFiles?.map((entry) => entry.path) ?? []
99435
+ ].map((path4) => safeManagedSnapshotChild(projectRoot, source3, path4)))].sort();
99436
+ }
99437
+ async function createRemovalPlan(projectRoot, selector) {
99438
+ const source3 = await resolveRemovableSource(projectRoot, selector);
99439
+ const references = await collectSourceReferences(projectRoot, source3);
99440
+ const registryWrite = await registryRemovalWrite(projectRoot, source3);
99441
+ const sharedMaterializedBy = await sharedMaterializedOwners(projectRoot, source3);
99442
+ const absoluteRemovals = [];
99443
+ let manifestWrite;
99444
+ let cleanup = {
99445
+ mode: "registry-only",
99446
+ sharedMaterializedBy,
99447
+ filesToRemove: [],
99448
+ directoriesToRemove: []
99449
+ };
99450
+ if (source3.type === "file" || source3.type === "lark") {
99451
+ const manifestPath = safeManagedManifestPath(projectRoot, source3);
99452
+ let removal;
99453
+ try {
99454
+ removal = removeDocumentSnapshotFromManifestFile({
99455
+ current: await readDocumentManifestFile(manifestPath),
99456
+ sourceName: source3.name
99457
+ });
99458
+ } catch (error) {
99459
+ const message = error instanceof Error ? error.message : String(error);
99460
+ throw new ContextError(ExitCode.WorkspaceStateError, `cannot prove snapshot ownership for '${source3.name}': ${message}`, {
99461
+ category: ErrorCategory.WorkspaceStateInvalid,
99462
+ code: "source-remove-ownership-invalid",
99463
+ source: `${source3.type}:${source3.name}`,
99464
+ manifest: projectRelative(projectRoot, manifestPath),
99465
+ next: "Repair or recapture the source snapshot, then preview source removal again."
99466
+ });
99467
+ }
99468
+ if (removal.snapshot !== null) {
99469
+ absoluteRemovals.push(...documentOwnedPaths(projectRoot, source3, removal.snapshot));
99470
+ if (removal.next === null) {
99471
+ absoluteRemovals.push(manifestPath);
99472
+ } else {
99473
+ manifestWrite = { path: manifestPath, bytes: renderDocumentManifestFile(removal.next) };
99474
+ }
99475
+ cleanup = {
99476
+ mode: "document-snapshot",
99477
+ sharedMaterializedBy,
99478
+ manifest: projectRelative(projectRoot, manifestPath),
99479
+ manifestEntry: source3.name,
99480
+ filesToRemove: absoluteRemovals.filter((path4) => path4 !== manifestPath).map((path4) => projectRelative(projectRoot, path4)),
99481
+ directoriesToRemove: []
99482
+ };
99483
+ }
99484
+ } else {
99485
+ const materializedPath = safeManagedMaterializedPath(projectRoot, source3);
99486
+ if (materializedPath !== undefined && sharedMaterializedBy.length === 0 && existsSync38(materializedPath)) {
99487
+ absoluteRemovals.push(materializedPath);
99488
+ cleanup = {
99489
+ mode: "exclusive-materialization",
99490
+ sharedMaterializedBy,
99491
+ filesToRemove: [],
99492
+ directoriesToRemove: [projectRelative(projectRoot, materializedPath)]
99493
+ };
99494
+ }
99495
+ }
99496
+ const planDigest = digest4({
99497
+ source: source3,
99498
+ registry: registryPath2(source3.type),
99499
+ registryBytes: registryWrite.bytes,
99500
+ references,
99501
+ cleanup,
99502
+ manifestBytes: manifestWrite?.bytes ?? null
99503
+ });
99504
+ const next = references.length > 0 ? `Remove all listed references, then preview context source remove '${source3.id}' --format json again.` : `context source remove '${source3.id}' --yes --plan-digest '${planDigest}' --format json`;
99505
+ return {
99506
+ action: "preview",
99507
+ source: source3,
99508
+ registry: registryPath2(source3.type),
99509
+ materialized: source3.materializedAt,
99510
+ references,
99511
+ plan_digest: planDigest,
99512
+ cleanup,
99513
+ next,
99514
+ registryWrite,
99515
+ ...manifestWrite !== undefined ? { manifestWrite } : {},
99516
+ absoluteRemovals: [...new Set(absoluteRemovals)].sort()
99517
+ };
99518
+ }
99519
+ function publicRemovalResult(plan, action) {
99520
+ return {
99521
+ action,
99522
+ source: plan.source,
99523
+ registry: plan.registry,
99524
+ materialized: plan.materialized,
99525
+ references: plan.references,
99526
+ plan_digest: plan.plan_digest,
99527
+ cleanup: plan.cleanup,
99528
+ ...action === "preview" && plan.next !== undefined ? { next: plan.next } : {}
99529
+ };
99530
+ }
99083
99531
  async function pruneExtractRuntime(projectRoot, source3) {
99084
99532
  const fingerprintPath = join61(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
99085
99533
  const removedPhaseIds = new Set;
@@ -99130,34 +99578,43 @@ async function pruneExtractRuntime(projectRoot, source3) {
99130
99578
  await visit3(snapshotRoot);
99131
99579
  }
99132
99580
  async function removeProjectSource(input) {
99133
- const source3 = await resolveRemovableSource(input.projectRoot, input.selector);
99134
- const references = await collectSourceReferences(input.projectRoot, source3);
99135
- const result = {
99136
- action: input.apply ? "removed" : "preview",
99137
- source: source3,
99138
- registry: registryPath2(source3.type),
99139
- materialized: source3.materializedAt,
99140
- references,
99141
- ...!input.apply ? { next: `Remove all listed references, then run context source remove '${source3.id}' --yes --format json.` } : {}
99142
- };
99143
- if (!input.apply)
99144
- return result;
99145
- if (references.length > 0) {
99146
- throw new ContextError(ExitCode.WorkspaceStateError, `source '${source3.name}' is still referenced`, {
99147
- category: ErrorCategory.WorkspaceStateInvalid,
99148
- code: "source-remove-referenced",
99149
- source: `${source3.type}:${source3.name}`,
99150
- references,
99151
- next: `Remove the listed project/candidate/knowledge references, then rerun context source remove '${source3.id}' --yes --format json.`
99581
+ if (!input.apply) {
99582
+ return publicRemovalResult(await createRemovalPlan(input.projectRoot, input.selector), "preview");
99583
+ }
99584
+ if (input.planDigest === undefined) {
99585
+ throw new ContextError(ExitCode.UserError, "source removal requires a digest-bound preview", {
99586
+ category: ErrorCategory.UserInputInvalid,
99587
+ code: "source-remove-plan-required",
99588
+ next: `Run context source remove '${input.selector}' --format json, inspect cleanup, then execute its next command.`
99152
99589
  });
99153
99590
  }
99154
99591
  return withProjectWriteLock(input.projectRoot, "source-remove", async () => {
99155
- await removeRegistryEntry(input.projectRoot, source3);
99156
- const managed = safeManagedMaterializedPath(input.projectRoot, source3);
99157
- if (managed !== undefined)
99158
- await rm15(managed, { recursive: true, force: true });
99159
- await pruneExtractRuntime(input.projectRoot, source3);
99160
- return result;
99592
+ const plan = await createRemovalPlan(input.projectRoot, input.selector);
99593
+ if (plan.plan_digest !== input.planDigest) {
99594
+ throw new ContextError(ExitCode.WorkspaceStateError, "source removal preview is stale", {
99595
+ category: ErrorCategory.WorkspaceStateInvalid,
99596
+ code: "source-remove-plan-stale",
99597
+ expected: input.planDigest,
99598
+ actual: plan.plan_digest,
99599
+ next: `Run context source remove '${plan.source.id}' --format json again and inspect the new cleanup plan.`
99600
+ });
99601
+ }
99602
+ if (plan.references.length > 0) {
99603
+ throw new ContextError(ExitCode.WorkspaceStateError, `source '${plan.source.name}' is still referenced`, {
99604
+ category: ErrorCategory.WorkspaceStateInvalid,
99605
+ code: "source-remove-referenced",
99606
+ source: `${plan.source.type}:${plan.source.name}`,
99607
+ references: plan.references,
99608
+ next: `Remove the listed project/candidate/knowledge references, then preview context source remove '${plan.source.id}' --format json again.`
99609
+ });
99610
+ }
99611
+ await applyAtomicFileBatch({
99612
+ transactionRoot: join61(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
99613
+ writes: [plan.registryWrite, ...plan.manifestWrite !== undefined ? [plan.manifestWrite] : []],
99614
+ removals: plan.absoluteRemovals
99615
+ });
99616
+ await pruneExtractRuntime(input.projectRoot, plan.source);
99617
+ return publicRemovalResult(plan, "removed");
99161
99618
  });
99162
99619
  }
99163
99620
 
@@ -99490,14 +99947,15 @@ the CLI derives a lowercase path-safe module and rejects duplicate batch identit
99490
99947
  const format = assertChoice(options.format, ["json", "yaml"], "--format");
99491
99948
  writeFormatted(projectSource, format);
99492
99949
  });
99493
- source3.command("remove <id>").description("Preview or remove one unreferenced source and its managed snapshot").option("--yes", "apply the removal after reference checks").option("--format <format>", "output format: json | yaml | table", "json").action(async (id2, ...args) => {
99950
+ source3.command("remove <id>").description("Preview or remove one unreferenced source and its managed snapshot").option("--yes", "apply the removal after reference checks").option("--plan-digest <digest>", "bind --yes to the exact previewed cleanup plan").option("--format <format>", "output format: json | yaml | table", "json").action(async (id2, ...args) => {
99494
99951
  const options = actionOptions(...args);
99495
99952
  const projectRoot = requireProjectRoot(process.cwd(), "source remove");
99496
99953
  const format = assertChoice(options.format, DATA_FORMATS, "--format");
99497
99954
  const result = await removeProjectSource({
99498
99955
  projectRoot,
99499
99956
  selector: id2,
99500
- apply: options.yes === true
99957
+ apply: options.yes === true,
99958
+ ...typeof options.planDigest === "string" ? { planDigest: options.planDigest } : {}
99501
99959
  });
99502
99960
  writeFormatted(result, format);
99503
99961
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c4a/context-cli",
3
- "version": "0.6.1-beta.5",
3
+ "version": "0.6.1-beta.6",
4
4
  "type": "module",
5
5
  "description": "Local CLI for capturing, compiling, and governing knowledge workspaces",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@c4a/agent-graph": "0.2.3",
24
- "@c4a/context": "0.6.1-beta.5",
24
+ "@c4a/context": "0.6.1-beta.6",
25
25
  "commander": "^11.0.0",
26
26
  "fast-xml-parser": "^5.10.1",
27
27
  "handlebars": "^4.7.8",
package/plugins/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.1-beta.5
1
+ 0.6.1-beta.6
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "context",
3
3
  "description": "Maintain a project-local knowledge workspace through init and next-step agent guidance.",
4
- "version": "0.6.1-beta.5",
4
+ "version": "0.6.1-beta.6",
5
5
  "author": {
6
6
  "name": "c4a"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context",
3
- "version": "0.6.1-beta.5",
3
+ "version": "0.6.1-beta.6",
4
4
  "description": "Maintain a project-local knowledge workspace through init and next-step agent guidance.",
5
5
  "author": {
6
6
  "name": "c4a"
@@ -18,7 +18,7 @@
18
18
  "skills": "./skills/",
19
19
  "interface": {
20
20
  "displayName": "C4A Context",
21
- "shortDescription": "Initialize and advance a local, source-linked project knowledge workspace.\nv0.6.1-beta.5",
21
+ "shortDescription": "Initialize and advance a local, source-linked project knowledge workspace.\nv0.6.1-beta.6",
22
22
  "longDescription": "Create a Context workspace and use agent-guided next steps to register sources, run extraction, review candidates, build package outputs, and verify health without silently mutating source repositories.",
23
23
  "developerName": "c4a",
24
24
  "category": "Productivity",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "context",
3
3
  "displayName": "C4A Context",
4
- "version": "0.6.1-beta.5",
4
+ "version": "0.6.1-beta.6",
5
5
  "description": "Maintain a project-local knowledge workspace through init and next-step agent guidance.",
6
6
  "author": {
7
7
  "name": "Context4AI",
@@ -129,8 +129,8 @@ nodes:
129
129
  resources:
130
130
  required:
131
131
  - resources/procedures/prose-compile.md
132
- - resources/views/structure-current.yaml
133
132
  recommended:
133
+ - resources/views/structure-current.yaml
134
134
  - resources/semantic/compile/index.md
135
135
 
136
136
  - id: resume-review-current-batch
@@ -2,14 +2,14 @@
2
2
  "schema": "agent-graph.bundle.v1",
3
3
  "provider": {
4
4
  "id": "c4a/context",
5
- "version": "0.6.1-beta.5"
5
+ "version": "0.6.1-beta.6"
6
6
  },
7
7
  "providerManifest": "provider.yaml",
8
8
  "graphs": [
9
9
  {
10
10
  "id": "workspace",
11
11
  "path": "graphs/workspace.yaml",
12
- "digest": "sha256:9f434f63af920ad70aee7bb7b74c3a4be2ecfdad1c9c39a5ee0927f917857325"
12
+ "digest": "sha256:4b344cb9ecc03990f2a2f35aca372949917510f5957c4760fe69a651368eddbc"
13
13
  }
14
14
  ],
15
15
  "actions": [
@@ -153,7 +153,7 @@
153
153
  {
154
154
  "id": "context.sdk.project-api",
155
155
  "path": "resources/manuals/reference/project-api.md",
156
- "digest": "sha256:c3a306c5022de31f501211a23bdbceb741d6069b087c138a6db70bd8071e87dd"
156
+ "digest": "sha256:9ab0be26aeccea79f561c7ecd01636beb97b7088ea1ddb141c3cbc96af758508"
157
157
  },
158
158
  {
159
159
  "id": "context.sdk.template-variables",
@@ -313,17 +313,17 @@
313
313
  {
314
314
  "id": "procedure.prose-compile",
315
315
  "path": "resources/procedures/prose-compile.md",
316
- "digest": "sha256:a0b2a8142f964732fb634d47f8e0af094a7377ce939b111de8c8f40df7dede02"
316
+ "digest": "sha256:c36ba3dcf9aa11a8759afaab2c9a99859c31acaf5f06456c4e0e6793130b7d6e"
317
317
  },
318
318
  {
319
319
  "id": "procedure.source-boundary",
320
320
  "path": "resources/procedures/source-boundary.md",
321
- "digest": "sha256:ccd32232599c90312a1fac930363bc7486ede1842a702a524cdc2fe4ce9a1467"
321
+ "digest": "sha256:99097947961d7fa5b17f9e6b9c9471aac38cec1943bd7feb414b21d6a1e26a49"
322
322
  },
323
323
  {
324
324
  "id": "procedure.source-capture-detailed",
325
325
  "path": "resources/procedures/source-capture-detailed.md",
326
- "digest": "sha256:c62f54d99adc3d4e7902e10ed3bf4ca066ddb5d85949d8ea44e6800f8c699c3f"
326
+ "digest": "sha256:0cf764a43c33fbcf86b32a3731fec5905ebf0d312b7939059aae99a6ab0532e2"
327
327
  },
328
328
  {
329
329
  "id": "procedure.verify-and-repair",
@@ -447,11 +447,11 @@
447
447
  },
448
448
  {
449
449
  "path": "graphs/workspace.yaml",
450
- "digest": "sha256:9f434f63af920ad70aee7bb7b74c3a4be2ecfdad1c9c39a5ee0927f917857325"
450
+ "digest": "sha256:4b344cb9ecc03990f2a2f35aca372949917510f5957c4760fe69a651368eddbc"
451
451
  },
452
452
  {
453
453
  "path": "provider.yaml",
454
- "digest": "sha256:6c17411a95ef31e6b29eff1bc4a2ed89421cb126852cfaffd6fb5c553ef96cd8"
454
+ "digest": "sha256:ad1b825734418814f1ac3673d329070228b6b8004cd821c39a3224c6d30935dd"
455
455
  },
456
456
  {
457
457
  "path": "resources/diagnostics/projection-stale.md",
@@ -511,7 +511,7 @@
511
511
  },
512
512
  {
513
513
  "path": "resources/manuals/reference/project-api.md",
514
- "digest": "sha256:c3a306c5022de31f501211a23bdbceb741d6069b087c138a6db70bd8071e87dd"
514
+ "digest": "sha256:9ab0be26aeccea79f561c7ecd01636beb97b7088ea1ddb141c3cbc96af758508"
515
515
  },
516
516
  {
517
517
  "path": "resources/manuals/reference/template-variables.md",
@@ -555,15 +555,15 @@
555
555
  },
556
556
  {
557
557
  "path": "resources/procedures/prose-compile.md",
558
- "digest": "sha256:a0b2a8142f964732fb634d47f8e0af094a7377ce939b111de8c8f40df7dede02"
558
+ "digest": "sha256:c36ba3dcf9aa11a8759afaab2c9a99859c31acaf5f06456c4e0e6793130b7d6e"
559
559
  },
560
560
  {
561
561
  "path": "resources/procedures/source-boundary.md",
562
- "digest": "sha256:ccd32232599c90312a1fac930363bc7486ede1842a702a524cdc2fe4ce9a1467"
562
+ "digest": "sha256:99097947961d7fa5b17f9e6b9c9471aac38cec1943bd7feb414b21d6a1e26a49"
563
563
  },
564
564
  {
565
565
  "path": "resources/procedures/source-capture-detailed.md",
566
- "digest": "sha256:c62f54d99adc3d4e7902e10ed3bf4ca066ddb5d85949d8ea44e6800f8c699c3f"
566
+ "digest": "sha256:0cf764a43c33fbcf86b32a3731fec5905ebf0d312b7939059aae99a6ab0532e2"
567
567
  },
568
568
  {
569
569
  "path": "resources/procedures/verify-and-repair.md",
@@ -633,5 +633,5 @@
633
633
  "graphDependencies": {
634
634
  "workspace": []
635
635
  },
636
- "digest": "sha256:219c68c8c55ac6cfe34d968bc96271fc418ad31f0b6bf4c5ef48998292a2dad9"
636
+ "digest": "sha256:9496e5137e372e4711605465046811bf322c53603bc7b8c5fbfa07df0862d0eb"
637
637
  }
@@ -1,6 +1,6 @@
1
1
  schema: agent-graph.provider.v1
2
2
  id: c4a/context
3
- version: 0.6.1-beta.5
3
+ version: 0.6.1-beta.6
4
4
  name: Context workflow
5
5
  description: Internal work contract for Context knowledge workspaces.
6
6
  graphs:
@@ -343,9 +343,12 @@ redacted XML audit asset, projects supported blocks deterministically into
343
343
  readable Markdown, and registers external resources such as document citations,
344
344
  images, video, whiteboards, and Base references in the snapshot manifest even
345
345
  when their binary content is not downloaded. The projection does not infer or
346
- summarize document meaning. Its fidelity report closes discovered blocks against
347
- converted and intentionally skipped blocks; a non-empty unsupported block is a
348
- fidelity error and prevents downstream Review until capture support is fixed.
346
+ summarize document meaning. Its fidelity report closes discovered blocks
347
+ against converted and intentionally skipped blocks and reports evidence
348
+ completeness separately from Markdown projection quality. Unknown non-empty XML
349
+ blocks receive a generic, auditable, non-interactive projection and do not block
350
+ downstream work. Missing source content or unresolved external-resource identity
351
+ remains an evidence error and prevents downstream Review.
349
352
  Snapshot files live under `sources/lark/<date>/` as sibling document files
350
353
  tracked by one date-level `manifest.json`. Access credentials and transient
351
354
  signed media URLs are not written into the workspace.
@@ -15,6 +15,9 @@ by the current Route. Do not create compile-action payloads or rewrite source
15
15
  content.
16
16
 
17
17
  One compile command validates every owned view first, then atomically
18
- materializes the source/collection candidate batch. Re-evaluate the workspace
19
- afterward so another structure slot can be routed to its own phase. Do not open
20
- a partial Review while planned views remain.
18
+ materializes the source/collection candidate batch. In an explicitly managed
19
+ conversation, the host loop may continue across the remaining deterministic
20
+ compile slots after this procedure has been read; it re-evaluates revision and
21
+ validation state after every write. The current structure view is optional
22
+ inspection context because the CLI consumes the confirmed structure directly.
23
+ Do not open a partial Review while planned views remain.
@@ -34,7 +34,12 @@ To retire a registered source, first run `context source remove <source-id>
34
34
  --format json`. This is a read-only preview that lists every project, candidate,
35
35
  or approved-knowledge reference. Only after those references are intentionally
36
36
  resolved may the route use `--yes`; the CLI never silently deletes referenced
37
- knowledge or another source's materialized files.
37
+ knowledge or another source's materialized files. Execute the exact
38
+ digest-bound command returned by the preview. For a shared document batch, the
39
+ manifest entry is the ownership boundary: removal deletes only that entry and
40
+ its explicitly listed files/assets. An uncaptured module has no manifest entry,
41
+ so removal is registry-only; the shared date directory is never inferred as
42
+ module-owned.
38
43
 
39
44
  Repository readiness checks are mechanical and may run after the boundary is
40
45
  registered. Clone, fetch, checkout, install, build, test, and other external
@@ -54,12 +54,15 @@ contract, so any manual edit breaks idempotency.
54
54
  Lark capture obtains the structured XML representation and produces two
55
55
  separate artifacts: XML audit evidence and a deterministic readable Markdown
56
56
  projection. Do not treat raw XML as Markdown and do not rewrite it yourself.
57
- Inspect the returned fidelity status and diagnostics. Warnings describe an
58
- explicitly skipped empty block; errors mean non-empty evidence or a resource
59
- locator could not be preserved and the workflow must not proceed to Align,
60
- Compile, or Review. The CLI, not the plugin, parses the document. The plugin
61
- only displays the structured counts and diagnostics returned by the current
62
- source resource.
57
+ Inspect `evidence_status` and `projection_status` separately. An evidence error
58
+ means the source body, external content, or a stable resource locator could not
59
+ be preserved, so the workflow must not proceed to Align, Compile, or Review.
60
+ A projection warning or `generic` status means the original XML is preserved
61
+ and a deterministic non-interactive Markdown fallback was emitted; report the
62
+ diagnostic, but continue through the Route. The CLI, not the plugin, parses the
63
+ document. The plugin only displays the structured counts and diagnostics
64
+ returned by the current source resource. Do not patch the CLI or edit snapshots
65
+ to add a renderer from a user workspace.
63
66
 
64
67
  ### Code capture diagnostics
65
68