@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/AGENTS.md CHANGED
@@ -1,288 +1,590 @@
1
- # AGENTS.md - AI Agent Quick Reference
2
-
3
- > This file helps AI coding agents understand @ansonlai/docx-redline-js quickly.
4
- > Read this instead of exploring the full source tree.
5
-
6
- ## What This Package Does
7
-
8
- Converts text/markdown edits into valid Office Open XML (OOXML) with Word-native tracked changes. Feed it original OOXML + desired text and it returns OOXML with `w:ins`/`w:del` revision markup.
9
-
10
- ## Conceptual Model
11
-
12
- ```
13
- Input: (paragraph OOXML, original text, modified text, options)
14
- |
15
- v
16
- Engine routes to: format-only | surgical | reconstruction | list | table mode
17
- |
18
- v
19
- Output: { oxml: string, hasChanges: boolean, status?: string, error?: object, warnings?: string[] }
20
- ```
21
-
22
- The engine usually works at paragraph/range/table scope. For full-document
23
- operations, use the standalone operation runner so the result is safe to write
24
- back to `word/document.xml`.
25
-
26
- ## Entry Point
27
-
28
- ```js
29
- import { applyRedlineToOxml, configureXmlProvider } from '@ansonlai/docx-redline-js';
30
- ```
31
-
32
- `index.js` is the single package entry point.
33
-
34
- ## Required Setup (Node.js only)
35
-
36
- ```js
37
- import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
38
- configureXmlProvider({ DOMParser, XMLSerializer });
39
- ```
40
-
41
- Browsers have native DOM APIs, so no provider injection is typically needed.
42
-
43
- ## Key APIs by Use Case
44
-
45
- ### Apply a text edit with tracked changes
46
-
47
- ```js
48
- const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
49
- generateRedlines: true,
50
- author: 'Agent Name',
51
- existingRevisions: 'reject-input'
52
- });
53
- ```
54
-
55
- `existingRevisions` defaults to `'reject-input'`. Use `'accept-all-first'` only
56
- when the caller intentionally wants to accept prior tracked changes before
57
- applying a new edit. A no-op still returns the untouched input; use
58
- `'accept-all-first-keep-normalized'` only when accepted revisions should be
59
- returned as a real change even without a new redline.
60
-
61
- ### Apply a text edit without tracked changes
62
-
63
- ```js
64
- const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
65
- generateRedlines: false
66
- });
67
- ```
68
-
69
- ### Convert OOXML to readable text or markdown
70
-
71
- ```js
72
- import { ingestWordOoxmlToPlainText, ingestWordOoxmlToMarkdown } from '@ansonlai/docx-redline-js';
73
- const plainText = ingestWordOoxmlToPlainText(documentXml);
74
- const markdown = ingestWordOoxmlToMarkdown(documentXml);
75
- ```
76
-
77
- ### Add a comment to OOXML
78
-
79
- ```js
80
- import { injectCommentsIntoOoxml } from '@ansonlai/docx-redline-js';
81
- const result = injectCommentsIntoOoxml(paragraphOoxml, [
82
- { text: 'Review this clause', targetText: 'force majeure', author: 'Agent' }
83
- ]);
84
- ```
85
-
86
- ### Accept tracked changes from one user (or all users)
87
-
88
- ```js
89
- import { acceptTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
90
- const acceptedMine = acceptTrackedChangesInOoxml(documentXml, { author: 'Agent' });
91
- const acceptedAll = acceptTrackedChangesInOoxml(documentXml, { allAuthors: true });
92
- ```
93
-
94
- ### Reject tracked changes from one user (or all users)
95
-
96
- ```js
97
- import { rejectTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
98
- const rejectedMine = rejectTrackedChangesInOoxml(documentXml, { author: 'Agent' });
99
- const rejectedAll = rejectTrackedChangesInOoxml(documentXml, { allAuthors: true });
100
- ```
101
-
102
- Move revisions are consumed too: accept removes `w:moveFrom` and unwraps
103
- `w:moveTo`; reject unwraps `w:moveFrom` and removes `w:moveTo`.
104
-
105
- ### Delete comments from one user (or all users)
106
-
107
- ```js
108
- import { deleteCommentsByAuthorInOoxml } from '@ansonlai/docx-redline-js';
109
- const removedMine = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { author: 'Agent' });
110
- const removedAll = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { allAuthors: true });
111
- ```
112
-
113
- ### Apply multiple operations to full document XML
114
-
115
- ```js
116
- import {
117
- applyOperationToDocumentXml,
118
- applyOperationsToDocumentXml
119
- } from '@ansonlai/docx-redline-js/services/standalone-operation-runner.js';
120
-
121
- const result = await applyOperationsToDocumentXml(documentXml, operations, 'Agent', runtimeContext, options);
122
- ```
123
-
124
- Use `result.documentXml` from these APIs when replacing full `word/document.xml`.
125
- For mixed batches, prefer `applyOperationsToDocumentXml(...)`; it applies comments
126
- before replacements so earlier edits cannot invalidate their anchors.
127
-
128
- Batches are atomic by default. If any operation fails, the batch returns the
129
- original `documentXml`, `hasChanges: false`, no comment/numbering artifacts, and
130
- `rolledBack: true`; `results` still describes every attempted operation because
131
- `continueOnError` defaults to `true`. Pass `{ atomic: false }` only when a
132
- partially applied document is intentional. Pass `{ continueOnError: false }` to
133
- stop attempting operations after the first error.
134
-
135
- ### Detect existing tracked changes
136
-
137
- ```js
138
- import { containsTrackedChanges } from '@ansonlai/docx-redline-js';
139
- const hasTrackedChanges = containsTrackedChanges(xmlDoc);
140
- ```
141
-
142
- ### Convert paragraph text into a Word list
143
-
144
- ```js
145
- const result = await applyRedlineToOxml(oxml, 'Item text', '1. Item text', {
146
- generateRedlines: true
147
- });
148
- ```
149
-
150
- ### Reconcile a table
151
-
152
- ```js
153
- import { reconcileMarkdownTableOoxml } from '@ansonlai/docx-redline-js';
154
- const result = await reconcileMarkdownTableOoxml(tableOoxml, originalText, markdownTable);
155
- ```
156
-
157
- ## Module Map
158
-
159
- ```
160
- index.js
161
- adapters/
162
- config.js
163
- xml-adapter.js
164
- logger.js
165
- core/
166
- types.js
167
- word-xml.js
168
- paragraph-targeting.js
169
- list-targeting.js
170
- table-targeting.js
171
- engine/
172
- oxml-engine.js
173
- surgical-mode.js
174
- surgical-run-splitting.js
175
- surgical-diff-application.js
176
- surgical-spans.js
177
- reconstruction-mode.js
178
- reconstruction-writer.js
179
- format-application.js
180
- formatting-removal.js
181
- run-builders.js
182
- table-mode.js
183
- pipeline/
184
- pipeline.js
185
- ingestion.js
186
- ingestion-export.js
187
- diff-engine.js
188
- markdown-processor.js
189
- serialization.js
190
- list-generation.js
191
- services/
192
- standalone-operation-runner.js
193
- standalone-docx-plumbing.js
194
- numbering-helpers.js
195
- comment-engine.js
196
- revision-comment-management.js
197
- table-reconciliation.js
198
- package-builder.js
199
- orchestration/
200
- route-plan.js
201
- list-markdown.js
202
- list-structural-fallback.js
203
- ```
204
-
205
- ## Common Patterns
206
-
207
- ### Options shape
208
-
209
- ```js
210
- {
211
- generateRedlines: true,
212
- author: 'Name',
213
- existingRevisions: 'reject-input',
214
- removeFormatting: false,
215
- sanitizeInput: false
216
- }
217
- ```
218
-
219
- ### Typical return shape
220
-
221
- ```js
222
- {
223
- oxml: string,
224
- hasChanges: boolean,
225
- status?: 'ok' | 'no-op' | 'error',
226
- error?: { code: string, message: string },
227
- warnings?: string[],
228
- numberingXml?: string,
229
- useNativeApi?: boolean
230
- }
231
- ```
232
-
1
+ # AGENTS.md - AI Agent Quick Reference
2
+
3
+ > This file helps AI coding agents understand @ansonlai/docx-redline-js quickly.
4
+ > Read this instead of exploring the full source tree.
5
+
6
+ ## What This Package Does
7
+
8
+ Converts text/markdown edits into valid Office Open XML (OOXML) with Word-native tracked changes. Feed it original OOXML + desired text and it returns OOXML with `w:ins`/`w:del` revision markup.
9
+
10
+ ## Conceptual Model
11
+
12
+ ```
13
+ Input: (paragraph OOXML, original text, modified text, options)
14
+ |
15
+ v
16
+ Engine routes to: format-only | surgical | reconstruction | list | table mode
17
+ |
18
+ v
19
+ Output: { oxml: string, hasChanges: boolean, status?: string, error?: object, warnings?: string[] }
20
+ ```
21
+
22
+ The engine usually works at paragraph/range/table scope. For full-document
23
+ operations, use the standalone operation runner so the result is safe to write
24
+ back to `word/document.xml`.
25
+
26
+ ## Entry Point
27
+
28
+ ```js
29
+ import { applyRedlineToOxml, configureXmlProvider } from '@ansonlai/docx-redline-js';
30
+ ```
31
+
32
+ `index.js` is the single package entry point.
33
+
34
+ ## Required Setup (Node.js only)
35
+
36
+ ```js
37
+ import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
38
+ configureXmlProvider({ DOMParser, XMLSerializer });
39
+ ```
40
+
41
+ Browsers have native DOM APIs, so no provider injection is typically needed.
42
+
43
+ ## Key APIs by Use Case
44
+
45
+ ### Apply a text edit with tracked changes
46
+
47
+ ```js
48
+ const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
49
+ generateRedlines: true,
50
+ author: 'Agent Name'
51
+ });
52
+ ```
53
+
54
+ `existingRevisions` defaults to `'merge-same-author'`. When a target paragraph
55
+ contains tracked changes from the same author, prior revisions by that author are
56
+ reverted to the pre-revision baseline and re-diffed to the new text, cleanly
57
+ merging the edits without accumulating intermediate revisions or nesting markup.
58
+ If the paragraph contains revisions from a different reviewer, the edit fails
59
+ with `EXISTING_REVISIONS` to safeguard third-party marks. Pass
60
+ `existingRevisions: 'accept-all-first'` (or `--existing-revisions accept-all-first`
61
+ via CLI) to normalize all prior revisions first, or `'reject-input'` to refuse any
62
+ paragraph with open revisions. Use `'accept-all-first-keep-normalized'` only when
63
+ accepted revisions should be returned as a real change even on a no-op edit.
64
+ Same-author merging also fails with `COMMENTED_CONTENT_MERGE` when the revised
65
+ paragraph contains comment anchors, because reverting the prior revision could
66
+ remove or orphan those comments. Resolve the comments before re-editing.
67
+
68
+ ### Apply a text edit without tracked changes (Direct Edits)
69
+
70
+ > [!IMPORTANT]
71
+ > **Tracked redlines are not always the preferred method.** When finalizing execution copies of contracts, restructuring documents, correcting minor typos, or whenever the user specifically desires clean document text without tracked changes markup clutter, pass `generateRedlines: false` (or `--no-redlines` via CLI).
72
+
73
+ ```js
74
+ const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
75
+ generateRedlines: false
76
+ });
77
+ ```
78
+
79
+ ### Convert OOXML to readable text or markdown
80
+
81
+ ```js
82
+ import { ingestWordOoxmlToPlainText, ingestWordOoxmlToMarkdown } from '@ansonlai/docx-redline-js';
83
+ const plainText = ingestWordOoxmlToPlainText(documentXml);
84
+ const markdown = ingestWordOoxmlToMarkdown(documentXml);
85
+ ```
86
+
87
+ ### Add a comment to OOXML
88
+
89
+ ```js
90
+ import { injectCommentsIntoOoxml } from '@ansonlai/docx-redline-js';
91
+ const result = injectCommentsIntoOoxml(paragraphOoxml, [
92
+ {
93
+ paragraphIndex: 1,
94
+ textToFind: 'force majeure',
95
+ commentContent: 'Review this clause'
96
+ }
97
+ ], { author: 'Agent' });
98
+ ```
99
+
100
+ `paragraphIndex` is 1-based within the supplied OOXML payload. The comment
101
+ author belongs in the options object and applies to the injected comments.
102
+
103
+ ### Accept tracked changes from one user (or all users)
104
+
105
+ ```js
106
+ import { acceptTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
107
+ const acceptedMine = acceptTrackedChangesInOoxml(documentXml, { author: 'Agent' });
108
+ const acceptedAll = acceptTrackedChangesInOoxml(documentXml, { allAuthors: true });
109
+ ```
110
+
111
+ ### Reject tracked changes from one user (or all users)
112
+
113
+ ```js
114
+ import { rejectTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
115
+ const rejectedMine = rejectTrackedChangesInOoxml(documentXml, { author: 'Agent' });
116
+ const rejectedAll = rejectTrackedChangesInOoxml(documentXml, { allAuthors: true });
117
+ ```
118
+
119
+ Move revisions are consumed too: accept removes `w:moveFrom` and unwraps
120
+ `w:moveTo`; reject unwraps `w:moveFrom` and removes `w:moveTo`.
121
+
122
+ ### Delete comments from one user (or all users)
123
+
124
+ ```js
125
+ import { deleteCommentsByAuthorInOoxml } from '@ansonlai/docx-redline-js';
126
+ const removedMine = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { author: 'Agent' });
127
+ const removedAll = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { allAuthors: true });
128
+ ```
129
+
130
+ ### Apply multiple operations to full document XML
131
+
132
+ ```js
133
+ import {
134
+ applyOperationToDocumentXml,
135
+ applyOperationsToDocumentXml
136
+ } from '@ansonlai/docx-redline-js/standalone-runner';
137
+
138
+ const result = await applyOperationsToDocumentXml(documentXml, operations, 'Agent', runtimeContext, options);
139
+ ```
140
+
141
+ The operation runner uses these field names:
142
+
143
+ ```js
144
+ const operations = [
145
+ { type: 'redline', target: 'Old paragraph text', modified: 'New paragraph text', targetRef: 12 },
146
+ { type: 'comment', target: 'Paragraph text', textToComment: 'anchor text', commentContent: 'Comment body', targetRef: 18 },
147
+ { type: 'highlight', target: 'Paragraph text', textToHighlight: 'anchor text', color: 'yellow', targetRef: 24 }
148
+ ];
149
+ ```
150
+
151
+ `targetRef` is an optional 1-based paragraph reference used to disambiguate
152
+ duplicate text. An operation-level `author` overrides the batch author; batch
153
+ results report both `authorUsed` per item and the aggregate `authorsUsed` list.
154
+
155
+ For safer targeting, `target` may be a descriptor:
156
+
157
+ ```js
158
+ {
159
+ type: 'replace',
160
+ target: {
161
+ exactText: 'Repeated paragraph text',
162
+ paragraphId: '1A2B3C4D', // when present in the source OOXML
163
+ index: 12,
164
+ occurrence: 2,
165
+ inTable: false,
166
+ fingerprint: 'fnv1a32:...'
167
+ },
168
+ modified: 'Replacement text',
169
+ author: 'Editor'
170
+ }
171
+ ```
172
+
173
+ Call `preflightOperations(documentXml, operations, author)` when you want a
174
+ read-only inspection of an agent-generated batch before applying it. Preflight is
175
+ read-only and strict by default: duplicate exact text returns `AMBIGUOUS_TARGET`,
176
+ approximate text is not selected, and the result reports candidate targets,
177
+ missing anchors, existing revisions, authors, required artifacts, and
178
+ same-paragraph conflicts. For direct execution, `applyOperationsToDocumentXml` is
179
+ already transactional and atomic by default. When permissive resolution
180
+ encounters duplicate candidate paragraphs, it emits an
181
+ `AMBIGUOUS_TARGET_HEURISTIC_USED` warning; migrate to `{ strictTargets: true }`
182
+ with strict descriptors (`paragraphId`, `index`, `occurrence`, or `fingerprint`)
183
+ before v1.0.0.
184
+
185
+ Whole-paragraph deletions targeting paragraphs with existing comments fail with
186
+ `COMMENTED_CONTENT_DELETE`. Resolve or remove the comments first.
187
+
188
+ Use `result.documentXml` from these APIs when replacing full `word/document.xml`.
189
+ For mixed batches, prefer `applyOperationsToDocumentXml(...)`; it applies comments
190
+ before replacements so earlier edits cannot invalidate their anchors.
191
+
192
+ Batches default to `atomic: false` for maximum speed and progressive execution: valid operations are applied directly, while problematic operations report structured errors in `results` (with `continueOnError: true` by default).
193
+
194
+ When all-or-nothing transactional protection is desired (e.g. in high-stakes legal contracts, large automated migrations, or strict CI pipelines where partial edits are inadmissible), pass `{ atomic: true }` (or `--atomic` on the CLI). In atomic mode, if any operation fails or yields invalid markup, the entire batch is rolled back to the original untouched document (`rolledBack: true`, `hasChanges: false`, `documentXml: original`).
195
+
196
+ Internally, a batch uses one live document DOM and one revision allocator, then
197
+ serializes the full document once. Every operation has a DOM/allocator savepoint;
198
+ do not remove this isolation merely for speed. Redline accuracy, accepted and
199
+ rejected text, and exact rollback take precedence over throughput.
200
+
201
+ Every operation produces a commit-aware `receipt` (and batch-level `receipts`)
202
+ enumerating exact allocated `revisionItems`, `commentIds`, `numberingIds`,
203
+ `relationshipIds`, `affectedTargets`, and `warnings`. The output reconciliation
204
+ oracle (`reconcileReceiptsAgainstOutput`) validates that all reported durable IDs
205
+ are present in the serialized output; any discrepancy triggers rollback and fails closed.
206
+
207
+ Always inspect `status` and `error`, not only `hasChanges`. A failed transform
208
+ can return `{ hasChanges: false, status: 'error', error: ... }`. Missing or
209
+ ambiguous comment anchors are structured errors and roll back atomic batches;
210
+ `no_change` is reserved for genuine no-ops. Continue to inspect warnings for
211
+ non-fatal diagnostics.
212
+
213
+ ### Detect existing tracked changes
214
+
215
+ ```js
216
+ import { containsTrackedChanges } from '@ansonlai/docx-redline-js';
217
+ const hasTrackedChanges = containsTrackedChanges(xmlDoc);
218
+ ```
219
+
220
+ ### Inspect document parts before editing
221
+
222
+ ```js
223
+ import { inspectDocumentParts } from '@ansonlai/docx-redline-js';
224
+ const inspection = inspectDocumentParts({ documentXml, commentsXml, numberingXml });
225
+ ```
226
+
227
+ Reuse `exactText` plus `paragraphId` or `fingerprint` in an operation. Computed
228
+ list labels and excerpts are for display, not replacements for exact targets.
229
+
230
+ ### Safely edit a complete DOCX in Node
231
+
232
+ ```js
233
+ import { openDocx } from '@ansonlai/docx-redline-js/node';
234
+ const document = openDocx(inputBuffer);
235
+ const result = await document.applyOperations(operations, {
236
+ author: 'Agent', atomic: true, validate: true
237
+ });
238
+ if (!result.written) throw new Error(result.error?.message || 'No output written');
239
+ const outputBuffer = result.toBuffer();
240
+ ```
241
+
242
+ This facade defaults to strict targets, allocates package-safe comment IDs,
243
+ merges numbering, updates relationships/content types, and rolls back to the
244
+ original buffer when an atomic transaction fails.
245
+
246
+ ### Agent Document Workflow (CLI)
247
+
248
+ Use the `docx-redline` CLI for complete `.docx` files. It emits JSON on stdout,
249
+ keeps exact text intact, and never overwrites the source unless `--in-place` is
250
+ explicitly supplied.
251
+
252
+ #### Standard Workflow (Fast & Direct)
253
+
254
+ `apply` is fast, progressive, and self-validating by default. It supports inline one-liners as well as batch operations files:
255
+
256
+ ```bash
257
+ # 1. Inline one-liner edit (fastest for 1–2 edits; no JSON file needed)
258
+ docx-redline apply contract.docx --target "Original clause" --modified "New clause" --output reviewed.docx
259
+
260
+ # 2. Direct edit without tracked changes (clean text, no revision clutter)
261
+ docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
262
+
263
+ # 3. Batch operations with ops.json
264
+ docx-redline apply contract.docx --operations operations.json --output reviewed.docx
265
+ ```
266
+
267
+ Key CLI defaults and behaviors:
268
+ - **Author**: Automatically defaults to `'AI Redliner'` (overridable via `--author` or `DOCX_REDLINE_AUTHOR` environment variable).
269
+ - **Overwrite behavior**: Destination files provided via `--output` overwrite by default. To protect existing destination files, pass `--no-overwrite` or `--no-clobber`. The source document is never overwritten unless `--in-place` is specified.
270
+ - **Tracked changes**: Defaults to `generateRedlines: true`. When clean direct text is needed, pass `--no-redlines`.
271
+ - **Atomic rollback (optional)**: Operations apply progressively by default (`atomic: false`). For all-or-nothing transactional rollback where any error halts and reverts all changes, pass `--atomic`.
272
+ - Check `written: true` on stdout. If an error occurs, inspect `error.code` or `results[i].error.code` (e.g. `TARGET_NOT_FOUND`, `ANCHOR_NOT_FOUND`) to correct the target text and re-apply.
273
+
274
+ For multi-clause or multi-page reviews, apply edits **section-by-section** or clause-by-clause (e.g., using `--in-place` on a working copy) rather than bundling dozens of edits into one massive batch. This keeps context compact, simplifies error diagnosis, and prevents cascading anchor drift.
275
+
276
+ #### High-Assurance / Staged Verification Workflow (Optional)
277
+
278
+ For high-stakes legal contracts, large automated batch migrations, or workflows
279
+ requiring explicit non-mutating pre-checks and an independent baseline audit
280
+ report, use the extended verification cycle:
281
+
282
+ ```bash
283
+ docx-redline inspect contract.docx --non-empty
284
+ docx-redline extract contract.docx --range 10:30 > paragraphs.json
285
+ docx-redline preflight contract.docx --operations operations.json --author "Editor"
286
+ docx-redline apply contract.docx --operations operations.json --author "Editor" --output reviewed.docx
287
+ docx-redline validate reviewed.docx --baseline contract.docx
288
+ ```
289
+
290
+ Copy `exactText`, `paragraphId`, and `fingerprint` from `extract` into operation
291
+ targets. For most unique clauses, `"target": "exact paragraph text"` is
292
+ sufficient; use discriminators (`paragraphId`, `fingerprint`, `index`, or
293
+ `occurrence`) when duplicate paragraph text appears in the document. Never
294
+ normalize or reconstruct `exactText`. Operation files follow
295
+ [`docs/schemas/document-operations.schema.json`](docs/schemas/document-operations.schema.json).
296
+
297
+ #### Commands
298
+
299
+ - `inspect` returns the structured inventory, comments, authors, and counts.
300
+ - `extract` returns a compact target inventory with exact text.
301
+ - `preflight` checks targets, anchors, revisions, conflicts, authors, and needed artifacts without mutation (read-only).
302
+ - `apply` applies an operation file transactionally with automatic rollback and internal markup validation.
303
+ - `accept` and `reject` resolve revisions selected by `--author` or `--all-authors`.
304
+ - `delete-comments` removes matching definitions and document anchors together.
305
+ - A whole-paragraph delete stops with `COMMENTED_CONTENT_DELETE` when the
306
+ paragraph has an existing comment. Surface the returned reviewer and comment
307
+ text for human follow-up; do not silently convert this into comment removal.
308
+ - `validate` audits revision markup and DOCX package wiring, optionally comparing against a `--baseline`.
309
+
310
+ Paragraph indexes are 1-based. Inspection filters are `--index 12`,
311
+ `--range 10:30`, `--indexes 2,5,8`, `--search text`, `--revised`, `--table`,
312
+ `--body`, `--non-empty`, and `--view accepted|rejected|current`. A malformed
313
+ filter or unknown option is an error rather than an unfiltered fallback.
314
+
315
+ Mutating commands require `--author`, authors on every operation, or
316
+ `--all-authors` where applicable. Without `--output`, a sibling such as
317
+ `contract.redlined.docx` is chosen. Existing outputs are refused unless
318
+ `--force` is present. `--in-place` is the only way to overwrite the input.
319
+
320
+ Treat a nonzero exit code or JSON `status: "error"` as failure. A failed atomic
321
+ operation reports `written: false` and does not write an output file.
322
+ Missing or repeated comment anchors are errors rather than no-ops. Explicit
323
+ anchors match exact text first and then a unique ordinary-space/NBSP equivalent;
324
+ omit `textToComment` to comment the entire resolved paragraph.
325
+
326
+ To reply inside an existing Word comment thread, use the comment ID returned by
327
+ `inspect` and do not supply a paragraph target:
328
+
329
+ ```json
330
+ { "type": "comment_reply", "parentCommentId": "8", "commentContent": "Agreed; updated.", "author": "Editor" }
331
+ ```
332
+
333
+ Replies are represented in `word/commentsExtended.xml` and deliberately add no
334
+ new `commentRangeStart`, `commentRangeEnd`, or `commentReference` to the body.
335
+
336
+ #### Legacy skill wrapper migration
337
+
338
+ Older skills that invoke `scripts/extract_text.mjs` and
339
+ `scripts/apply_changes.mjs` should use the compatibility entrypoints published
340
+ with this package rather than carrying copied targeting or ZIP logic. The
341
+ legacy positional apply form remains supported:
342
+
343
+ ```bash
344
+ node scripts/apply_changes.mjs input.docx changes.json output.docx --author "Editor"
345
+ ```
346
+
347
+ Operation files may contain an array, an `operations` array, or a legacy
348
+ `changes` array. The wrapper delegates to the same strict, atomic, validated
349
+ CLI described above. If `--author` and operation authors are absent, its
350
+ compatibility fallback is `DOCX_REDLINE_AUTHOR` and then `Agent`. Consumers
351
+ must use the JSON status and process exit code; failed atomic work has
352
+ `written: false`, `outputPath: null`, and does not modify the output path.
353
+
354
+ ### Convert paragraph text into a Word list
355
+
356
+ ```js
357
+ const result = await applyRedlineToOxml(oxml, 'Item text', '1. Item text', {
358
+ generateRedlines: true
359
+ });
360
+ ```
361
+
362
+ ### Insert a large mixed-content block safely
363
+
364
+ Do not send a long attachment containing literal pipe rows, headings, lists,
365
+ and paragraphs as an unchecked replacement. Plan it first:
366
+
367
+ ```js
368
+ import { planStructuredReplacement } from '@ansonlai/docx-redline-js';
369
+
370
+ const plan = planStructuredReplacement(targetDescriptor, markdown, {
371
+ author: 'Agent'
372
+ });
373
+ if (!plan.valid || !plan.operation) {
374
+ throw new Error(plan.issues.map(issue => issue.message).join(' '));
375
+ }
376
+ const result = await document.applyOperations([plan.operation], {
377
+ author: 'Agent', atomic: true, validate: true
378
+ });
379
+ ```
380
+
381
+ Use blank lines between paragraphs, `#`/`##` for headings, normal Markdown
382
+ markers for lists, and a separator row immediately after every table header:
383
+
384
+ ```markdown
385
+ | Agency | Contact |
386
+ | --- | --- |
387
+ | BCHD | Dr. Jenkins |
388
+ ```
389
+
390
+ The planner returns typed `blocks`, counts, normalized Markdown, and structured
391
+ issues. `TABLE_SEPARATOR_REQUIRED` is an error: never remove `structuredContent`
392
+ or retry the same content as plain text merely to make the operation pass. Keep
393
+ the result as one atomic replacement operation so the first inserted block does
394
+ not invalidate the anchor for later blocks. After applying, require real
395
+ `w:tbl`, positive list `w:numId` values, valid redline OOXML, and independent
396
+ Accept/Reject checks.
397
+
398
+ ### Reconcile a table
399
+
400
+ ```js
401
+ import { reconcileMarkdownTableOoxml } from '@ansonlai/docx-redline-js';
402
+ const result = await reconcileMarkdownTableOoxml(tableOoxml, originalText, markdownTable);
403
+ ```
404
+
405
+ ## Module Map
406
+
407
+ ```
408
+ index.js
409
+ adapters/
410
+ config.js
411
+ xml-adapter.js
412
+ logger.js
413
+ core/
414
+ types.js
415
+ paragraph-text.js
416
+ word-xml.js
417
+ paragraph-targeting.js
418
+ list-targeting.js
419
+ table-targeting.js
420
+ engine/
421
+ oxml-engine.js
422
+ surgical-mode.js
423
+ surgical-run-splitting.js
424
+ surgical-diff-application.js
425
+ surgical-spans.js
426
+ reconstruction-mode.js
427
+ reconstruction-writer.js
428
+ format-application.js
429
+ formatting-removal.js
430
+ run-builders.js
431
+ table-mode.js
432
+ pipeline/
433
+ pipeline.js
434
+ ingestion.js
435
+ ingestion-export.js
436
+ diff-engine.js
437
+ markdown-processor.js
438
+ serialization.js
439
+ list-generation.js
440
+ services/
441
+ document-operation-session.js
442
+ document-operation-applier.js
443
+ document-operation-mutations.js
444
+ batch-operation-orchestrator.js
445
+ operation-heuristics.js
446
+ standalone-operation-runner.js
447
+ standalone-operation-runner.d.ts
448
+ document-operation-contract.js
449
+ operation-preflight.js
450
+ standalone-docx-plumbing.js
451
+ numbering-helpers.js
452
+ comment-engine.js
453
+ revision-comment-management.js
454
+ table-reconciliation.js
455
+ package-builder.js
456
+ document-inspection.js
457
+ node/
458
+ docx-document.js
459
+ zip-archive.js
460
+ orchestration/
461
+ route-plan.js
462
+ list-markdown.js
463
+ list-structural-fallback.js
464
+ ```
465
+
466
+ ## Common Patterns
467
+
468
+ ### Options and Defaults Reference
469
+
470
+ | Option | Type | Default | Description |
471
+ |--------|------|---------|-------------|
472
+ | `generateRedlines` | `boolean` | `true` | When `true`, emits Word-native tracked changes (`w:ins`/`w:del`). When `false`, applies direct text edits without revision markup. **Note: Redlines are not always the preferred method** — pass `generateRedlines: false` (or `--no-redlines` via CLI) when producing clean execution drafts, restructuring documents, or when revision clutter is unwanted. |
473
+ | `author` | `string` | `'AI Redliner'` | Reviewer/author name stamped on generated tracked changes and comments. Overridable via `DOCX_REDLINE_AUTHOR` environment variable. |
474
+ | `atomic` | `boolean` | `false` | Batch transaction mode. By default (`false`), valid edits are applied directly and failing operations report errors. When `true`, any operation failure rolls back the entire batch to the original document state (`rolledBack: true`, `hasChanges: false`). Use `atomic: true` (or `--atomic` in CLI) for high-assurance workflows. |
475
+ | `structuredContent` | `boolean` | `true` | Auto-detects Markdown tables, headings (`#`), and lists in replacement text and renders them as native Word elements (`w:tbl`, `w:pStyle`, `w:numPr`). Pass `false` to treat replacement text strictly as plain text. |
476
+ | `pairReplacements` | `boolean` | `true` | Links adjacent `<w:del>` and `<w:ins>` revisions with matching timestamps so Word groups them as a single replacement in the Reviewing Pane. |
477
+ | `strictTargets` | `boolean` | `true` (CLI/facade) | Requires exact target descriptors (`exactText`, `paragraphId`, `index`, `occurrence`, `fingerprint`) and forbids ambiguous matching. Defaults to `false` in low-level runner for backwards compatibility. |
478
+ | `existingRevisions` | `string` | `'merge-same-author'` | How to handle paragraphs with existing tracked changes. `'merge-same-author'` automatically merges subsequent edits from the same author against the pre-revision baseline while protecting different authors' revisions with `EXISTING_REVISIONS`. Pass `'accept-all-first'` to normalize prior revisions or `'reject-input'` to refuse editing revised paragraphs. |
479
+ | `removeFormatting` | `boolean` | `false` | When `true` and the text is unchanged with no Markdown hints, strips existing bold/italic/underline/strikethrough formatting. |
480
+ | `sanitizeInput` | `boolean` | `false` | Opt-in removal of standalone leading assistant-preface lines. Literal dollar signs and `\n` sequences are always preserved. |
481
+
482
+ ### Options shape
483
+
484
+ ```js
485
+ {
486
+ generateRedlines: true,
487
+ author: 'AI Redliner',
488
+ atomic: false,
489
+ structuredContent: true,
490
+ pairReplacements: true,
491
+ existingRevisions: 'merge-same-author',
492
+ removeFormatting: false,
493
+ sanitizeInput: false
494
+ }
495
+ ```
496
+
497
+ ### Typical return shape
498
+
499
+ ```js
500
+ {
501
+ oxml: string,
502
+ hasChanges: boolean,
503
+ status?: 'ok' | 'no-op' | 'error',
504
+ error?: { code: string, message: string },
505
+ warnings?: string[],
506
+ numberingXml?: string,
507
+ useNativeApi?: boolean
508
+ }
509
+ ```
510
+
233
511
  Known error codes include `PARSE_ERROR`, `TARGET_NOT_FOUND`, `PARTIAL_TARGET`,
234
- `EXISTING_REVISIONS`, `DIFF_TOKEN_LIMIT`, and `BATCH_OPERATION_FAILED`.
235
-
236
- For ingestion that must distinguish an empty document from malformed OOXML,
237
- use `ingestWordOoxmlToPlainTextResult` or
238
- `ingestWordOoxmlToMarkdownResult`. The legacy ingestion helpers intentionally
239
- retain their string-only return type and return `''` for parse failures.
240
-
241
- ### OOXML wrapping for Word insertOoxml scenarios
242
-
243
- ```js
244
- import { wrapInDocumentFragment } from '@ansonlai/docx-redline-js';
245
- const wrapped = wrapInDocumentFragment(rawOoxml, { includeNumbering: true, numberingXml });
246
- ```
247
-
248
- ### Output shape guardrail (important for packaging)
249
-
250
- When consuming `result.oxml`, do not assume the payload is always safe to write
251
- directly into `word/document.xml`.
252
-
253
- - Paragraph/range/table APIs can return a fragment, `<w:document>`, or package payload (`<pkg:package>`).
254
- - `applyOperationToDocumentXml(...).documentXml` is the document-safe path when you need a full `word/document.xml` replacement.
255
- - Use `extractReplacementNodesFromOoxml(payload)` to normalize unknown payloads.
256
- - If `sourceType === 'package'` or the payload starts with `<pkg:package`, do not write it into `word/document.xml` as-is.
257
-
258
- ## Gotchas
259
-
260
- 1. Call `configureXmlProvider` first in Node.js.
261
- 2. `applyRedlineToOxml` is async.
262
- 3. Paragraph APIs expect paragraph-level OOXML, not full `word/document.xml` in all cases.
263
- 4. List operations may return `numberingXml` that must be merged into package parts.
264
- 5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
265
- 6. `deleteCommentsByAuthorInOoxml` removes matching `comments.xml` entries and linked comment anchors/references in the document.
266
- 7. If output begins with `<pkg:package`, treat it as package-level OOXML and normalize it before writing anything back to `word/document.xml`.
267
- 8. Existing revisions are rejected by default; `accept-all-first` preserves the original OOXML on no-op, while `accept-all-first-keep-normalized` explicitly returns normalization as a change.
268
- 9. Caller content is not sanitized by default. Pass `sanitizeInput: true` only for raw assistant output; literal dollar delimiters and `\\n` sequences are never rewritten.
269
- 10. Hyperlinks, bookmarks, comment markers, tabs/breaks, and footnote/endnote references are structural OOXML and should survive adjacent redline edits.
270
- 11. Internally, create Word elements through `createWordElement` and tracked-change metadata through `createRevisionMetadata`.
271
- 12. Revision IDs are document-scoped in public operation paths. Thread the
272
- internal allocator through new string-serialization paths; generated
273
- `w:id` values are not stable across documents.
274
-
275
- ## Validation Commands
276
-
277
- ```bash
278
- npm test
279
- npm run test:isolation
280
- npm run check:types
281
- node scripts/export-validation-fixtures.mjs
282
- ```
283
-
284
- Optional Windows/Word smoke test for a completed `.docx`:
285
-
286
- ```bash
287
- npm run smoke:word -- path/to/file.docx
288
- ```
512
+ `EXISTING_REVISIONS`, `COMMENTED_CONTENT_MERGE`, `UNSAFE_REVISION_NESTING`, `UNSUPPORTED_REVISION_VIEW_MUTATION`,
513
+ `UNSAFE_PARAGRAPH_BOUNDARY`, `DIFF_TOKEN_LIMIT`, and `BATCH_OPERATION_FAILED`.
514
+
515
+ For ingestion that must distinguish an empty document from malformed OOXML,
516
+ use `ingestWordOoxmlToPlainTextResult` or
517
+ `ingestWordOoxmlToMarkdownResult`. The legacy ingestion helpers intentionally
518
+ retain their string-only return type and return `''` for parse failures.
519
+
520
+ ### Target text versus replacement text
521
+
522
+ Target resolution may normalize surrounding or repeated whitespace while
523
+ matching a paragraph. Replacement text is not normalized: tabs, line breaks,
524
+ non-breaking spaces, repeated spaces, and leading/trailing whitespace become
525
+ part of the requested edit. When editing extracted document text, copy the
526
+ exact paragraph text and modify it in place rather than round-tripping it
527
+ through a formatter that may change whitespace.
528
+
529
+ For ordinary insertions and deletions, target the visible accepted view:
530
+ inserted `w:t` text is visible and deleted `w:delText` is not. Move revisions
531
+ and other complex structures require additional care until targeting and
532
+ ingestion share one canonical text extractor. Prefer a `targetRef` plus the full
533
+ paragraph text when duplicate paragraphs are possible. Current text-only
534
+ matching can select the first matching paragraph, so callers that cannot
535
+ disambiguate safely should stop instead of guessing.
536
+
537
+ ### OOXML wrapping for Word insertOoxml scenarios
538
+
539
+ ```js
540
+ import { wrapInDocumentFragment } from '@ansonlai/docx-redline-js';
541
+ const wrapped = wrapInDocumentFragment(rawOoxml, { includeNumbering: true, numberingXml });
542
+ ```
543
+
544
+ ### Output shape guardrail (important for packaging)
545
+
546
+ When consuming `result.oxml`, do not assume the payload is always safe to write
547
+ directly into `word/document.xml`.
548
+
549
+ - Paragraph/range/table APIs can return a fragment, `<w:document>`, or package payload (`<pkg:package>`).
550
+ - `applyOperationToDocumentXml(...).documentXml` is the document-safe path when you need a full `word/document.xml` replacement.
551
+ - Use `extractReplacementNodesFromOoxml(payload)` to normalize unknown payloads.
552
+ - If `sourceType === 'package'` or the payload starts with `<pkg:package`, do not write it into `word/document.xml` as-is.
553
+
554
+ ## Gotchas
555
+
556
+ 1. Call `configureXmlProvider` first in Node.js.
557
+ 2. `applyRedlineToOxml` is async.
558
+ 3. Paragraph APIs expect paragraph-level OOXML, not full `word/document.xml` in all cases.
559
+ 4. List operations may return `numberingXml` that must be merged into package parts. When `word/numbering.xml` already exists, pass `mergeNumberingXmlBySchemaOrder` to `ensureNumberingArtifactsInZip`; without a merge callback the helper replaces the prior payload.
560
+ That replacement behavior is deprecated and will become an error in the next major version.
561
+ 5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
562
+ 6. `deleteCommentsByAuthorInOoxml` removes definitions and linked anchors only when they are present in the same OOXML payload. In a real `.docx`, `word/comments.xml` and `word/document.xml` are separate parts and must both be updated by the package integration layer.
563
+ 7. If output begins with `<pkg:package`, treat it as package-level OOXML and normalize it before writing anything back to `word/document.xml`.
564
+ 8. Existing revisions from the same author are merged by default against the pre-revision baseline (`merge-same-author`), while third-party revisions fail closed with `EXISTING_REVISIONS`. Pass `existingRevisions: 'accept-all-first'` to normalize all prior revisions first, or `'reject-input'` to refuse any revised paragraph.
565
+ 9. Caller content is not sanitized by default. Pass `sanitizeInput: true` only for raw assistant output; literal dollar delimiters and `\\n` sequences are never rewritten.
566
+ 10. Hyperlinks, bookmarks, comment markers, tabs/breaks, and footnote/endnote references are structural OOXML and should survive adjacent redline edits.
567
+ 11. Internally, create Word elements through `createWordElement` and tracked-change metadata through `createRevisionMetadata`.
568
+ 12. Revision IDs are document-scoped in public operation paths. Thread the
569
+ internal allocator through new string-serialization paths; generated
570
+ `w:id` values are not stable across documents.
571
+ 13. Splitting or cloning a run can duplicate nested `w:rPrChange` metadata.
572
+ Preserve the original ID on at most one resulting run and allocate fresh
573
+ IDs for every additional clone through the document-scoped allocator.
574
+ 14. Run `validateRedlineOoxml` on generated markup before packaging it, then
575
+ run `validateDocxPackage` after merging comments and numbering artifacts.
576
+
577
+ ## Validation Commands
578
+
579
+ ```bash
580
+ npm test
581
+ npm run test:isolation
582
+ npm run check:types
583
+ node scripts/export-validation-fixtures.mjs
584
+ ```
585
+
586
+ Optional Windows/Word smoke test for a completed `.docx`:
587
+
588
+ ```bash
589
+ npm run smoke:word -- path/to/file.docx
590
+ ```