@ansonlai/docx-redline-js 0.8.3 → 0.9.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 (71) hide show
  1. package/ARCHITECTURE.md +221 -12
  2. package/CHANGELOG.md +803 -586
  3. package/README.md +1018 -901
  4. package/core/current-properties.js +136 -0
  5. package/core/paragraph-text.js +211 -209
  6. package/core/types.js +9 -3
  7. package/core/word-revision-semantics.js +256 -0
  8. package/dist/docx-redline-js.esm.js +4551 -967
  9. package/dist/docx-redline-js.esm.js.map +4 -4
  10. package/dist/docx-redline-js.esm.min.js +112 -106
  11. package/dist/docx-redline-js.esm.min.js.map +4 -4
  12. package/dist/docx-redline.bundle.cjs +4690 -1106
  13. package/dist/docx-redline.bundle.cjs.map +4 -4
  14. package/dist/docx-redline.bundle.js +4690 -1106
  15. package/dist/docx-redline.bundle.js.map +4 -4
  16. package/docs/AGENT_FAST_START.md +2 -0
  17. package/docs/AGENT_KNOWLEDGE_BASE.md +23 -4
  18. package/docs/SKILL_AUTHORING.md +165 -150
  19. package/docs/TESTING.md +1142 -1059
  20. package/docs/schemas/document-operations.schema.json +221 -168
  21. package/document/docx-document.js +1022 -1010
  22. package/engine/oxml-engine.js +567 -560
  23. package/engine/paragraph-mark-revisions.js +292 -0
  24. package/engine/reconstruction-mapper.js +312 -311
  25. package/engine/reconstruction-mode.js +183 -183
  26. package/engine/reconstruction-writer.js +67 -10
  27. package/engine/rpr-helpers.js +42 -0
  28. package/engine/run-builders.js +0 -73
  29. package/engine/surgical-diff-application.js +604 -605
  30. package/engine/table-mode.js +63 -63
  31. package/index.d.ts +51 -1
  32. package/index.js +47 -3
  33. package/node/cli-help.js +1 -0
  34. package/node/cli.js +992 -974
  35. package/orchestration/list-structural-fallback.js +67 -9
  36. package/package.json +1 -1
  37. package/pipeline/diff-engine.js +130 -1
  38. package/pipeline/ingestion-export.js +227 -243
  39. package/pipeline/ingestion-paragraph.js +93 -98
  40. package/pipeline/list-generation.js +415 -419
  41. package/pipeline/pipeline.js +206 -204
  42. package/pipeline/serialization.js +111 -94
  43. package/scripts/export-validation-fixtures.mjs +19 -6
  44. package/scripts/generate-word-revision-semantics-matrix.mjs +134 -0
  45. package/scripts/lib/word-coverage-catalogue.mjs +7 -2
  46. package/scripts/render-multilevel-bullet-visual.ps1 +2 -2
  47. package/scripts/word-com-corpus-visual-suite.ps1 +2 -2
  48. package/scripts/word-com-differential.ps1 +443 -335
  49. package/scripts/word-com-visual-suite.ps1 +2 -2
  50. package/scripts/word-revision-semantics-matrix.ps1 +108 -0
  51. package/services/batch-operation-orchestrator.js +26 -0
  52. package/services/capture-engine.js +228 -226
  53. package/services/document-operation-applier.js +215 -0
  54. package/services/document-operation-contract.js +628 -537
  55. package/services/document-operation-mutations.js +789 -46
  56. package/services/error-recovery.js +17 -0
  57. package/services/list-conversion-diagnostics.js +296 -0
  58. package/services/list-format-change.js +586 -0
  59. package/services/list-format-mutation.js +416 -0
  60. package/services/list-format.js +305 -0
  61. package/services/numbering-allocation.js +642 -0
  62. package/services/numbering-helpers.js +354 -524
  63. package/services/operation-batch-compiler.js +612 -436
  64. package/services/operation-heuristics.js +197 -173
  65. package/services/operation-preflight.js +1 -0
  66. package/services/receipt-collector.js +288 -288
  67. package/services/revision-comment-management.js +634 -594
  68. package/services/revision-fidelity-verifier.js +498 -0
  69. package/services/standalone-docx-plumbing.js +627 -631
  70. package/services/standalone-operation-runner.d.ts +606 -427
  71. package/services/table-reconciliation.js +103 -49
@@ -1,427 +1,606 @@
1
- import type { ExistingRevisionsPolicy, RedlineError, RedlineStatus, RevisionToken } from '../index.js';
2
-
3
- export interface ParagraphTargetDescriptor {
4
- text?: string;
5
- exactText?: string;
6
- index?: number | string;
7
- paragraphIndex?: number | string;
8
- paragraphId?: string;
9
- occurrence?: number;
10
- inTable?: boolean;
11
- fingerprint?: string;
12
- sourceFingerprint?: string;
13
- /** Defaults to rejected for restore operations and accepted for all other operations. */
14
- revisionView?: 'accepted' | 'rejected';
15
- captureRef?: string;
16
- select?: string;
17
- createdByOperation?: number;
18
- }
19
-
20
- export interface InsertionAffinity {
21
- formatting?: 'left' | 'right' | 'none';
22
- hyperlink?: 'inside' | 'outside' | 'preserve';
23
- revision?: 'coalesce_same_author' | 'separate';
24
- bookmark?: 'inside' | 'outside';
25
- comment?: 'inside' | 'outside';
26
- }
27
-
28
- export interface RejectedTextInsertionAnchor {
29
- exactText: string;
30
- occurrence?: number;
31
- offset: number;
32
- }
33
-
34
- export interface LocalizedReplacement {
35
- find: string;
36
- replace: string;
37
- occurrence?: number;
38
- }
39
-
40
- /** A header/footer part path from `inspect().headersFooters`, or a selector. */
41
- export type HeaderFooterPartSelector =
42
- | string
43
- | { kind: 'header' | 'footer'; type?: 'default' | 'first' | 'even'; section?: number };
44
-
45
- export interface DocumentOperationBase {
46
- operationId?: string;
47
- /** Target a header or footer instead of the body. Comments are not supported there. */
48
- part?: HeaderFooterPartSelector;
49
- captureKey?: string;
50
- target?: string | ParagraphTargetDescriptor;
51
- targetRef?: number | string | null;
52
- author?: string;
53
- generateRedlines?: boolean;
54
- existingRevisions?: ExistingRevisionsPolicy;
55
- pairReplacements?: boolean;
56
- insertionAffinity?: InsertionAffinity;
57
- }
58
-
59
- interface RedlineDocumentOperationCommon extends DocumentOperationBase {
60
- structuredContent?: boolean;
61
- targetEnd?: ParagraphTargetDescriptor;
62
- targetEndRef?: number | string | null;
63
- /** Required when type is insert and target.revisionView is rejected. */
64
- anchor?: RejectedTextInsertionAnchor;
65
- }
66
-
67
- export type RedlineDocumentOperation =
68
- | (RedlineDocumentOperationCommon & {
69
- type: 'redline' | 'replace';
70
- modified: string;
71
- replacements?: never;
72
- })
73
- | (RedlineDocumentOperationCommon & {
74
- type: 'redline' | 'replace';
75
- modified?: never;
76
- /** Exact, case-sensitive replacements in one batch-start accepted-view paragraph. */
77
- replacements: LocalizedReplacement[];
78
- })
79
- | (RedlineDocumentOperationCommon & {
80
- type: 'format' | 'list-change' | 'table-reconciliation' | 'insert';
81
- modified: string;
82
- replacements?: never;
83
- });
84
-
85
- interface RestoreDocumentOperationCommon extends DocumentOperationBase {
86
- type: 'restore';
87
- }
88
-
89
- export type RestoreDocumentOperation =
90
- | (RestoreDocumentOperationCommon & {
91
- modified: string | string[];
92
- replacements?: never;
93
- targetEnd?: ParagraphTargetDescriptor;
94
- targetEndRef?: number | string | null;
95
- })
96
- | (RestoreDocumentOperationCommon & {
97
- modified?: never;
98
- /** Exact, case-sensitive replacements in one strong rejected-view paragraph. */
99
- replacements: LocalizedReplacement[];
100
- targetEnd?: never;
101
- targetEndRef?: never;
102
- })
103
- | (RestoreDocumentOperationCommon & {
104
- /** Omit modified to restore one strong rejected-view paragraph verbatim. */
105
- modified?: never;
106
- replacements?: never;
107
- targetEnd?: never;
108
- targetEndRef?: never;
109
- });
110
-
111
- export interface DeleteDocumentOperation extends DocumentOperationBase {
112
- type: 'delete';
113
- modified?: '';
114
- }
115
-
116
- export interface CommentDocumentOperation extends DocumentOperationBase {
117
- type: 'comment';
118
- textToComment?: string;
119
- commentContent: string;
120
- }
121
-
122
- export interface CommentReplyDocumentOperation {
123
- type: 'comment_reply';
124
- operationId?: string;
125
- parentCommentId: number | string;
126
- commentContent: string;
127
- author?: string;
128
- date?: string;
129
- }
130
-
131
- /** Resolves (or with `resolved: false`, reopens) the whole thread containing `commentId`. Body-only. */
132
- export interface CommentResolveDocumentOperation {
133
- type: 'comment_resolve';
134
- operationId?: string;
135
- commentId: number | string;
136
- resolved?: boolean;
137
- }
138
-
139
- export interface CharacterFormatProperties {
140
- bold?: boolean;
141
- italic?: boolean;
142
- underline?: boolean;
143
- strike?: boolean;
144
- strikethrough?: boolean;
145
- highlight?: string | null;
146
- color?: string | null;
147
- fontSize?: number | string | null;
148
- fontFamily?: string | null;
149
- }
150
-
151
- export interface ParagraphFormatProperties {
152
- alignment?: 'left' | 'center' | 'right' | 'both';
153
- keepNext?: boolean;
154
- keepLines?: boolean;
155
- pageBreakBefore?: boolean;
156
- style?: string | null;
157
- }
158
-
159
- export interface HighlightDocumentOperation extends DocumentOperationBase {
160
- type: 'highlight';
161
- textToHighlight: string;
162
- color?: string;
163
- }
164
-
165
- export interface CharacterFormatDocumentOperation extends DocumentOperationBase {
166
- type: 'format' | 'character-format';
167
- textToFormat: string;
168
- properties: CharacterFormatProperties;
169
- formattingRevisionPolicy?: 'always' | 'coalesce-own-insertion';
170
- }
171
-
172
- export interface ParagraphFormatDocumentOperation extends DocumentOperationBase {
173
- type: 'paragraph-format';
174
- properties: ParagraphFormatProperties;
175
- }
176
-
177
- export type DocumentOperation =
178
- | RedlineDocumentOperation
179
- | RestoreDocumentOperation
180
- | DeleteDocumentOperation
181
- | CommentDocumentOperation
182
- | CommentReplyDocumentOperation
183
- | CommentResolveDocumentOperation
184
- | HighlightDocumentOperation
185
- | CharacterFormatDocumentOperation
186
- | ParagraphFormatDocumentOperation;
187
-
188
- export interface ResolvedDocumentTarget {
189
- index: number;
190
- paragraphId: string | null;
191
- text: string;
192
- fingerprint?: string;
193
- revisionView?: 'accepted' | 'rejected';
194
- inTable?: boolean;
195
- targetTextMatch?: {
196
- mode: 'exact' | 'space_equivalent' | 'normalized';
197
- differences?: Array<{
198
- offset: number;
199
- sourceCodePoint: string;
200
- requestedCodePoint: string;
201
- }>;
202
- sourceExcerpt?: string;
203
- requestedExcerpt?: string;
204
- };
205
- }
206
-
207
- export interface ResolvedCommentAnchor {
208
- requestIndex: number;
209
- paragraphIndex: number;
210
- text: string;
211
- resolvedBy: 'exact_anchor' | 'space_equivalent_anchor';
212
- start: number;
213
- end: number;
214
- }
215
-
216
- export interface StandaloneRunnerOptions {
217
- atomic?: boolean;
218
- continueOnError?: boolean;
219
- generateRedlines?: boolean;
220
- existingRevisions?: ExistingRevisionsPolicy;
221
- strictTargets?: boolean;
222
- expectedRevision?: RevisionToken;
223
- pairReplacements?: boolean;
224
- insertionAffinity?: InsertionAffinity;
225
- onInfo?: (message: string) => void;
226
- onWarn?: (message: string) => void;
227
- [key: string]: unknown;
228
- }
229
-
230
- export interface MutationReceiptRevisionItem {
231
- id: string;
232
- kind: 'ins' | 'del' | 'move_from' | 'move_to' | 'rPrChange' | 'pPrChange' | 'structural';
233
- partName: string;
234
- }
235
-
236
- export interface MutationReceipt {
237
- operationIndex: number;
238
- operationId?: string;
239
- attemptedDisposition: 'applied' | 'no_change' | 'refused' | 'not_attempted';
240
- finalDisposition: 'applied' | 'no_change' | 'refused' | 'rolled_back' | 'not_attempted';
241
- committed: boolean;
242
- authorUsed?: string;
243
- revisionItems: MutationReceiptRevisionItem[];
244
- commentIds: string[];
245
- numberingIds: string[];
246
- relationshipIds: string[];
247
- affectedTargets: ResolvedDocumentTarget[];
248
- warnings: string[];
249
- }
250
-
251
- export interface LocalizedReplacementChange {
252
- kind: 'localized_replacement';
253
- committed: boolean;
254
- finalDisposition: 'applied' | 'rolled_back';
255
- target: {
256
- paragraphId?: string;
257
- index?: number;
258
- fingerprint?: string;
259
- humanReference?: string;
260
- };
261
- context?: {
262
- search?: string;
263
- range?: string;
264
- anchorMatchCount?: number;
265
- humanReference?: string;
266
- };
267
- replacements: Array<{
268
- find: string;
269
- replace: string;
270
- occurrence?: number;
271
- matchMode?: 'space_equivalent';
272
- beforeExcerpt: string;
273
- afterExcerpt: string;
274
- }>;
275
- verification: {
276
- acceptedViewMatchesCompiledText: boolean;
277
- };
278
- }
279
-
280
- export interface DocumentOperationResult {
281
- documentXml: string;
282
- hasChanges: boolean;
283
- numberingXml?: string | null;
284
- commentsXml?: string | null;
285
- commentsExtendedXml?: string | null;
286
- commentsXmlMode?: 'merge' | 'replace';
287
- commentsExtendedXmlMode?: 'merge' | 'replace';
288
- warnings?: string[];
289
- status?: RedlineStatus;
290
- error?: RedlineError;
291
- operationType: 'redline' | 'comment' | 'comment_reply' | 'comment_resolve' | 'highlight' | 'format' | 'paragraph-format';
292
- authorUsed: string;
293
- resolvedBy?: string;
294
- resolvedTarget?: ResolvedDocumentTarget;
295
- resolvedAnchor?: ResolvedCommentAnchor;
296
- receipt?: MutationReceipt;
297
- change?: LocalizedReplacementChange;
298
- }
299
-
300
- export interface BatchOperationItemResult {
301
- index: number;
302
- type: string;
303
- operationType: 'redline' | 'comment' | 'comment_reply' | 'comment_resolve' | 'highlight' | 'format' | 'paragraph-format';
304
- status: 'applied' | 'no_change' | 'error';
305
- authorUsed: string;
306
- resolvedBy?: string;
307
- resolvedTarget?: ResolvedDocumentTarget;
308
- resolvedAnchor?: ResolvedCommentAnchor;
309
- warnings?: string[];
310
- error?: RedlineError;
311
- receipt?: MutationReceipt;
312
- change?: LocalizedReplacementChange;
313
- }
314
-
315
- export interface DocumentOperationBatchResult {
316
- documentXml: string;
317
- hasChanges: boolean;
318
- commentsXml: string | null;
319
- commentsExtendedXml?: string | null;
320
- commentsXmlMode?: 'merge' | 'replace';
321
- commentsExtendedXmlMode?: 'merge' | 'replace';
322
- numberingXmlParts: string[];
323
- results: BatchOperationItemResult[];
324
- receipts?: MutationReceipt[];
325
- executionOrder: number[];
326
- /** Authors attached to committed changes. Empty when an atomic batch rolls back. */
327
- authorsUsed: string[];
328
- rolledBack?: boolean;
329
- status?: RedlineStatus;
330
- error?: RedlineError;
331
- warnings?: string[];
332
- conflicts?: OperationConflict[];
333
- retryPlan?: MutationRetryPlan;
334
- }
335
-
336
- export interface MutationRetryPlan {
337
- base: 'original' | 'output';
338
- committedIndexes: number[];
339
- failedIndexes: number[];
340
- unattemptedIndexes: number[];
341
- replayWholeBatch: boolean;
342
- sameArgumentsSafe: false;
343
- }
344
-
345
- export interface OperationPreflightItemResult {
346
- index: number;
347
- type: string;
348
- operationType: 'redline' | 'comment' | 'comment_reply' | 'comment_resolve' | 'highlight' | 'format' | 'paragraph-format';
349
- status: 'ready' | 'deferred' | 'error';
350
- authorUsed: string;
351
- resolvedBy?: string;
352
- captureRef?: string;
353
- select?: string;
354
- resolvedTarget?: ResolvedDocumentTarget;
355
- matchDiagnostics?: {
356
- exactTextMatch: boolean;
357
- normalizedTextMatch: boolean;
358
- suppliedText: string;
359
- actualText: string;
360
- };
361
- anchor?: {
362
- text: string;
363
- found: boolean;
364
- resolvedBy?: 'exact_anchor' | 'space_equivalent_anchor';
365
- start?: number;
366
- end?: number;
367
- candidates?: Array<{ start: number; end: number }>;
368
- } | null;
369
- hasRevisions?: boolean;
370
- existingRevisions?: ExistingRevisionsPolicy;
371
- warnings?: string[];
372
- error?: RedlineError & { candidates?: ResolvedDocumentTarget[] };
373
- }
374
-
375
- export interface OperationConflict {
376
- code: 'OVERLAPPING_SOURCE_TARGETS' | 'OVERLAPPING_TEXT_EDITS' | 'REVISION_ORDER_CONFLICT' | 'CAPTURE_FANOUT_CONFLICT' | string;
377
- message: string;
378
- operationIndexes: number[];
379
- target: ResolvedDocumentTarget;
380
- recoveryVersion?: number;
381
- category?: string;
382
- recovery?: RedlineError['recovery'];
383
- }
384
-
385
- export interface OperationPreflightResult {
386
- valid: boolean;
387
- status: 'ok' | 'error';
388
- results: OperationPreflightItemResult[];
389
- conflicts: OperationConflict[];
390
- authorsUsed: string[];
391
- requiredArtifacts: { comments: boolean; numbering: boolean };
392
- error?: RedlineError;
393
- }
394
-
395
- export interface OperationDependencyPlan {
396
- valid: boolean;
397
- scheduled?: Array<{ operation: DocumentOperation; index: number }>;
398
- captureProducers?: Map<string, number>;
399
- error?: RedlineError;
400
- }
401
-
402
- export function buildOperationDependencyPlan(operations?: DocumentOperation[]): OperationDependencyPlan;
403
-
404
- export function orderOperationsForStableTargets(operations?: DocumentOperation[]): DocumentOperation[];
405
-
406
- export function preflightOperations(
407
- documentXml: string,
408
- operations: DocumentOperation[],
409
- author?: string,
410
- options?: StandaloneRunnerOptions
411
- ): OperationPreflightResult;
412
-
413
- export function applyOperationToDocumentXml(
414
- documentXml: string,
415
- operation: DocumentOperation,
416
- author?: string,
417
- runtimeContext?: Record<string, unknown> | null,
418
- options?: StandaloneRunnerOptions
419
- ): Promise<DocumentOperationResult>;
420
-
421
- export function applyOperationsToDocumentXml(
422
- documentXml: string,
423
- operations: DocumentOperation[],
424
- author?: string,
425
- runtimeContext?: Record<string, unknown> | null,
426
- options?: StandaloneRunnerOptions
427
- ): Promise<DocumentOperationBatchResult>;
1
+ import type { ExistingRevisionsPolicy, RedlineError, RedlineStatus, RevisionToken } from '../index.js';
2
+
3
+ export interface ParagraphTargetDescriptor {
4
+ text?: string;
5
+ exactText?: string;
6
+ index?: number | string;
7
+ paragraphIndex?: number | string;
8
+ paragraphId?: string;
9
+ occurrence?: number;
10
+ inTable?: boolean;
11
+ fingerprint?: string;
12
+ sourceFingerprint?: string;
13
+ /** Defaults to rejected for restore operations and accepted for all other operations. */
14
+ revisionView?: 'accepted' | 'rejected';
15
+ captureRef?: string;
16
+ select?: string;
17
+ createdByOperation?: number;
18
+ }
19
+
20
+ export interface InsertionAffinity {
21
+ formatting?: 'left' | 'right' | 'none';
22
+ hyperlink?: 'inside' | 'outside' | 'preserve';
23
+ revision?: 'coalesce_same_author' | 'separate';
24
+ bookmark?: 'inside' | 'outside';
25
+ comment?: 'inside' | 'outside';
26
+ }
27
+
28
+ export interface RejectedTextInsertionAnchor {
29
+ exactText: string;
30
+ occurrence?: number;
31
+ offset: number;
32
+ }
33
+
34
+ export interface LocalizedReplacement {
35
+ find: string;
36
+ replace: string;
37
+ occurrence?: number;
38
+ }
39
+
40
+ /** A header/footer part path from `inspect().headersFooters`, or a selector. */
41
+ export type HeaderFooterPartSelector =
42
+ | string
43
+ | { kind: 'header' | 'footer'; type?: 'default' | 'first' | 'even'; section?: number };
44
+
45
+ /**
46
+ * Structured list-conversion diagnostic (`list-conversion-diagnostics-v1`). Warnings appear in
47
+ * `diagnostics` (and as `CODE: message` strings in `warnings`) on an operation that succeeded;
48
+ * the same details appear on `error` when the strict policy refuses the operation. They are
49
+ * unrelated to `REVISION_FIDELITY_VIOLATION`.
50
+ */
51
+ export interface ListConversionDiagnostic {
52
+ code:
53
+ | 'LIST_MARKER_NOT_BOUND'
54
+ | 'UNSUPPORTED_LIST_CONVERSION'
55
+ | 'LIST_FORMAT_NOT_APPLIED'
56
+ | 'UNSUPPORTED_LIST_FORMAT_CHANGE'
57
+ | 'UNKNOWN_PARAGRAPH_FORMAT_PROPERTY';
58
+ severity: 'warning' | 'error';
59
+ message: string;
60
+ /** The literal marker text (for example `A.`) the edit introduced. */
61
+ listMarker?: string;
62
+ requestedList?: { kind: 'bullet' | 'numbered'; format: string };
63
+ /** The numbering definition the list item keeps (format-change diagnostics). */
64
+ sourceList?: { numId: string; level: number; kind: 'bullet' | 'numbered'; format: string };
65
+ targetExcerpt?: string;
66
+ operationIndex?: number;
67
+ /** Rejected `paragraph-format.properties` keys (UNKNOWN_PARAGRAPH_FORMAT_PROPERTY). */
68
+ properties?: string[];
69
+ recovery?: {
70
+ action: string;
71
+ hint?: string;
72
+ field?: string;
73
+ allowedValue?: string;
74
+ /**
75
+ * The list-format operation to send (add a target): convert-plain for LIST_MARKER_NOT_BOUND /
76
+ * UNSUPPORTED_LIST_CONVERSION, change-format (scope "list") for LIST_FORMAT_NOT_APPLIED / UNSUPPORTED_LIST_FORMAT_CHANGE.
77
+ */
78
+ suggestedOperation?: { type: 'list-format'; list: ListFormatRequest };
79
+ };
80
+ }
81
+
82
+ export interface DocumentOperationBase {
83
+ operationId?: string;
84
+ /** Target a header or footer instead of the body. Comments are not supported there. */
85
+ part?: HeaderFooterPartSelector;
86
+ captureKey?: string;
87
+ target?: string | ParagraphTargetDescriptor;
88
+ targetRef?: number | string | null;
89
+ author?: string;
90
+ generateRedlines?: boolean;
91
+ existingRevisions?: ExistingRevisionsPolicy;
92
+ pairReplacements?: boolean;
93
+ insertionAffinity?: InsertionAffinity;
94
+ /**
95
+ * `'literal'` accepts list-marker text as literal text and suppresses LIST_MARKER_NOT_BOUND,
96
+ * UNSUPPORTED_LIST_CONVERSION, LIST_FORMAT_NOT_APPLIED and UNSUPPORTED_LIST_FORMAT_CHANGE for this operation.
97
+ */
98
+ listMarkers?: 'literal';
99
+ }
100
+
101
+ interface RedlineDocumentOperationCommon extends DocumentOperationBase {
102
+ structuredContent?: boolean;
103
+ targetEnd?: ParagraphTargetDescriptor;
104
+ targetEndRef?: number | string | null;
105
+ /** Required when type is insert and target.revisionView is rejected. */
106
+ anchor?: RejectedTextInsertionAnchor;
107
+ }
108
+
109
+ export type RedlineDocumentOperation =
110
+ | (RedlineDocumentOperationCommon & {
111
+ type: 'redline' | 'replace';
112
+ modified: string;
113
+ replacements?: never;
114
+ })
115
+ | (RedlineDocumentOperationCommon & {
116
+ type: 'redline' | 'replace';
117
+ modified?: never;
118
+ /** Exact, case-sensitive replacements in one batch-start accepted-view paragraph. */
119
+ replacements: LocalizedReplacement[];
120
+ })
121
+ | (RedlineDocumentOperationCommon & {
122
+ type: 'format' | 'list-change' | 'table-reconciliation' | 'insert';
123
+ modified: string;
124
+ replacements?: never;
125
+ });
126
+
127
+ interface RestoreDocumentOperationCommon extends DocumentOperationBase {
128
+ type: 'restore';
129
+ }
130
+
131
+ export type RestoreDocumentOperation =
132
+ | (RestoreDocumentOperationCommon & {
133
+ modified: string | string[];
134
+ replacements?: never;
135
+ targetEnd?: ParagraphTargetDescriptor;
136
+ targetEndRef?: number | string | null;
137
+ })
138
+ | (RestoreDocumentOperationCommon & {
139
+ modified?: never;
140
+ /** Exact, case-sensitive replacements in one strong rejected-view paragraph. */
141
+ replacements: LocalizedReplacement[];
142
+ targetEnd?: never;
143
+ targetEndRef?: never;
144
+ })
145
+ | (RestoreDocumentOperationCommon & {
146
+ /** Omit modified to restore one strong rejected-view paragraph verbatim. */
147
+ modified?: never;
148
+ replacements?: never;
149
+ targetEnd?: never;
150
+ targetEndRef?: never;
151
+ });
152
+
153
+ export interface DeleteDocumentOperation extends DocumentOperationBase {
154
+ type: 'delete';
155
+ modified?: '';
156
+ }
157
+
158
+ export interface CommentDocumentOperation extends DocumentOperationBase {
159
+ type: 'comment';
160
+ textToComment?: string;
161
+ commentContent: string;
162
+ }
163
+
164
+ export interface CommentReplyDocumentOperation {
165
+ type: 'comment_reply';
166
+ operationId?: string;
167
+ parentCommentId: number | string;
168
+ commentContent: string;
169
+ author?: string;
170
+ date?: string;
171
+ }
172
+
173
+ /** Resolves (or with `resolved: false`, reopens) the whole thread containing `commentId`. Body-only. */
174
+ export interface CommentResolveDocumentOperation {
175
+ type: 'comment_resolve';
176
+ operationId?: string;
177
+ commentId: number | string;
178
+ resolved?: boolean;
179
+ }
180
+
181
+ export interface CharacterFormatProperties {
182
+ bold?: boolean;
183
+ italic?: boolean;
184
+ underline?: boolean;
185
+ strike?: boolean;
186
+ strikethrough?: boolean;
187
+ highlight?: string | null;
188
+ color?: string | null;
189
+ fontSize?: number | string | null;
190
+ fontFamily?: string | null;
191
+ }
192
+
193
+ /** Closed set. Unknown keys are rejected with INVALID_OPERATION (field `properties.<key>`) since 0.9.0. */
194
+ export interface ParagraphFormatProperties {
195
+ alignment?: 'left' | 'center' | 'right' | 'both';
196
+ keepNext?: boolean;
197
+ keepLines?: boolean;
198
+ pageBreakBefore?: boolean;
199
+ style?: string | null;
200
+ /**
201
+ * Binds the paragraph to an EXISTING list (`numId` and `level` as reported by inspect), or `null` to remove its direct
202
+ * list binding. Tracked as `w:pPrChange`; refuses `PARAGRAPH_NUMBERING_NOT_FOUND` when the numId/level is not defined.
203
+ * Capability `paragraph-numbering-binding-v1`.
204
+ */
205
+ numbering?: { numId: number | string; level?: number } | null;
206
+ }
207
+
208
+ export interface HighlightDocumentOperation extends DocumentOperationBase {
209
+ type: 'highlight';
210
+ textToHighlight: string;
211
+ color?: string;
212
+ }
213
+
214
+ export interface CharacterFormatDocumentOperation extends DocumentOperationBase {
215
+ type: 'format' | 'character-format';
216
+ textToFormat: string;
217
+ /** 1-based match of `textToFormat` inside the resolved paragraph (default 1). Independent of `target.occurrence`. */
218
+ textOccurrence?: number;
219
+ properties: CharacterFormatProperties;
220
+ formattingRevisionPolicy?: 'always' | 'coalesce-own-insertion';
221
+ }
222
+
223
+ export interface ParagraphFormatDocumentOperation extends DocumentOperationBase {
224
+ type: 'paragraph-format';
225
+ properties: ParagraphFormatProperties;
226
+ }
227
+
228
+ export type ListNumberFormat = 'decimal' | 'upperLetter' | 'lowerLetter' | 'upperRoman' | 'lowerRoman' | 'bullet';
229
+
230
+ /** `list-format` request. `convert-plain`: `list-format-v1` (CLI contract version 9); `change-format` and `join`: `list-format-change-v1`. */
231
+ export type ListFormatRequest = ListFormatConvertPlainRequest | ListFormatChangeFormatRequest | ListFormatJoinRequest;
232
+
233
+ /** `convert-plain` (`list-format-v1`): bind plain paragraphs to a NEW list. */
234
+ export interface ListFormatConvertPlainRequest {
235
+ mode: 'convert-plain';
236
+ kind?: 'bullet' | 'numbered';
237
+ /** Required with `strip: 'none'`; with `strip: 'marker'` it must match the typed markers (and disambiguates `I.`/`C.`). */
238
+ format?: 'decimal' | 'upperLetter' | 'lowerLetter' | 'upperRoman' | 'lowerRoman' | 'bullet';
239
+ /** 0-8, default 0. */
240
+ level?: number;
241
+ /** `'marker'` (default) removes the typed marker as a tracked deletion; `'none'` keeps the text and only binds. */
242
+ strip?: 'marker' | 'none';
243
+ /** `strip: 'none'` only. Defaults to continuing the batch's previous converted list of the same format, else 1. */
244
+ start?: number;
245
+ }
246
+
247
+ /**
248
+ * `change-format` (`list-format-change-v1`): change the numbering format of the target's list. Allocates a NEW
249
+ * definition (copying the source levels and geometry, one level redefined) and rebinds the items with tracked
250
+ * `w:pPrChange`s; the shared definition is never edited, so Reject All restores the original labels.
251
+ */
252
+ export interface ListFormatChangeFormatRequest {
253
+ mode: 'change-format';
254
+ kind?: 'bullet' | 'numbered';
255
+ /** The new format of the level (`kind: 'numbered'` alone is refused: name the format). */
256
+ format?: ListNumberFormat;
257
+ /** Level text: numbered `%1`..`%9` with the level's own placeholder (`"%1."`, `"(%2)"`); bullet: the bullet character. */
258
+ lvlText?: string;
259
+ /** Restart value of the level (the new definition's `w:start`); scope `'list'` only. */
260
+ start?: number;
261
+ /** The level to redefine; default: the one level the target paragraphs use. */
262
+ level?: number;
263
+ /** `'list'` (default): every live item of the list; `'range'`: the target range, which must run to the end of the list. */
264
+ scope?: 'list' | 'range';
265
+ }
266
+
267
+ /** `join` (`list-format-change-v1`): the target list (or a range running to its end) continues an earlier list. */
268
+ export interface ListFormatJoinRequest {
269
+ mode: 'join';
270
+ /** An item of the earlier list to continue; bound at batch start like `target`. */
271
+ continueFrom: ParagraphTargetDescriptor;
272
+ scope?: 'list' | 'range';
273
+ }
274
+
275
+ /**
276
+ * Binds plain paragraphs (target, or target..targetEnd in one container) to a NEW list definition in place. The
277
+ * binding is a tracked `w:pPrChange`; the stripped marker is a tracked `w:del`. Each typed marker's value is the
278
+ * item's value, so Accept All renders the labels the text showed. Body only (no `part`); text changes belong in a
279
+ * separate single-paragraph `redline` of the same paragraph that keeps the typed marker exactly: the batch compiler
280
+ * lets that pair share one batch and runs the redline first ("1. Old" -> "1. New": redline the text, convert-plain
281
+ * strips "1. "); other pairs fail LIST_FORMAT_COMPOSITION_CONFLICT. Refusals: LIST_MARKER_NOT_FOUND, LIST_MARKER_AMBIGUOUS, LIST_MARKER_UNSUPPORTED,
282
+ * LIST_FORMAT_TARGET_IS_LIST, LIST_FORMAT_STYLE_NUMBERED, PARAGRAPH_FORMAT_REVISION_CONFLICT.
283
+ *
284
+ * `change-format` / `join` (`list-format-change-v1`) rebind existing list items with tracked `w:pPrChange`s. With
285
+ * `scope: 'list'` every rebound item counts as a write of the operation in the batch compiler: only a single-paragraph
286
+ * `redline` per item composes with it (LIST_FORMAT_COMPOSITION_CONFLICT `reason: list-rebind` otherwise).
287
+ * Refusals: LIST_FORMAT_TARGET_NOT_LIST, LIST_FORMAT_MIXED_RANGE, LIST_FORMAT_STYLE_LINKED, LIST_FORMAT_STYLE_NUMBERED,
288
+ * LIST_FORMAT_SPLIT_UNSUPPORTED, LIST_FORMAT_DEFINITION_UNSUPPORTED, LIST_FORMAT_JOIN_INVALID,
289
+ * PARAGRAPH_FORMAT_REVISION_CONFLICT; a no-op is `ok` with a LIST_FORMAT_UNCHANGED warning.
290
+ */
291
+ export interface ListFormatDocumentOperation extends Omit<DocumentOperationBase, 'part'> {
292
+ type: 'list-format';
293
+ list: ListFormatRequest;
294
+ targetEnd?: ParagraphTargetDescriptor;
295
+ targetEndRef?: number | string | null;
296
+ }
297
+
298
+ /** What a `list-format` operation bound or rebound (`result.list`). */
299
+ export type ListFormatResult = ListFormatConvertPlainResult | ListFormatRebindResult;
300
+
301
+ /** `convert-plain` result. */
302
+ export interface ListFormatConvertPlainResult {
303
+ mode: 'convert-plain';
304
+ format: 'decimal' | 'upperLetter' | 'lowerLetter' | 'upperRoman' | 'lowerRoman' | 'bullet';
305
+ level: number;
306
+ strip: 'marker' | 'none';
307
+ items: Array<{ paragraphIndex: number; paragraphId: string | null; marker: string | null; value: number | null; numId: string }>;
308
+ /** Paragraphs of the range left as they were: empty, or (strip 'marker') body text without a marker. */
309
+ skipped: Array<{ paragraphIndex: number; reason: 'empty' | 'no-marker' }>;
310
+ }
311
+
312
+ /** `change-format` / `join` result: every paragraph rebound (scope `'list'` includes items outside the target). */
313
+ export interface ListFormatRebindResult {
314
+ mode: 'change-format' | 'join';
315
+ scope: 'list' | 'range';
316
+ sourceNumId: string;
317
+ /** change-format: the newly allocated numId; join: the continued list's numId. */
318
+ numId: string;
319
+ format?: ListNumberFormat;
320
+ level?: number;
321
+ lvlText?: string;
322
+ /** change-format: per-level `w:start` values of the new definition (carried restarts, split continuation, `start`). */
323
+ levelStarts?: Record<string, number>;
324
+ continueFrom?: { paragraphIndex: number | null; numId: string };
325
+ items: Array<{ paragraphIndex: number | null; paragraphId: string | null; level: number; numId: string; sourceNumId: string; inTarget: boolean }>;
326
+ /** Tracked-deleted paragraphs in the range keep their binding. */
327
+ skipped: Array<{ paragraphIndex: number | null; reason: 'tracked-deleted' }>;
328
+ }
329
+
330
+ export type DocumentOperation =
331
+ | RedlineDocumentOperation
332
+ | RestoreDocumentOperation
333
+ | DeleteDocumentOperation
334
+ | CommentDocumentOperation
335
+ | CommentReplyDocumentOperation
336
+ | CommentResolveDocumentOperation
337
+ | HighlightDocumentOperation
338
+ | CharacterFormatDocumentOperation
339
+ | ParagraphFormatDocumentOperation
340
+ | ListFormatDocumentOperation;
341
+
342
+ export interface ResolvedDocumentTarget {
343
+ index: number;
344
+ paragraphId: string | null;
345
+ text: string;
346
+ fingerprint?: string;
347
+ revisionView?: 'accepted' | 'rejected';
348
+ inTable?: boolean;
349
+ targetTextMatch?: {
350
+ mode: 'exact' | 'space_equivalent' | 'normalized';
351
+ differences?: Array<{
352
+ offset: number;
353
+ sourceCodePoint: string;
354
+ requestedCodePoint: string;
355
+ }>;
356
+ sourceExcerpt?: string;
357
+ requestedExcerpt?: string;
358
+ };
359
+ }
360
+
361
+ export interface ResolvedCommentAnchor {
362
+ requestIndex: number;
363
+ paragraphIndex: number;
364
+ text: string;
365
+ resolvedBy: 'exact_anchor' | 'space_equivalent_anchor';
366
+ start: number;
367
+ end: number;
368
+ }
369
+
370
+ export interface StandaloneRunnerOptions {
371
+ atomic?: boolean;
372
+ continueOnError?: boolean;
373
+ generateRedlines?: boolean;
374
+ existingRevisions?: ExistingRevisionsPolicy;
375
+ strictTargets?: boolean;
376
+ /**
377
+ * Make list-conversion diagnostics errors (UNSUPPORTED_LIST_CONVERSION, UNSUPPORTED_LIST_FORMAT_CHANGE)
378
+ * instead of warnings. Defaults to `atomic`; the CLI agent profile sets it. An operation's
379
+ * `listMarkers: 'literal'` always wins.
380
+ */
381
+ strictListConversion?: boolean;
382
+ /**
383
+ * Post-apply check that Word's Reject All of each tracked operation's output equals the source
384
+ * (paragraph count, text, properties, numbering, wrapper membership). Default `'enforce'` refuses
385
+ * the operation with `REVISION_FIDELITY_VIOLATION` (per-invariant `violations[]` and
386
+ * `recovery.retryGuidance`); `'warn'` applies it and appends the same message to `warnings`;
387
+ * `'off'` is for diagnosis only.
388
+ */
389
+ verifyRevisionFidelity?: 'enforce' | 'warn' | 'off';
390
+ expectedRevision?: RevisionToken;
391
+ pairReplacements?: boolean;
392
+ insertionAffinity?: InsertionAffinity;
393
+ onInfo?: (message: string) => void;
394
+ onWarn?: (message: string) => void;
395
+ [key: string]: unknown;
396
+ }
397
+
398
+ export interface MutationReceiptRevisionItem {
399
+ id: string;
400
+ kind: 'ins' | 'del' | 'move_from' | 'move_to' | 'rPrChange' | 'pPrChange' | 'structural';
401
+ partName: string;
402
+ }
403
+
404
+ export interface MutationReceipt {
405
+ operationIndex: number;
406
+ operationId?: string;
407
+ attemptedDisposition: 'applied' | 'no_change' | 'refused' | 'not_attempted';
408
+ finalDisposition: 'applied' | 'no_change' | 'refused' | 'rolled_back' | 'not_attempted';
409
+ committed: boolean;
410
+ authorUsed?: string;
411
+ revisionItems: MutationReceiptRevisionItem[];
412
+ commentIds: string[];
413
+ numberingIds: string[];
414
+ relationshipIds: string[];
415
+ affectedTargets: ResolvedDocumentTarget[];
416
+ warnings: string[];
417
+ }
418
+
419
+ export interface LocalizedReplacementChange {
420
+ kind: 'localized_replacement';
421
+ committed: boolean;
422
+ finalDisposition: 'applied' | 'rolled_back';
423
+ target: {
424
+ paragraphId?: string;
425
+ index?: number;
426
+ fingerprint?: string;
427
+ humanReference?: string;
428
+ };
429
+ context?: {
430
+ search?: string;
431
+ range?: string;
432
+ anchorMatchCount?: number;
433
+ humanReference?: string;
434
+ };
435
+ replacements: Array<{
436
+ find: string;
437
+ replace: string;
438
+ occurrence?: number;
439
+ matchMode?: 'space_equivalent';
440
+ beforeExcerpt: string;
441
+ afterExcerpt: string;
442
+ }>;
443
+ verification: {
444
+ acceptedViewMatchesCompiledText: boolean;
445
+ };
446
+ }
447
+
448
+ export interface DocumentOperationResult {
449
+ documentXml: string;
450
+ hasChanges: boolean;
451
+ numberingXml?: string | null;
452
+ commentsXml?: string | null;
453
+ commentsExtendedXml?: string | null;
454
+ commentsXmlMode?: 'merge' | 'replace';
455
+ commentsExtendedXmlMode?: 'merge' | 'replace';
456
+ warnings?: string[];
457
+ diagnostics?: ListConversionDiagnostic[];
458
+ status?: RedlineStatus;
459
+ error?: RedlineError;
460
+ operationType: 'redline' | 'comment' | 'comment_reply' | 'comment_resolve' | 'highlight' | 'format' | 'paragraph-format' | 'list-format';
461
+ authorUsed: string;
462
+ resolvedBy?: string;
463
+ resolvedTarget?: ResolvedDocumentTarget;
464
+ resolvedAnchor?: ResolvedCommentAnchor;
465
+ receipt?: MutationReceipt;
466
+ change?: LocalizedReplacementChange;
467
+ /** `list-format` operations: what was bound. */
468
+ list?: ListFormatResult;
469
+ }
470
+
471
+ export interface BatchOperationItemResult {
472
+ index: number;
473
+ type: string;
474
+ operationType: 'redline' | 'comment' | 'comment_reply' | 'comment_resolve' | 'highlight' | 'format' | 'paragraph-format' | 'list-format';
475
+ status: 'applied' | 'no_change' | 'error';
476
+ authorUsed: string;
477
+ resolvedBy?: string;
478
+ resolvedTarget?: ResolvedDocumentTarget;
479
+ resolvedAnchor?: ResolvedCommentAnchor;
480
+ warnings?: string[];
481
+ diagnostics?: ListConversionDiagnostic[];
482
+ error?: RedlineError;
483
+ receipt?: MutationReceipt;
484
+ change?: LocalizedReplacementChange;
485
+ /** `list-format` operations: what was bound. */
486
+ list?: ListFormatResult;
487
+ }
488
+
489
+ export interface DocumentOperationBatchResult {
490
+ documentXml: string;
491
+ hasChanges: boolean;
492
+ commentsXml: string | null;
493
+ commentsExtendedXml?: string | null;
494
+ commentsXmlMode?: 'merge' | 'replace';
495
+ commentsExtendedXmlMode?: 'merge' | 'replace';
496
+ numberingXmlParts: string[];
497
+ results: BatchOperationItemResult[];
498
+ receipts?: MutationReceipt[];
499
+ executionOrder: number[];
500
+ /** Authors attached to committed changes. Empty when an atomic batch rolls back. */
501
+ authorsUsed: string[];
502
+ rolledBack?: boolean;
503
+ status?: RedlineStatus;
504
+ error?: RedlineError;
505
+ warnings?: string[];
506
+ conflicts?: OperationConflict[];
507
+ retryPlan?: MutationRetryPlan;
508
+ }
509
+
510
+ export interface MutationRetryPlan {
511
+ base: 'original' | 'output';
512
+ committedIndexes: number[];
513
+ failedIndexes: number[];
514
+ unattemptedIndexes: number[];
515
+ replayWholeBatch: boolean;
516
+ sameArgumentsSafe: false;
517
+ }
518
+
519
+ export interface OperationPreflightItemResult {
520
+ index: number;
521
+ type: string;
522
+ operationType: 'redline' | 'comment' | 'comment_reply' | 'comment_resolve' | 'highlight' | 'format' | 'paragraph-format' | 'list-format';
523
+ status: 'ready' | 'deferred' | 'error';
524
+ authorUsed: string;
525
+ resolvedBy?: string;
526
+ captureRef?: string;
527
+ select?: string;
528
+ resolvedTarget?: ResolvedDocumentTarget;
529
+ matchDiagnostics?: {
530
+ exactTextMatch: boolean;
531
+ normalizedTextMatch: boolean;
532
+ suppliedText: string;
533
+ actualText: string;
534
+ };
535
+ anchor?: {
536
+ text: string;
537
+ found: boolean;
538
+ resolvedBy?: 'exact_anchor' | 'space_equivalent_anchor';
539
+ start?: number;
540
+ end?: number;
541
+ candidates?: Array<{ start: number; end: number }>;
542
+ } | null;
543
+ hasRevisions?: boolean;
544
+ existingRevisions?: ExistingRevisionsPolicy;
545
+ warnings?: string[];
546
+ error?: RedlineError & { candidates?: ResolvedDocumentTarget[] };
547
+ }
548
+
549
+ export interface OperationConflict {
550
+ code: 'OVERLAPPING_SOURCE_TARGETS' | 'OVERLAPPING_TEXT_EDITS' | 'REVISION_ORDER_CONFLICT' | 'LIST_FORMAT_COMPOSITION_CONFLICT' | 'CAPTURE_FANOUT_CONFLICT' | string;
551
+ message: string;
552
+ operationIndexes: number[];
553
+ target: ResolvedDocumentTarget;
554
+ /** LIST_FORMAT_COMPOSITION_CONFLICT: why the redline and the list-format do not compose. */
555
+ reason?: 'marker-changed' | 'redline-unchanged' | 'redline-shape' | 'list-rebind' | string;
556
+ sourceMarker?: string | null;
557
+ modifiedMarker?: string | null;
558
+ rebindingOperationIndexes?: number[];
559
+ recoveryVersion?: number;
560
+ category?: string;
561
+ recovery?: RedlineError['recovery'];
562
+ }
563
+
564
+ export interface OperationPreflightResult {
565
+ valid: boolean;
566
+ status: 'ok' | 'error';
567
+ results: OperationPreflightItemResult[];
568
+ conflicts: OperationConflict[];
569
+ authorsUsed: string[];
570
+ requiredArtifacts: { comments: boolean; numbering: boolean };
571
+ error?: RedlineError;
572
+ }
573
+
574
+ export interface OperationDependencyPlan {
575
+ valid: boolean;
576
+ scheduled?: Array<{ operation: DocumentOperation; index: number }>;
577
+ captureProducers?: Map<string, number>;
578
+ error?: RedlineError;
579
+ }
580
+
581
+ export function buildOperationDependencyPlan(operations?: DocumentOperation[]): OperationDependencyPlan;
582
+
583
+ export function orderOperationsForStableTargets(operations?: DocumentOperation[]): DocumentOperation[];
584
+
585
+ export function preflightOperations(
586
+ documentXml: string,
587
+ operations: DocumentOperation[],
588
+ author?: string,
589
+ options?: StandaloneRunnerOptions
590
+ ): OperationPreflightResult;
591
+
592
+ export function applyOperationToDocumentXml(
593
+ documentXml: string,
594
+ operation: DocumentOperation,
595
+ author?: string,
596
+ runtimeContext?: Record<string, unknown> | null,
597
+ options?: StandaloneRunnerOptions
598
+ ): Promise<DocumentOperationResult>;
599
+
600
+ export function applyOperationsToDocumentXml(
601
+ documentXml: string,
602
+ operations: DocumentOperation[],
603
+ author?: string,
604
+ runtimeContext?: Record<string, unknown> | null,
605
+ options?: StandaloneRunnerOptions
606
+ ): Promise<DocumentOperationBatchResult>;