@ansonlai/docx-redline-js 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/AGENTS.md +589 -287
  2. package/ARCHITECTURE.md +215 -9
  3. package/CHANGELOG.md +319 -0
  4. package/README.md +604 -360
  5. package/adapters/config.js +45 -43
  6. package/bin/docx-redline.js +3 -0
  7. package/core/list-targeting.js +101 -110
  8. package/core/paragraph-targeting.js +501 -61
  9. package/core/paragraph-text.js +209 -0
  10. package/core/revision-cloning.js +38 -0
  11. package/core/types.js +64 -10
  12. package/core/word-xml.js +43 -15
  13. package/dist/docx-redline-js.esm.js +2849 -466
  14. package/dist/docx-redline-js.esm.js.map +4 -4
  15. package/dist/docx-redline-js.esm.min.js +87 -76
  16. package/dist/docx-redline-js.esm.min.js.map +4 -4
  17. package/docs/TESTING.md +342 -23
  18. package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +1669 -0
  19. package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +669 -0
  20. package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +427 -0
  21. package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +519 -0
  22. package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +69 -0
  23. package/docs/plans/completed/structural-revision-capability-matrix.md +115 -0
  24. package/docs/schemas/document-operations.schema.json +109 -0
  25. package/docs/test-comparison-dashboard.html +4250 -7
  26. package/engine/formatting-removal.js +11 -2
  27. package/engine/oxml-engine.js +491 -336
  28. package/engine/reconstruction-mode.js +15 -14
  29. package/engine/reconstruction-writer.js +247 -142
  30. package/engine/route-selection.js +35 -0
  31. package/engine/rpr-helpers.js +334 -35
  32. package/engine/run-builders.js +239 -196
  33. package/engine/surgical-diff-application.js +222 -37
  34. package/engine/surgical-mode.js +134 -6
  35. package/engine/surgical-spans.js +52 -1
  36. package/engine/table-cell-context.js +3 -6
  37. package/engine/table-mode.js +1 -1
  38. package/index.d.ts +234 -6
  39. package/index.js +24 -1
  40. package/node/cli.js +317 -0
  41. package/node/docx-document.js +302 -0
  42. package/node/index.d.ts +31 -0
  43. package/node/index.js +2 -0
  44. package/node/zip-archive.js +52 -0
  45. package/orchestration/list-markdown.js +10 -16
  46. package/orchestration/list-parsing.js +7 -12
  47. package/orchestration/list-structural-fallback.js +21 -10
  48. package/package.json +24 -3
  49. package/pipeline/content-analysis.js +12 -17
  50. package/pipeline/ingestion-export.js +3 -31
  51. package/pipeline/ingestion-paragraph.js +10 -5
  52. package/pipeline/list-generation.js +150 -55
  53. package/pipeline/list-markers.js +70 -3
  54. package/pipeline/serialization.js +4 -2
  55. package/pipeline/structured-content.js +160 -0
  56. package/scripts/apply_changes.mjs +27 -0
  57. package/scripts/benchmark-operation-session.mjs +137 -0
  58. package/scripts/benchmark-targeting-browser.html +74 -0
  59. package/scripts/benchmark-targeting-hot-paths.mjs +67 -0
  60. package/scripts/benchmark-test-runner.mjs +59 -0
  61. package/scripts/build-test-dashboard.mjs +23 -0
  62. package/scripts/export-lane1-fixtures.mjs +380 -0
  63. package/scripts/export-reredline-stress-fixtures.mjs +317 -0
  64. package/scripts/export-validation-fixtures.mjs +1 -1
  65. package/scripts/extract_text.mjs +7 -0
  66. package/scripts/generate-paragraph-boundary-fixtures.ps1 +215 -0
  67. package/scripts/generate-test-dashboard.mjs +362 -11
  68. package/scripts/lib/word-coverage-catalogue.mjs +6 -2
  69. package/scripts/profile-route-selection.mjs +19 -0
  70. package/scripts/render-agenda-multilevel.mjs +0 -5
  71. package/scripts/render-multilevel-cases.mjs +0 -1
  72. package/scripts/run-tests.mjs +107 -35
  73. package/scripts/word-com-corpus-suite.ps1 +3 -0
  74. package/scripts/word-com-differential.ps1 +64 -4
  75. package/scripts/word-com-suite.ps1 +3 -0
  76. package/services/batch-operation-orchestrator.js +494 -0
  77. package/services/capture-engine.js +226 -0
  78. package/services/comment-builders.js +23 -6
  79. package/services/comment-engine.js +108 -47
  80. package/services/comment-locator.js +187 -82
  81. package/services/comment-replies.js +95 -0
  82. package/services/document-inspection.js +258 -0
  83. package/services/document-operation-applier.js +372 -0
  84. package/services/document-operation-contract.js +323 -0
  85. package/services/document-operation-mutations.js +1733 -0
  86. package/services/document-operation-session.js +258 -0
  87. package/services/numbering-service.js +14 -5
  88. package/services/operation-heuristics.js +173 -0
  89. package/services/operation-preflight.js +366 -0
  90. package/services/receipt-collector.js +288 -0
  91. package/services/revision-comment-management.js +37 -5
  92. package/services/revision-token.js +290 -0
  93. package/services/standalone-docx-plumbing.js +123 -8
  94. package/services/standalone-operation-runner.d.ts +296 -0
  95. package/services/standalone-operation-runner.js +10 -1455
  96. package/services/table-reconciliation.js +15 -6
  97. package/docs/VALIDATION.md +0 -183
  98. package/docs/WORD-MANUAL-REVIEW.md +0 -138
  99. package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +0 -210
  100. /package/docs/plans/{2026-08-30-reliability-testing-improvements.md → completed/2026-08-30-reliability-testing-improvements.md} +0 -0
package/index.d.ts CHANGED
@@ -1,10 +1,61 @@
1
1
  export type OoxmlSourceType = 'package' | 'document' | 'fragment';
2
2
  export type RedlineStatus = 'ok' | 'no-op' | 'error';
3
- export type ExistingRevisionsPolicy = 'reject-input' | 'accept-all-first' | 'accept-all-first-keep-normalized';
3
+ export type ExistingRevisionsPolicy = 'merge-same-author' | 'reject-input' | 'accept-all-first' | 'accept-all-first-keep-normalized';
4
+ export type RevisionView = 'accepted' | 'rejected';
5
+
6
+ export interface RevisionTextSegment {
7
+ text: string;
8
+ kind: 'baseline' | 'insertion' | 'deletion' | 'move_from' | 'move_to';
9
+ author?: string;
10
+ revisionId?: string;
11
+ acceptedStart: number | null;
12
+ rejectedStart: number | null;
13
+ }
14
+
15
+ import type { InsertionAffinity } from './services/standalone-operation-runner.js';
16
+
17
+ export type {
18
+ BatchOperationItemResult,
19
+ CommentDocumentOperation,
20
+ CommentReplyDocumentOperation,
21
+ DeleteDocumentOperation,
22
+ DocumentOperation,
23
+ DocumentOperationBatchResult,
24
+ HighlightDocumentOperation,
25
+ InsertionAffinity,
26
+ OperationConflict,
27
+ OperationDependencyPlan,
28
+ OperationPreflightItemResult,
29
+ OperationPreflightResult,
30
+ ParagraphTargetDescriptor,
31
+ RedlineDocumentOperation,
32
+ ResolvedCommentAnchor,
33
+ ResolvedDocumentTarget,
34
+ StandaloneRunnerOptions
35
+ } from './services/standalone-operation-runner.js';
4
36
 
5
37
  export interface RedlineError {
6
- code: 'PARSE_ERROR' | 'TARGET_NOT_FOUND' | 'EXISTING_REVISIONS' | 'DIFF_TOKEN_LIMIT' | string;
38
+ code:
39
+ | 'PARSE_ERROR'
40
+ | 'TARGET_NOT_FOUND'
41
+ | 'EXISTING_REVISIONS'
42
+ | 'DIFF_TOKEN_LIMIT'
43
+ | 'UNSUPPORTED_REVISION_VIEW_MUTATION'
44
+ | 'UNSUPPORTED_INSERTION_AFFINITY'
45
+ | 'UNSAFE_PARAGRAPH_BOUNDARY'
46
+ | 'TARGET_INDEX_MISMATCH'
47
+ | 'REVISION_MISMATCH'
48
+ | 'REVISION_TOKEN_SCOPE_MISMATCH'
49
+ | 'INVALID_REVISION_TOKEN'
50
+ | 'DUPLICATE_CAPTURE_KEY'
51
+ | 'CAPTURE_NOT_FOUND'
52
+ | 'CAPTURE_DEPENDENCY_CYCLE'
53
+ | 'AMBIGUOUS_CAPTURE_SELECTION'
54
+ | 'CAPTURE_STALE'
55
+ | string;
7
56
  message: string;
57
+ commentIds?: string[];
58
+ comments?: Array<{ id: string; author: string; text: string }>;
8
59
  }
9
60
 
10
61
  export interface RedlineOptions {
@@ -14,6 +65,9 @@ export interface RedlineOptions {
14
65
  existingRevisions?: ExistingRevisionsPolicy;
15
66
  removeFormatting?: boolean;
16
67
  sanitizeInput?: boolean;
68
+ structuredContent?: boolean;
69
+ pairReplacements?: boolean;
70
+ insertionAffinity?: InsertionAffinity;
17
71
  [key: string]: unknown;
18
72
  }
19
73
 
@@ -78,6 +132,28 @@ export interface IngestionTextResult {
78
132
  warnings?: string[];
79
133
  }
80
134
 
135
+ export interface ParagraphTargetCandidate {
136
+ paragraph: Element;
137
+ index: number;
138
+ inTable: boolean;
139
+ paragraphId: string | null;
140
+ fingerprint: string | null;
141
+ text: string;
142
+ }
143
+
144
+ export interface ParagraphTargetResolution {
145
+ paragraph: Element;
146
+ resolvedBy:
147
+ | 'ref'
148
+ | 'paragraph_id'
149
+ | 'occurrence'
150
+ | 'fingerprint'
151
+ | 'strict_text'
152
+ | 'fuzzy_text'
153
+ | 'strict_text_after_ref_drift'
154
+ | 'fuzzy_text_after_ref_drift';
155
+ }
156
+
81
157
  export interface XmlProvider {
82
158
  DOMParser: typeof DOMParser;
83
159
  XMLSerializer: typeof XMLSerializer;
@@ -94,6 +170,7 @@ export interface DocumentOperationResult {
94
170
  hasChanges: boolean;
95
171
  numberingXml?: string | null;
96
172
  commentsXml?: string | null;
173
+ commentsExtendedXml?: string | null;
97
174
  warnings?: string[];
98
175
  status?: RedlineStatus;
99
176
  error?: RedlineError;
@@ -128,10 +205,86 @@ export function reconcileMarkdownTableOoxml(
128
205
  options?: RedlineOptions
129
206
  ): Promise<TableReconciliationResult>;
130
207
 
208
+ export interface StructuredContentIssue {
209
+ severity: 'error' | 'warning';
210
+ code: string;
211
+ line: number;
212
+ message: string;
213
+ }
214
+ export interface StructuredContentBlock {
215
+ type: 'heading' | 'paragraph' | 'list' | 'table';
216
+ markdown: string;
217
+ text?: string;
218
+ level?: number;
219
+ items?: number;
220
+ columns?: number;
221
+ rows?: number;
222
+ hasHeader?: boolean;
223
+ }
224
+ export interface StructuredContentAnalysis {
225
+ valid: boolean;
226
+ normalizedMarkdown: string;
227
+ blocks: StructuredContentBlock[];
228
+ issues: StructuredContentIssue[];
229
+ counts: Record<'heading' | 'paragraph' | 'list' | 'table', number>;
230
+ requiresStructuredContent: boolean;
231
+ }
232
+ export function analyzeStructuredContent(markdown: string): StructuredContentAnalysis;
233
+ export function planStructuredReplacement(
234
+ target: string | import('./services/standalone-operation-runner.js').ParagraphTargetDescriptor,
235
+ markdown: string,
236
+ options?: { author?: string; generateRedlines?: boolean; existingRevisions?: ExistingRevisionsPolicy }
237
+ ): StructuredContentAnalysis & { operation: import('./services/standalone-operation-runner.js').RedlineDocumentOperation | null };
238
+
131
239
  export function ingestWordOoxmlToPlainText(oxml: string): string;
132
240
  export function ingestWordOoxmlToMarkdown(oxml: string): string;
133
241
  export function ingestWordOoxmlToPlainTextResult(oxml: unknown): IngestionTextResult;
134
242
  export function ingestWordOoxmlToMarkdownResult(oxml: unknown): IngestionTextResult;
243
+ export function extractCanonicalParagraphText(paragraph: Element | null | undefined, options?: { revisionView?: 'accepted' | 'rejected' | 'current' }): string;
244
+ export function extractParagraphRevisionSegments(paragraph: Element | null | undefined, options?: { mergeRuns?: boolean; [key: string]: unknown }): RevisionTextSegment[];
245
+ export function readCanonicalRunText(run: Element, options?: { revisionView?: 'accepted' | 'rejected' | 'current'; boundary?: Element | null }): string;
246
+ export function isNodeVisibleInRevisionView(node: Node, boundary?: Node | null, revisionView?: 'accepted' | 'rejected' | 'current'): boolean;
247
+ export function containsTrackedChanges(xmlDoc: Document | Element | null | undefined): boolean;
248
+ export function getTrackedChangeAuthors(xmlDocOrElement: Document | Element | null | undefined): string[];
249
+
250
+ export interface InspectedParagraph {
251
+ index: number; ref: string; paragraphId: string | null; fingerprint: string | null;
252
+ text: string; exactText: string; excerpt: string; inTable: boolean;
253
+ humanReference: string; styleId: string | null;
254
+ table: { tableIndex: number; rowIndex: number; cellIndex: number } | null;
255
+ structuralReferences: Array<{ type: 'footnote' | 'endnote' | 'comment'; id: string | null }>;
256
+ headingLevel: number | null; nearestHeading: { level: number; text: string } | null;
257
+ list: { numId: string; level: number; label: string | null; format: string | null } | null;
258
+ hasRevisions: boolean; revisionAuthors: string[]; commentIds: string[];
259
+ segments: RevisionTextSegment[];
260
+ }
261
+ export interface RevisionToken {
262
+ algorithm: 'sha256';
263
+ version: 1;
264
+ scope: 'document-parts' | 'package';
265
+ value: string;
266
+ coveredParts?: string[];
267
+ }
268
+
269
+ export interface InspectedComment { id: string; author: string | null; date: string | null; text: string; paraId?: string | null; parentParaId?: string; parentCommentId?: string | null; done?: boolean; paragraphIndex?: number; targetRef?: string; anchoredText?: string; }
270
+ export interface DocumentInspectionOptions { revisionView?: 'accepted' | 'rejected' | 'current'; excerptLength?: number; revisedOnly?: boolean; inTable?: boolean; skipEmpty?: boolean; search?: string; indexes?: number[]; range?: { start: number; end: number } | [number, number]; digestFn?: (bytes: Uint8Array) => string; }
271
+ export interface DocumentInspectionResult {
272
+ status: 'ok' | 'error'; paragraphs: InspectedParagraph[]; comments: InspectedComment[];
273
+ revisionAuthors?: string[]; commentAuthors?: string[]; counts?: { paragraphs: number; comments: number; revisedParagraphs: number };
274
+ revisionToken?: RevisionToken | null;
275
+ coveredParts?: string[];
276
+ warnings: string[]; error?: RedlineError;
277
+ }
278
+ export function inspectDocumentParts(parts: { documentXml: string; commentsXml?: string | null; commentsExtendedXml?: string | null; numberingXml?: string | null; stylesXml?: string | null; parts?: Map<string, unknown>; additionalParts?: Record<string, unknown> }, options?: DocumentInspectionOptions): DocumentInspectionResult;
279
+ export function buildRevisionTokenFraming(options: { scope: string; entries?: unknown }): { framing: Uint8Array; scope: string; version: number; coveredParts: string[] };
280
+ export function computeRevisionToken(options: { scope: string; entries?: unknown; digestFn?: (bytes: Uint8Array) => Promise<string> }): Promise<RevisionToken>;
281
+ export function computeRevisionTokenSync(options: { scope: string; entries?: unknown; digestFn: (bytes: Uint8Array) => string }): RevisionToken;
282
+ export function computeDocumentPartsRevisionToken(parts: unknown, options?: { digestFn?: (bytes: Uint8Array) => Promise<string> }): Promise<RevisionToken>;
283
+ export function validateRevisionToken(token: unknown): { valid: boolean; error?: { code: string; message: string } };
284
+ export function areRevisionTokensEqual(a: string, b: string): boolean;
285
+ export function normalizeOpcEntryName(name: string): string;
286
+ export function extractDocumentPartsEntries(parts: unknown): Array<{ name: string; payload: unknown }>;
287
+
135
288
  export function ingestOoxml(oxml: string): unknown;
136
289
  export function preprocessMarkdown(text: string): { cleanText: string; formatHints: unknown[] };
137
290
  export function serializeToOoxml(runModel: unknown[], pPrXml?: string | null, formatHints?: unknown[], options?: Record<string, unknown>): string;
@@ -139,6 +292,7 @@ export function wrapInDocumentFragment(rawOoxml: string, options?: Record<string
139
292
 
140
293
  export function injectCommentsIntoOoxml(oxml: string, comments: unknown[], options?: Record<string, unknown>): unknown;
141
294
  export function injectCommentsIntoPackage(packageXml: string, comments: unknown[], options?: Record<string, unknown>): unknown;
295
+ export function applyCommentReplyToParts(options: { commentsXml: string; commentsExtendedXml?: string | null; parentCommentId: string | number; commentId: string | number; commentContent: string; author: string; date?: string }): { status: 'ok' | 'error'; hasChanges?: boolean; commentsXml?: string; commentsExtendedXml?: string; commentsXmlMode?: 'replace'; commentsExtendedXmlMode?: 'replace'; parentCommentId?: string; paraId?: string; parentParaId?: string; error?: RedlineError };
142
296
  export function acceptTrackedChangesInOoxml(oxml: string, options?: RevisionFilterOptions): AcceptTrackedChangesResult;
143
297
  export function rejectTrackedChangesInOoxml(oxml: string, options?: RevisionFilterOptions): RejectTrackedChangesResult;
144
298
  export function deleteCommentsByAuthorInOoxml(oxml: string, options?: RevisionFilterOptions): DeleteCommentsResult;
@@ -174,6 +328,7 @@ export function extractReplacementNodesFromOoxml(oxml: string): unknown;
174
328
  export function validateDocxPackage(zip: unknown): Promise<unknown> | unknown;
175
329
  export function ensureNumberingArtifactsInZip(zip: unknown, numberingXml: string): Promise<unknown> | unknown;
176
330
  export function ensureCommentsArtifactsInZip(zip: unknown, commentsXml: string): Promise<unknown> | unknown;
331
+ export function ensureCommentsExtendedArtifactsInZip(zip: unknown, commentsExtendedXml: string): Promise<unknown> | unknown;
177
332
  export function createDynamicNumberingIdState(numberingXml?: string): unknown;
178
333
 
179
334
  export function parseOoxml(ooxml: string): Document | null;
@@ -231,6 +386,8 @@ export function enforceListBindingOnParagraphNodes(nodes: Node[], options?: Reco
231
386
  export function stripSingleLineListMarkerPrefix(text: string): string;
232
387
 
233
388
  export function getParagraphText(paragraph: Element | null | undefined): string;
389
+ export function getParagraphId(paragraph: Element | null | undefined): string | null;
390
+ export function createParagraphFingerprint(paragraph: Element | null | undefined): string | null;
234
391
  export function getDocumentParagraphNodes(xmlDoc: Document | Element | null | undefined): Element[];
235
392
  export function normalizeWhitespaceForTargeting(text: string): string;
236
393
  export function isMarkdownTableText(text: string): boolean;
@@ -241,10 +398,23 @@ export function findContainingWordElement(node: Node | null, localName: string):
241
398
  export function findParagraphByReference(xmlDoc: Document | Element, reference: unknown): Element | null;
242
399
  export function findParagraphByStrictText(xmlDoc: Document | Element, text: string): Element | null;
243
400
  export function findParagraphByBestTextMatch(xmlDoc: Document | Element, text: string): Element | null;
244
- export function resolveTargetParagraph(xmlDoc: Document | Element, options?: Record<string, unknown>): Element | null;
245
- export function buildTargetReferenceSnapshot(paragraph: Element, options?: Record<string, unknown>): Record<string, unknown>;
246
- export function resolveTargetParagraphWithSnapshot(xmlDoc: Document | Element, snapshot: unknown): Element | null;
247
- export function resolveParagraphRangeByRefs(xmlDoc: Document | Element, references: unknown[]): Element[];
401
+ export function findStrictTargetCandidates(xmlDoc: Document | Element, text: string): ParagraphTargetCandidate[];
402
+ export function resolveTargetParagraph(xmlDoc: Document | Element, options?: Record<string, unknown>): ParagraphTargetResolution;
403
+ export function buildTargetReferenceSnapshot(xmlDoc: Document | Element): Map<number, {
404
+ text: string;
405
+ normalizedText: string;
406
+ inTable: boolean;
407
+ }>;
408
+ export function resolveTargetParagraphWithSnapshot(
409
+ xmlDoc: Document | Element,
410
+ options?: Record<string, unknown>
411
+ ): ParagraphTargetResolution;
412
+ export function resolveParagraphRangeByRefs(
413
+ xmlDoc: Document | Element,
414
+ startRef: string | number | null,
415
+ endRef: string | number | null,
416
+ options?: Record<string, unknown>
417
+ ): Element[] | null;
248
418
  export function extractParagraphIdFromOoxml(oxml: string): string | null;
249
419
 
250
420
  export function getParagraphListInfo(paragraph: Element): Record<string, unknown> | null;
@@ -262,3 +432,61 @@ export function insertBodyElementBeforeSectPr(body: Element, element: Element):
262
432
  export function normalizeBodySectionOrderStandalone(documentXml: string): string;
263
433
  export function sanitizeNestedParagraphsInTables(documentXml: string): string;
264
434
  export function getPackagePartName(part: unknown): string | null;
435
+
436
+ export interface MutationReceiptRevisionItem {
437
+ id: string;
438
+ kind: 'ins' | 'del' | 'move_from' | 'move_to' | 'rPrChange' | 'pPrChange' | 'structural';
439
+ partName: string;
440
+ }
441
+
442
+ export interface MutationReceipt {
443
+ operationIndex: number;
444
+ operationId?: string;
445
+ attemptedDisposition: 'applied' | 'no_change' | 'refused' | 'not_attempted';
446
+ finalDisposition: 'applied' | 'no_change' | 'refused' | 'rolled_back' | 'not_attempted';
447
+ committed: boolean;
448
+ authorUsed?: string;
449
+ revisionItems: MutationReceiptRevisionItem[];
450
+ commentIds: string[];
451
+ numberingIds: string[];
452
+ relationshipIds: string[];
453
+ affectedTargets: unknown[];
454
+ warnings: string[];
455
+ }
456
+
457
+ export class ReceiptCollector {
458
+ constructor();
459
+ beginOperation(operationIndex: number, operationId?: string | null, authorUsed?: string | null): void;
460
+ recordRevision(id: string | number, kind?: string, partName?: string): void;
461
+ recordComment(id: string | number, partName?: string): void;
462
+ recordNumbering(id: string | number, partName?: string): void;
463
+ recordRelationship(id: string | number, partName?: string): void;
464
+ recordAffectedTarget(target: unknown): void;
465
+ recordWarning(warning: unknown): void;
466
+ commitOperation(disposition?: string): MutationReceipt | null;
467
+ abortOperation(disposition?: string): MutationReceipt | null;
468
+ createSavepoint(): unknown;
469
+ restoreSavepoint(savepoint: unknown): void;
470
+ clear(): void;
471
+ markRolledBack(): void;
472
+ getReceipts(): MutationReceipt[];
473
+ getCurrentReceipt(): MutationReceipt | null;
474
+ }
475
+
476
+ export function createEmptyReceipt(
477
+ operationIndex: number,
478
+ operationId?: string | null,
479
+ authorUsed?: string | null,
480
+ disposition?: 'applied' | 'no_change' | 'refused' | 'not_attempted'
481
+ ): MutationReceipt;
482
+
483
+ export function reconcileReceiptsAgainstOutput(
484
+ parts: {
485
+ documentXml: string;
486
+ commentsXml?: string | null;
487
+ numberingXml?: string | null;
488
+ numberingXmlParts?: string[];
489
+ relationshipsXml?: string | null;
490
+ },
491
+ receipts: MutationReceipt[]
492
+ ): { valid: boolean; error?: { code: string; message: string } };
package/index.js CHANGED
@@ -21,8 +21,9 @@ import {
21
21
  executeSingleLineListStructuralFallback
22
22
  } from './orchestration/list-structural-fallback.js';
23
23
  import { withOoxmlSourceType } from './core/word-xml.js';
24
- export { containsTrackedChanges } from './core/word-xml.js';
24
+ export { containsTrackedChanges, getTrackedChangeAuthors } from './core/word-xml.js';
25
25
  export { validateRedlineOoxml } from './core/redline-validation.js';
26
+ export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/structured-content.js';
26
27
 
27
28
  /**
28
29
  * Standalone-safe redline wrapper.
@@ -237,6 +238,18 @@ export {
237
238
  ingestWordOoxmlToPlainTextResult,
238
239
  ingestWordOoxmlToMarkdownResult
239
240
  } from './pipeline/ingestion-export.js';
241
+ export { extractCanonicalParagraphText, readCanonicalRunText, isNodeVisibleInRevisionView, extractParagraphRevisionSegments } from './core/paragraph-text.js';
242
+ export { inspectDocumentParts } from './services/document-inspection.js';
243
+ export {
244
+ buildRevisionTokenFraming,
245
+ computeRevisionToken,
246
+ computeRevisionTokenSync,
247
+ computeDocumentPartsRevisionToken,
248
+ validateRevisionToken,
249
+ areRevisionTokensEqual,
250
+ normalizeOpcEntryName,
251
+ extractDocumentPartsEntries
252
+ } from './services/revision-token.js';
240
253
  export { preprocessMarkdown } from './pipeline/markdown-processor.js';
241
254
  export { serializeToOoxml, wrapInDocumentFragment } from './pipeline/serialization.js';
242
255
 
@@ -247,6 +260,7 @@ export {
247
260
  buildCommentElement,
248
261
  buildCommentsPartXml
249
262
  } from './services/comment-engine.js';
263
+ export { applyCommentReplyToParts } from './services/comment-replies.js';
250
264
  export {
251
265
  acceptTrackedChangesInOoxml,
252
266
  rejectTrackedChangesInOoxml,
@@ -272,6 +286,7 @@ export {
272
286
  extractReplacementNodesFromOoxml,
273
287
  ensureNumberingArtifactsInZip,
274
288
  ensureCommentsArtifactsInZip,
289
+ ensureCommentsExtendedArtifactsInZip,
275
290
  validateDocxPackage
276
291
  } from './services/standalone-docx-plumbing.js';
277
292
  export { buildReconciliationPlan, RoutePlanKind, normalizeContentEscapesForRouting } from './orchestration/route-plan.js';
@@ -293,6 +308,8 @@ export { extractParagraphIdFromOoxml } from './core/ooxml-identifiers.js';
293
308
  export {
294
309
  WORD_MAIN_NS,
295
310
  getParagraphText,
311
+ getParagraphId,
312
+ createParagraphFingerprint,
296
313
  getDocumentParagraphNodes,
297
314
  normalizeWhitespaceForTargeting,
298
315
  isMarkdownTableText,
@@ -303,6 +320,7 @@ export {
303
320
  findParagraphByReference,
304
321
  findParagraphByStrictText,
305
322
  findParagraphByBestTextMatch,
323
+ findStrictTargetCandidates,
306
324
  resolveTargetParagraph,
307
325
  buildTargetReferenceSnapshot,
308
326
  resolveTargetParagraphWithSnapshot
@@ -315,3 +333,8 @@ export {
315
333
  planListInsertionOnlyEdit,
316
334
  stripRedundantLeadingListMarkers
317
335
  } from './core/list-targeting.js';
336
+ export {
337
+ ReceiptCollector,
338
+ createEmptyReceipt,
339
+ reconcileReceiptsAgainstOutput
340
+ } from './services/receipt-collector.js';