@ansonlai/docx-redline-js 0.5.3 → 0.6.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 (59) hide show
  1. package/AGENTS.md +82 -667
  2. package/ARCHITECTURE.md +51 -4
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +176 -39
  5. package/core/paragraph-revision-safety.js +10 -8
  6. package/core/paragraph-targeting.js +14 -2
  7. package/core/redline-validation.js +7 -4
  8. package/core/revision-cloning.js +21 -0
  9. package/core/validation-delta.js +23 -0
  10. package/dist/docx-redline-js.esm.js +275 -45
  11. package/dist/docx-redline-js.esm.js.map +3 -3
  12. package/dist/docx-redline-js.esm.min.js +82 -82
  13. package/dist/docx-redline-js.esm.min.js.map +4 -4
  14. package/docs/AGENT_FAST_START.md +59 -0
  15. package/docs/AGENT_KNOWLEDGE_BASE.md +868 -0
  16. package/docs/TESTING.md +20 -1
  17. package/docs/schemas/document-operations.schema.json +16 -2
  18. package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +82 -0
  19. package/engine/oxml-engine.js +80 -13
  20. package/engine/run-builders.js +5 -15
  21. package/engine/surgical-mode.js +148 -3
  22. package/engine/surgical-run-splitting.js +19 -7
  23. package/engine/surgical-spans.js +2 -1
  24. package/index.d.ts +17 -1
  25. package/node/cli.js +235 -36
  26. package/node/docx-document.js +137 -83
  27. package/node/index.d.ts +6 -2
  28. package/package.json +10 -3
  29. package/pipeline/diff-engine.js +15 -0
  30. package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
  31. package/services/batch-operation-orchestrator.js +215 -120
  32. package/services/document-inspection.js +5 -3
  33. package/services/document-operation-applier.js +99 -36
  34. package/services/document-operation-contract.js +50 -6
  35. package/services/document-operation-mutations.js +404 -41
  36. package/services/document-operation-session.js +4 -0
  37. package/services/error-recovery.js +174 -0
  38. package/services/operation-batch-compiler.js +394 -0
  39. package/services/operation-preflight.js +91 -72
  40. package/services/standalone-operation-runner.d.ts +35 -1
  41. package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
  42. package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -856
  43. package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
  44. package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
  45. package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
  46. package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
  47. package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
  48. package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
  49. package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
  50. package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
  51. package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
  52. package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
  53. package/docs/test-comparison-dashboard.html +0 -4338
  54. package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
  55. package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
  56. package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
  57. package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
  58. package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
  59. package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
package/AGENTS.md CHANGED
@@ -1,672 +1,87 @@
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: 'slice-cross-author'` (or `--existing-revisions slice-cross-author`)
61
- to preserve the other reviewer's attribution while applying Word-native
62
- insertions and deletions inside their pending insertion. Pass
63
- `existingRevisions: 'accept-all-first'` (or `--existing-revisions accept-all-first`
64
- via CLI) to normalize all prior revisions first, or `'reject-input'` to refuse any
65
- paragraph with open revisions. Use `'accept-all-first-keep-normalized'` only when
66
- accepted revisions should be returned as a real change even on a no-op edit.
67
- Same-author merging also fails with `COMMENTED_CONTENT_MERGE` when the revised
68
- paragraph contains comment anchors, because reverting the prior revision could
69
- remove or orphan those comments. Resolve the comments before re-editing.
70
-
71
- ### Apply a text edit without tracked changes (Direct Edits)
72
-
73
- > [!IMPORTANT]
74
- > **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).
75
-
76
- ```js
77
- const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
78
- generateRedlines: false
79
- });
80
- ```
81
-
82
- ### Convert OOXML to readable text or markdown
83
-
84
- ```js
85
- import { ingestWordOoxmlToPlainText, ingestWordOoxmlToMarkdown } from '@ansonlai/docx-redline-js';
86
- const plainText = ingestWordOoxmlToPlainText(documentXml);
87
- const markdown = ingestWordOoxmlToMarkdown(documentXml);
88
- ```
89
-
90
- ### Add a comment to OOXML
91
-
92
- ```js
93
- import { injectCommentsIntoOoxml } from '@ansonlai/docx-redline-js';
94
- const result = injectCommentsIntoOoxml(paragraphOoxml, [
95
- {
96
- paragraphIndex: 1,
97
- textToFind: 'force majeure',
98
- commentContent: 'Review this clause'
99
- }
100
- ], { author: 'Agent' });
101
- ```
102
-
103
- `paragraphIndex` is 1-based within the supplied OOXML payload. The comment
104
- author belongs in the options object and applies to the injected comments.
105
-
106
- ### Accept tracked changes from one user (or all users)
107
-
108
- ```js
109
- import { acceptTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
110
- const acceptedMine = acceptTrackedChangesInOoxml(documentXml, { author: 'Agent' });
111
- const acceptedAll = acceptTrackedChangesInOoxml(documentXml, { allAuthors: true });
112
- ```
113
-
114
- ### Reject tracked changes from one user (or all users)
115
-
116
- ```js
117
- import { rejectTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
118
- const rejectedMine = rejectTrackedChangesInOoxml(documentXml, { author: 'Agent' });
119
- const rejectedAll = rejectTrackedChangesInOoxml(documentXml, { allAuthors: true });
120
- ```
121
-
122
- Move revisions are consumed too: accept removes `w:moveFrom` and unwraps
123
- `w:moveTo`; reject unwraps `w:moveFrom` and removes `w:moveTo`.
124
-
125
- ### Delete comments from one user (or all users)
126
-
127
- ```js
128
- import { deleteCommentsByAuthorInOoxml } from '@ansonlai/docx-redline-js';
129
- const removedMine = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { author: 'Agent' });
130
- const removedAll = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { allAuthors: true });
131
- ```
132
-
133
- ### Apply multiple operations to full document XML
134
-
135
- ```js
136
- import {
137
- applyOperationToDocumentXml,
138
- applyOperationsToDocumentXml
139
- } from '@ansonlai/docx-redline-js/standalone-runner';
140
-
141
- const result = await applyOperationsToDocumentXml(documentXml, operations, 'Agent', runtimeContext, options);
142
- ```
143
-
144
- The operation runner uses these field names:
145
-
146
- ```js
147
- const operations = [
148
- { type: 'redline', target: 'Old paragraph text', modified: 'New paragraph text', targetRef: 12 },
149
- { type: 'comment', target: 'Paragraph text', textToComment: 'anchor text', commentContent: 'Comment body', targetRef: 18 },
150
- { type: 'highlight', target: 'Paragraph text', textToHighlight: 'anchor text', color: 'yellow', targetRef: 24 }
151
- ];
1
+ # AGENTS.md Launch Card
2
+
3
+ Use this file to route work on `@ansonlai/docx-redline-js`. Do not explore the
4
+ whole repository before acting, and never inspect `dist/`, a vendored CLI bundle,
5
+ or an installed plugin bundle to infer public behavior.
6
+
7
+ ## Pick the route
8
+
9
+ | Task | Start here |
10
+ |---|---|
11
+ | Edit or review a complete `.docx` | [Agent Fast Start](docs/AGENT_FAST_START.md) and the `docx-redline` CLI |
12
+ | Build an agent/tool wrapper | [README wrapper example](README.md#example-agent-session-wrapper-development-only), then [knowledge base](docs/AGENT_KNOWLEDGE_BASE.md#designing-a-thin-agent-wrapper) |
13
+ | Change paragraph/range reconciliation | `index.js` `engine/oxml-engine.js` → selected `engine/*-mode.js` |
14
+ | Change complete-document operations | `services/standalone-operation-runner.js` → `services/document-operation-*.js` |
15
+ | Change DOCX ZIP or CLI behavior | `node/index.js`, `node/docx-document.js`, `node/cli.js` |
16
+ | Choose or add tests | Closest `tests/*.mjs`, then [Testing Guide](docs/TESTING.md) |
17
+ | Understand ownership/dependencies | [Architecture](ARCHITECTURE.md) |
18
+
19
+ Open the full [Agent Knowledge Base](docs/AGENT_KNOWLEDGE_BASE.md) only for the
20
+ specific advanced operation, API, or recovery topic you need.
21
+
22
+ ## Ordinary document edits
23
+
24
+ Use one focused extraction and one apply call. With a structured wrapper, use
25
+ its revision-bound target handles. For shell-only work, serialize operations to
26
+ stdin and use the explicit agent profile:
27
+
28
+ ```bash
29
+ docx-redline extract contract.docx --range 10:30
30
+ node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --output reviewed.docx
152
31
  ```
153
32
 
154
- To counterpropose text for a paragraph wholly deleted by another reviewer,
155
- use explicit restoration intent. A normal `redline` remains fail-closed with
156
- `FOREIGN_PARAGRAPH_MARK_DELETION`:
33
+ For every ordinary text operation, `modified` is the complete desired
34
+ accepted-view content. Copy inspected `exactText` verbatim and include
35
+ `paragraphId` or `fingerprint`. Independent strong targets are bound against the
36
+ batch-start document, so do not manually sort around structural edits.
37
+ Consolidate incompatible writes to the same source; use captures for intentional
38
+ created-content dependencies.
39
+
40
+ Require `completion: true`, `written: true`, a non-null output path, and no
41
+ per-operation errors. Follow `error.recovery.action` and `retryPlan`; never retry
42
+ unchanged failed arguments. Do not accept/reject another reviewer's work or
43
+ remove comments without explicit user authorization. The source is never
44
+ overwritten unless `--in-place` is explicit.
157
45
 
158
- ```js
159
- const restoration = {
160
- type: 'restore',
161
- target: { paragraphId: '1A2B3C4D' },
162
- modified: 'Restored or adjusted paragraph text.',
163
- author: 'Editor'
164
- };
46
+ Advanced restore, rejected-view insertion, list, table, formatting, comments,
47
+ revision policies, and failure examples live in the
48
+ [knowledge base](docs/AGENT_KNOWLEDGE_BASE.md#agent-document-workflow-cli) and
49
+ [operation schema](docs/schemas/document-operations.schema.json).
50
+
51
+ ## Thin wrappers
52
+
53
+ Wrap the CLI for shell hosts or `openDocx` from
54
+ `@ansonlai/docx-redline-js/node` for byte-oriented Node hosts. A wrapper should
55
+ inspect, translate narrow ergonomic inputs into canonical operations, call the
56
+ facade once, and return its structured result. Do not reproduce ZIP handling,
57
+ targeting, revision allocation, comments, numbering, validation, or rollback.
58
+
59
+ The stateful wrapper in `examples/agent-session-wrapper.mjs` is a testable
60
+ development demonstration only. It is excluded from package files and exports.
61
+ Production harnesses own their transport and negotiate the minimum CLI
62
+ `contractVersion`/capabilities they use.
63
+
64
+ ## Code map
65
+
66
+ ```text
67
+ index.js host-independent public API
68
+ core/ OOXML primitives, text views, targeting, validation
69
+ pipeline/ ingestion, diffing, Markdown, lists, serialization
70
+ engine/ reconciliation modes and run-level mutation
71
+ orchestration/ route planning and structural conversion
72
+ services/ document operations, comments, receipts, artifacts
73
+ node/ Node-only ZIP and whole-DOCX facade/CLI
74
+ tests/*.mjs directly runnable suites
165
75
  ```
166
76
 
167
- For a contiguous range, provide `targetEnd`/`targetEndRef` and one string per
168
- source paragraph in `modified`. Restoration always uses tracked changes,
169
- preserves the deleted source paragraph, and inserts the counterproposal before
170
- it with a fresh paragraph ID.
171
-
172
- `targetRef` is an optional 1-based paragraph reference used to disambiguate
173
- duplicate text. An operation-level `author` overrides the batch author; batch
174
- results report both `authorUsed` per item and the aggregate `authorsUsed` list.
175
-
176
- For safer targeting, `target` may be a descriptor:
177
-
178
- ```js
179
- {
180
- type: 'replace',
181
- target: {
182
- exactText: 'Repeated paragraph text',
183
- paragraphId: '1A2B3C4D', // when present in the source OOXML
184
- index: 12,
185
- occurrence: 2,
186
- inTable: false,
187
- fingerprint: 'fnv1a32:...'
188
- },
189
- modified: 'Replacement text',
190
- author: 'Editor'
191
- }
192
- ```
193
-
194
- Call `preflightOperations(documentXml, operations, author)` when you want a
195
- read-only inspection of an agent-generated batch before applying it. Preflight is
196
- read-only and strict by default: duplicate exact text returns `AMBIGUOUS_TARGET`,
197
- approximate text is not selected, and the result reports candidate targets,
198
- missing anchors, existing revisions, authors, required artifacts, and
199
- same-paragraph conflicts. For direct execution, `applyOperationsToDocumentXml` is
200
- already transactional and atomic by default. When permissive resolution
201
- encounters duplicate candidate paragraphs, it emits an
202
- `AMBIGUOUS_TARGET_HEURISTIC_USED` warning; migrate to `{ strictTargets: true }`
203
- with strict descriptors (`paragraphId`, `index`, `occurrence`, or `fingerprint`)
204
- before v1.0.0.
205
-
206
- Whole-paragraph deletions targeting paragraphs with existing comments fail with
207
- `COMMENTED_CONTENT_DELETE`. Resolve or remove the comments first.
208
-
209
- Use `result.documentXml` from these APIs when replacing full `word/document.xml`.
210
- For mixed batches, prefer `applyOperationsToDocumentXml(...)`; it applies comments
211
- before replacements so earlier edits cannot invalidate their anchors.
212
-
213
- 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).
214
-
215
- 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`).
216
-
217
- Internally, a batch uses one live document DOM and one revision allocator, then
218
- serializes the full document once. Every operation has a DOM/allocator savepoint;
219
- do not remove this isolation merely for speed. Redline accuracy, accepted and
220
- rejected text, and exact rollback take precedence over throughput.
221
-
222
- Every operation produces a commit-aware `receipt` (and batch-level `receipts`)
223
- enumerating exact allocated `revisionItems`, `commentIds`, `numberingIds`,
224
- `relationshipIds`, `affectedTargets`, and `warnings`. The output reconciliation
225
- oracle (`reconcileReceiptsAgainstOutput`) validates that all reported durable IDs
226
- are present in the serialized output; any discrepancy triggers rollback and fails closed.
227
-
228
- Always inspect `status` and `error`, not only `hasChanges`. A failed transform
229
- can return `{ hasChanges: false, status: 'error', error: ... }`. Missing or
230
- ambiguous comment anchors are structured errors and roll back atomic batches;
231
- `no_change` is reserved for genuine no-ops. Continue to inspect warnings for
232
- non-fatal diagnostics.
233
-
234
- ### Detect existing tracked changes
235
-
236
- ```js
237
- import { containsTrackedChanges } from '@ansonlai/docx-redline-js';
238
- const hasTrackedChanges = containsTrackedChanges(xmlDoc);
239
- ```
240
-
241
- ### Inspect document parts before editing
242
-
243
- ```js
244
- import { inspectDocumentParts } from '@ansonlai/docx-redline-js';
245
- const inspection = inspectDocumentParts({ documentXml, commentsXml, numberingXml });
246
- ```
247
-
248
- Reuse `exactText` plus `paragraphId` or `fingerprint` in an operation. Computed
249
- list labels and excerpts are for display, not replacements for exact targets.
250
-
251
- ### Safely edit a complete DOCX in Node
252
-
253
- ```js
254
- import { openDocx } from '@ansonlai/docx-redline-js/node';
255
- const document = openDocx(inputBuffer);
256
- const result = await document.applyOperations(operations, {
257
- author: 'Agent', atomic: true, validate: true
258
- });
259
- if (!result.written) throw new Error(result.error?.message || 'No output written');
260
- const outputBuffer = result.toBuffer();
261
- ```
262
-
263
- This facade defaults to strict targets, allocates package-safe comment IDs,
264
- merges numbering, updates relationships/content types, and rolls back to the
265
- original buffer when an atomic transaction fails.
266
-
267
- ### Agent Document Workflow (CLI)
268
-
269
- Use the `docx-redline` CLI for complete `.docx` files. It emits JSON on stdout,
270
- keeps exact text intact, and never overwrites the source unless `--in-place` is
271
- explicitly supplied.
272
-
273
- #### Standard Workflow (Fast & Direct)
274
-
275
- Use this for everything by default. `apply` is fast, progressive, and self-validating by default—it validates the resulting package and revision markup internally before writing. **Do not insert a `preflight` or baseline `validate` step on top of it "to be safe"**; `apply` already covers that internally. It supports inline one-liners as well as batch operations files:
276
-
277
- ```bash
278
- # 1. Inline one-liner edit (fastest for 1–2 edits; no JSON file needed)
279
- docx-redline apply contract.docx --target "Original clause" --modified "New clause" --output reviewed.docx
280
-
281
- # 2. Direct edit without tracked changes (clean text, no revision clutter)
282
- docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
283
-
284
- # 3. Cross-author edit inside another reviewer's pending insertion
285
- docx-redline apply contract.docx --target "Pending clause text" --modified "Updated clause text" --existing-revisions slice-cross-author --output reviewed.docx
286
-
287
- # 4. Batch operations with ops.json
288
- docx-redline apply contract.docx --operations operations.json --output reviewed.docx
289
- ```
290
-
291
- Key CLI defaults and behaviors:
292
- - **Author**: Automatically defaults to `'AI Redliner'` (overridable via `--author` or `DOCX_REDLINE_AUTHOR` environment variable).
293
- - **Existing revisions**: Defaults to `'merge-same-author'`. Pass `--existing-revisions slice-cross-author` to edit inside another reviewer's pending insertions with native carrier slicing.
294
- - **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.
295
- - **Tracked changes**: Defaults to `generateRedlines: true`. When clean direct text is needed, pass `--no-redlines`.
296
- - **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`.
297
- - 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.
298
-
299
- 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.
300
-
301
- #### High-Assurance / Staged Verification Workflow (Optional)
302
-
303
- This is an opt-in, higher-latency path for cases like large automated batch
304
- migrations or workflows where the user specifically requests a non-mutating dry run
305
- and an independent baseline audit report. **Never switch into it on your own initiative**
306
- (not even for "high-stakes" contracts); unless the user explicitly requests it, stick with the
307
- Standard workflow above. Use the extended verification cycle:
308
-
309
- ```bash
310
- docx-redline inspect contract.docx --non-empty
311
- docx-redline extract contract.docx --range 10:30 > paragraphs.json
312
- docx-redline preflight contract.docx --operations operations.json --author "Editor"
313
- docx-redline apply contract.docx --operations operations.json --author "Editor" --output reviewed.docx
314
- docx-redline validate reviewed.docx --baseline contract.docx
315
- ```
316
-
317
- Copy `exactText`, `paragraphId`, and `fingerprint` from `extract` into operation
318
- targets. For most unique clauses, `"target": "exact paragraph text"` is
319
- sufficient; use discriminators (`paragraphId`, `fingerprint`, `index`, or
320
- `occurrence`) when duplicate paragraph text appears in the document. Never
321
- normalize or reconstruct `exactText`. Operation files follow
322
- [`docs/schemas/document-operations.schema.json`](docs/schemas/document-operations.schema.json).
323
-
324
- #### Commands
325
-
326
- - `inspect` returns the structured inventory, comments, authors, and counts.
327
- - `extract` returns a compact target inventory with exact text.
328
- - `preflight` checks targets, anchors, revisions, conflicts, authors, and needed artifacts without mutation (read-only).
329
- - `apply` applies an operation file transactionally with automatic rollback and internal markup validation.
330
- - `accept` and `reject` resolve revisions selected by `--author` or `--all-authors`.
331
- - `delete-comments` removes matching definitions and document anchors together.
332
- - A whole-paragraph delete stops with `COMMENTED_CONTENT_DELETE` when the
333
- paragraph has an existing comment. Surface the returned reviewer and comment
334
- text for human follow-up; do not silently convert this into comment removal.
335
- - `validate` audits revision markup and DOCX package wiring, optionally comparing against a `--baseline`.
336
-
337
- Paragraph indexes are 1-based. Inspection filters are `--index 12`,
338
- `--range 10:30`, `--indexes 2,5,8`, `--search text`, `--revised`, `--table`,
339
- `--body`, `--non-empty`, and `--view accepted|rejected|current`. A malformed
340
- filter or unknown option is an error rather than an unfiltered fallback.
341
-
342
- Mutating commands require `--author`, authors on every operation, or
343
- `--all-authors` where applicable. Without `--output`, a sibling such as
344
- `contract.redlined.docx` is chosen. Existing outputs are refused unless
345
- `--force` is present. `--in-place` is the only way to overwrite the input.
346
-
347
- Treat a nonzero exit code or JSON `status: "error"` as failure. A failed atomic
348
- operation reports `written: false` and does not write an output file.
349
- Missing or repeated comment anchors are errors rather than no-ops. Explicit
350
- anchors match exact text first and then a unique ordinary-space/NBSP equivalent;
351
- omit `textToComment` to comment the entire resolved paragraph.
352
-
353
- To reply inside an existing Word comment thread, use the comment ID returned by
354
- `inspect` and do not supply a paragraph target:
355
-
356
- ```json
357
- { "type": "comment_reply", "parentCommentId": "8", "commentContent": "Agreed; updated.", "author": "Editor" }
358
- ```
359
-
360
- Replies are represented in `word/commentsExtended.xml` and deliberately add no
361
- new `commentRangeStart`, `commentRangeEnd`, or `commentReference` to the body.
362
-
363
- #### Legacy skill wrapper migration
364
-
365
- Older skills that invoke `scripts/extract_text.mjs` and
366
- `scripts/apply_changes.mjs` should use the compatibility entrypoints published
367
- with this package rather than carrying copied targeting or ZIP logic. The
368
- legacy positional apply form remains supported:
369
-
370
- ```bash
371
- node scripts/apply_changes.mjs input.docx changes.json output.docx --author "Editor"
372
- ```
373
-
374
- Operation files may contain an array, an `operations` array, or a legacy
375
- `changes` array. The wrapper delegates to the same strict, atomic, validated
376
- CLI described above. If `--author` and operation authors are absent, its
377
- compatibility fallback is `DOCX_REDLINE_AUTHOR` and then `Agent`. Consumers
378
- must use the JSON status and process exit code; failed atomic work has
379
- `written: false`, `outputPath: null`, and does not modify the output path.
380
-
381
- #### Safe Operations File Creation (JSON vs. Shell Heredocs)
382
-
383
- When composing batch operations files (`operations.json`):
384
-
385
- - **Use structured file-writing tools or JSON serializers**: Write operations files via your environment's file-creation tools or a language JSON serializer (`JSON.stringify`).
386
- - **Never compose operations in raw shell heredocs** (e.g., `cat << 'EOF'` in bash or PowerShell `@" ... "@`): Legal clauses routinely contain curly quotes (`“ ”`), smart apostrophes (`’`), em-dashes (`—`), section symbols (`§`), non-breaking spaces, and backslashes. Shell heredocs frequently mangle Unicode character encodings, quote escaping, and whitespace formatting, causing immediate `TARGET_NOT_FOUND` failures.
387
-
388
- #### Walking Progressive Batch Results (Status & Partial Execution)
389
-
390
- In default progressive mode (`atomic: false`), operations execute independently: valid operations commit to the document while failing operations report errors without aborting the batch:
391
-
392
- - **Do not rely solely on top-level `written: true` or `status !== "error"`**: A progressive batch can return `status: "partial"` with `written: true` when some operations succeed and others fail.
393
- - **Walk every entry in `results`**: Check `results[i].status` and `results[i].error`. Any `status: "error"` entry in `results` represents an unapplied change that must be investigated and resolved.
394
- - **`written: false`**: Indicates that zero operations were committed (or an atomic rollback occurred). Never treat or present an unwritten or partial output file as complete.
395
-
396
- #### Human-Readable References vs. Internal Machine Handles
397
-
398
- Target handles such as `ref` (`P<index>`), `targetRef`, and bare paragraph `index` numbers are **strictly internal machine handles** for the CLI and engine. They do not correspond to any visual or followable marker in Microsoft Word:
399
-
400
- - **Never surface `P11`, `P42`, or bare paragraph numbers** in user-facing prose, comments, redline summaries, or negotiation notes.
401
- - Instead, cite locations using the human-readable fields provided by `inspect` / `extract`:
402
- - **`provision`**: Lead with section/clause numbers when present (e.g., `§14.1 Entire Agreement`).
403
- - **`nearestHeading` + ordinal offset**: When `provision` is absent, describe position relative to the nearest heading (e.g., `under "Limitation of Liability", 2nd paragraph`).
404
- - **Structural context**: For unnumbered clauses prior to the first heading, use plain language (e.g., `opening recital, before Section 1`).
405
- - **`humanReference`**: Use the pre-joined citation string provided directly on inspected paragraph objects.
406
-
407
- #### Actionable Error Recovery Matrix
408
-
409
- When the CLI or runner returns an error code, follow these specific recovery actions:
410
-
411
- | Error Code | Meaning | Actionable Recovery |
412
- |---|---|---|
413
- | `TARGET_NOT_FOUND` | Target text did not match any paragraph. | **Do NOT retry with paraphrased text.** Re-run `extract`/`inspect`, copy `exactText` verbatim (including exact whitespace/punctuation), and add a discriminator (`paragraphId`, `fingerprint`, or `occurrence`). |
414
- | `AMBIGUOUS_TARGET` | Multiple paragraphs match identical text. | Disambiguate by supplying `paragraphId`, `fingerprint`, `occurrence`, or `index` in the target descriptor. |
415
- | `ANCHOR_NOT_FOUND` / `AMBIGUOUS_ANCHOR` | Comment anchor text was not uniquely matched in paragraph. | Narrow `textToComment` to a unique exact substring, or omit `textToComment` to anchor the comment to the entire paragraph. |
416
- | `OVERLAPPING_TEXT_EDITS` | Multiple operations target the same paragraph concurrently. | Consolidate all changes to the same paragraph into a single `redline` or `replace` operation. |
417
- | `EXISTING_REVISIONS` | Target paragraph contains tracked changes from another author. | Fails closed to protect third-party review marks. If editing inside that reviewer's pending insertion is intended, pass `--existing-revisions slice-cross-author` (or `existingRevisions: 'slice-cross-author'`). Do not pass `accept-all-first` without explicit user authorization. |
418
- | `PATCH_ROUNDTRIP_MISMATCH` | A cross-author surgical edit did not reconstruct the requested modified text exactly. | Treat the operation as unapplied. Re-extract the exact paragraph text and split the edit into a narrower operation that does not cross the reported structural boundary. |
419
- | `FOREIGN_PARAGRAPH_MARK_DELETION` | A normal edit attempted to write into a paragraph wholly deleted by another reviewer. | Use an explicit `restore` operation if the user intends to counterpropose that paragraph; otherwise leave the deletion unresolved. |
420
- | `RESTORATION_STATE_REQUIRED` / `RESTORATION_COUNT_MISMATCH` | A `restore` target is not a wholly foreign-deleted paragraph, or its replacement count does not match the paragraph range. | Re-inspect the document and target the deleted paragraph by stable descriptor; provide exactly one replacement string per source paragraph. |
421
- | `UNSAFE_DELETED_TABLE_ROW` / `UNSUPPORTED_MOVE_REVISION` / `SECTION_BREAK_PARAGRAPH` / `UNSAFE_PARAGRAPH_PLACEMENT` | Paragraph restoration cannot preserve the source structural boundary safely. | Do not retry as an ordinary redline. Resolve the row/move/section/placement condition manually or narrow the restoration to a safe paragraph. |
422
- | `COMMENTED_CONTENT_MERGE` / `COMMENTED_CONTENT_DELETE` | Operation would overwrite, revert, or delete content with comments. | Fails closed to prevent orphaned comment threads. Report the comment author and text to the user; resolve the comment before re-editing. |
423
- | `INVALID_OPERATION` | Operation object violates schema or has incompatible fields. | Validate the JSON structure against [`document-operations.schema.json`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/docs/schemas/document-operations.schema.json) before targeting is attempted. |
424
- | `STRUCTURED_CONTENT_INVALID` | Malformed Markdown table or structure in replacement text. | Ensure tables include a separator row (`\| --- \| --- \|`) and consistent column counts; do not downgrade to raw text. |
425
-
426
- **Important Rule:** Never repeat the exact same failing command without correcting the reported cause. If an error persists after one correction attempt, stop and report the diagnostic code to the user.
427
-
428
- #### Document Scope & Boundary Invariants
429
-
430
- The `docx-redline` engine and CLI operate specifically on the **main document body**:
431
-
432
- - **Supported Content**: Body paragraphs, numbered/bulleted lists, tables and table cells, comments, and comment replies.
433
- - **Unsupported Content**: Headers, footers, footnotes, endnotes, floating text boxes, shape drawings, watermarks, and embedded macros.
434
- - Do not attempt to target, edit, or comment on header/footer text or footnote citations using `docx-redline`. Use specialized document manipulation tools or manual editing for layout frames outside the body text.
435
-
436
- ### Convert paragraph text into a Word list
437
-
438
- ```js
439
- const result = await applyRedlineToOxml(oxml, 'Item text', '1. Item text', {
440
- generateRedlines: true
441
- });
442
- ```
443
-
444
- ### Insert a large mixed-content block safely
445
-
446
- Do not send a long attachment containing literal pipe rows, headings, lists,
447
- and paragraphs as an unchecked replacement. Plan it first:
448
-
449
- ```js
450
- import { planStructuredReplacement } from '@ansonlai/docx-redline-js';
451
-
452
- const plan = planStructuredReplacement(targetDescriptor, markdown, {
453
- author: 'Agent'
454
- });
455
- if (!plan.valid || !plan.operation) {
456
- throw new Error(plan.issues.map(issue => issue.message).join(' '));
457
- }
458
- const result = await document.applyOperations([plan.operation], {
459
- author: 'Agent', atomic: true, validate: true
460
- });
461
- ```
462
-
463
- Use blank lines between paragraphs, `#`/`##` for headings, normal Markdown
464
- markers for lists, and a separator row immediately after every table header:
465
-
466
- ```markdown
467
- | Agency | Contact |
468
- | --- | --- |
469
- | BCHD | Dr. Jenkins |
470
- ```
471
-
472
- The planner returns typed `blocks`, counts, normalized Markdown, and structured
473
- issues. `TABLE_SEPARATOR_REQUIRED` is an error: never remove `structuredContent`
474
- or retry the same content as plain text merely to make the operation pass. Keep
475
- the result as one atomic replacement operation so the first inserted block does
476
- not invalidate the anchor for later blocks. After applying, require real
477
- `w:tbl`, positive list `w:numId` values, valid redline OOXML, and independent
478
- Accept/Reject checks.
479
-
480
- ### Reconcile a table
481
-
482
- ```js
483
- import { reconcileMarkdownTableOoxml } from '@ansonlai/docx-redline-js';
484
- const result = await reconcileMarkdownTableOoxml(tableOoxml, originalText, markdownTable);
485
- ```
486
-
487
- ## Module Map
488
-
489
- ```
490
- index.js
491
- adapters/
492
- config.js
493
- xml-adapter.js
494
- logger.js
495
- core/
496
- types.js
497
- paragraph-text.js
498
- word-xml.js
499
- paragraph-targeting.js
500
- list-targeting.js
501
- table-targeting.js
502
- engine/
503
- oxml-engine.js
504
- surgical-mode.js
505
- surgical-run-splitting.js
506
- surgical-diff-application.js
507
- surgical-spans.js
508
- reconstruction-mode.js
509
- reconstruction-writer.js
510
- format-application.js
511
- formatting-removal.js
512
- run-builders.js
513
- table-mode.js
514
- pipeline/
515
- pipeline.js
516
- ingestion.js
517
- ingestion-export.js
518
- diff-engine.js
519
- markdown-processor.js
520
- serialization.js
521
- list-generation.js
522
- services/
523
- document-operation-session.js
524
- document-operation-applier.js
525
- document-operation-mutations.js
526
- batch-operation-orchestrator.js
527
- operation-heuristics.js
528
- standalone-operation-runner.js
529
- standalone-operation-runner.d.ts
530
- document-operation-contract.js
531
- operation-preflight.js
532
- standalone-docx-plumbing.js
533
- numbering-helpers.js
534
- comment-engine.js
535
- revision-comment-management.js
536
- table-reconciliation.js
537
- package-builder.js
538
- document-inspection.js
539
- node/
540
- docx-document.js
541
- zip-archive.js
542
- orchestration/
543
- route-plan.js
544
- list-markdown.js
545
- list-structural-fallback.js
546
- ```
547
-
548
- ## Common Patterns
549
-
550
- ### Options and Defaults Reference
551
-
552
- | Option | Type | Default | Description |
553
- |--------|------|---------|-------------|
554
- | `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. |
555
- | `author` | `string` | `'AI Redliner'` | Reviewer/author name stamped on generated tracked changes and comments. Overridable via `DOCX_REDLINE_AUTHOR` environment variable. |
556
- | `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. |
557
- | `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. |
558
- | `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. |
559
- | `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. |
560
- | `existingRevisions` | `string` | `'merge-same-author'` | How to handle paragraphs with existing tracked changes. `'merge-same-author'` merges the same author's work and protects other authors with `EXISTING_REVISIONS`. `'slice-cross-author'` retains same-author merging while allowing Word-native edits inside another author's pending insertion. Pass `'accept-all-first'` to normalize prior revisions or `'reject-input'` to refuse editing revised paragraphs. |
561
- | `removeFormatting` | `boolean` | `false` | When `true` and the text is unchanged with no Markdown hints, strips existing bold/italic/underline/strikethrough formatting. |
562
- | `sanitizeInput` | `boolean` | `false` | Opt-in removal of standalone leading assistant-preface lines. Literal dollar signs and `\n` sequences are always preserved. |
563
-
564
- ### Options shape
565
-
566
- ```js
567
- {
568
- generateRedlines: true,
569
- author: 'AI Redliner',
570
- atomic: false,
571
- structuredContent: true,
572
- pairReplacements: true,
573
- existingRevisions: 'merge-same-author',
574
- removeFormatting: false,
575
- sanitizeInput: false
576
- }
577
- ```
578
-
579
- ### Typical return shape
580
-
581
- ```js
582
- {
583
- oxml: string,
584
- hasChanges: boolean,
585
- status?: 'ok' | 'no-op' | 'error',
586
- error?: { code: string, message: string },
587
- warnings?: string[],
588
- numberingXml?: string,
589
- useNativeApi?: boolean
590
- }
591
- ```
592
-
593
- Known error codes include `PARSE_ERROR`, `TARGET_NOT_FOUND`, `PARTIAL_TARGET`,
594
- `EXISTING_REVISIONS`, `COMMENTED_CONTENT_MERGE`, `UNSAFE_REVISION_NESTING`, `UNSUPPORTED_REVISION_VIEW_MUTATION`,
595
- `UNSAFE_PARAGRAPH_BOUNDARY`, `DIFF_TOKEN_LIMIT`, and `BATCH_OPERATION_FAILED`.
596
-
597
- For ingestion that must distinguish an empty document from malformed OOXML,
598
- use `ingestWordOoxmlToPlainTextResult` or
599
- `ingestWordOoxmlToMarkdownResult`. The legacy ingestion helpers intentionally
600
- retain their string-only return type and return `''` for parse failures.
601
-
602
- ### Target text versus replacement text
603
-
604
- Target resolution may normalize surrounding or repeated whitespace while
605
- matching a paragraph. Replacement text is not normalized: tabs, line breaks,
606
- non-breaking spaces, repeated spaces, and leading/trailing whitespace become
607
- part of the requested edit. When editing extracted document text, copy the
608
- exact paragraph text and modify it in place rather than round-tripping it
609
- through a formatter that may change whitespace.
610
-
611
- For ordinary insertions and deletions, target the visible accepted view:
612
- inserted `w:t` text is visible and deleted `w:delText` is not. Move revisions
613
- and other complex structures require additional care until targeting and
614
- ingestion share one canonical text extractor. Prefer a `targetRef` plus the full
615
- paragraph text when duplicate paragraphs are possible. Current text-only
616
- matching can select the first matching paragraph, so callers that cannot
617
- disambiguate safely should stop instead of guessing.
618
-
619
- ### OOXML wrapping for Word insertOoxml scenarios
620
-
621
- ```js
622
- import { wrapInDocumentFragment } from '@ansonlai/docx-redline-js';
623
- const wrapped = wrapInDocumentFragment(rawOoxml, { includeNumbering: true, numberingXml });
624
- ```
625
-
626
- ### Output shape guardrail (important for packaging)
627
-
628
- When consuming `result.oxml`, do not assume the payload is always safe to write
629
- directly into `word/document.xml`.
630
-
631
- - Paragraph/range/table APIs can return a fragment, `<w:document>`, or package payload (`<pkg:package>`).
632
- - `applyOperationToDocumentXml(...).documentXml` is the document-safe path when you need a full `word/document.xml` replacement.
633
- - Use `extractReplacementNodesFromOoxml(payload)` to normalize unknown payloads.
634
- - If `sourceType === 'package'` or the payload starts with `<pkg:package`, do not write it into `word/document.xml` as-is.
635
-
636
- ## Gotchas
637
-
638
- 1. Call `configureXmlProvider` first in Node.js.
639
- 2. `applyRedlineToOxml` is async.
640
- 3. Paragraph APIs expect paragraph-level OOXML, not full `word/document.xml` in all cases.
641
- 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.
642
- That replacement behavior is deprecated and will become an error in the next major version.
643
- 5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
644
- 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.
645
- 7. If output begins with `<pkg:package`, treat it as package-level OOXML and normalize it before writing anything back to `word/document.xml`.
646
- 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: 'slice-cross-author'` to preserve third-party attribution while editing inside pending insertions, `'accept-all-first'` to normalize all prior revisions first, or `'reject-input'` to refuse any revised paragraph.
647
- 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.
648
- 10. Hyperlinks, bookmarks, comment markers, tabs/breaks, and footnote/endnote references are structural OOXML and should survive adjacent redline edits.
649
- 11. Internally, create Word elements through `createWordElement` and tracked-change metadata through `createRevisionMetadata`.
650
- 12. Revision IDs are document-scoped in public operation paths. Thread the
651
- internal allocator through new string-serialization paths; generated
652
- `w:id` values are not stable across documents.
653
- 13. Splitting or cloning a run can duplicate nested `w:rPrChange` metadata.
654
- Preserve the original ID on at most one resulting run and allocate fresh
655
- IDs for every additional clone through the document-scoped allocator.
656
- 14. Run `validateRedlineOoxml` on generated markup before packaging it, then
657
- run `validateDocxPackage` after merging comments and numbering artifacts.
658
-
659
- ## Validation Commands
660
-
661
- ```bash
662
- npm test
663
- npm run test:isolation
664
- npm run check:types
665
- node scripts/export-validation-fixtures.mjs
666
- ```
667
-
668
- Optional Windows/Word smoke test for a completed `.docx`:
669
-
670
- ```bash
671
- npm run smoke:word -- path/to/file.docx
672
- ```
77
+ Keep shared modules from importing `index.js`; keep host-independent code from
78
+ importing `node/`. Use `rg` to follow only the symbol being changed. Preserve
79
+ unrelated worktree changes and edit source files, never generated `dist/` files.
80
+
81
+ ## Verification
82
+
83
+ Run the closest test first: `node tests/<focused-suite>.mjs`. Use `npm test` for
84
+ cross-subsystem or release handoff, plus `npm run check:types` and
85
+ `npm run test:isolation` when boundaries or declarations change. Word COM,
86
+ visual, corpus, coverage, and fixture-generation lanes are separate; select them
87
+ from the [Testing Guide](docs/TESTING.md).