@ansonlai/docx-redline-js 0.5.4 → 0.6.1

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 (55) hide show
  1. package/AGENTS.md +82 -697
  2. package/ARCHITECTURE.md +13 -1
  3. package/CHANGELOG.md +8 -0
  4. package/README.md +177 -45
  5. package/core/paragraph-targeting.js +14 -2
  6. package/dist/docx-redline-js.esm.js +184 -51
  7. package/dist/docx-redline-js.esm.js.map +3 -3
  8. package/dist/docx-redline-js.esm.min.js +77 -77
  9. package/dist/docx-redline-js.esm.min.js.map +4 -4
  10. package/docs/AGENT_FAST_START.md +59 -0
  11. package/docs/AGENT_KNOWLEDGE_BASE.md +878 -0
  12. package/docs/SKILL_AUTHORING.md +126 -0
  13. package/docs/TESTING.md +35 -1
  14. package/docs/schemas/document-operations.schema.json +5 -1
  15. package/docs/validation-reports/2026-09-12-agent-cli-discovery-baseline.md +56 -0
  16. package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +86 -0
  17. package/docs/validation-reports/2026-09-13-agent-cli-efficiency-rollout.md +86 -0
  18. package/engine/oxml-engine.js +80 -13
  19. package/engine/run-builders.js +5 -15
  20. package/index.d.ts +28 -3
  21. package/node/cli-help.js +209 -0
  22. package/node/cli.js +323 -65
  23. package/node/docx-document.js +120 -69
  24. package/node/index.d.ts +6 -2
  25. package/package.json +15 -3
  26. package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
  27. package/services/batch-operation-orchestrator.js +215 -120
  28. package/services/document-inspection.js +89 -11
  29. package/services/document-operation-applier.js +52 -34
  30. package/services/document-operation-contract.js +10 -6
  31. package/services/document-operation-mutations.js +51 -5
  32. package/services/document-operation-session.js +4 -0
  33. package/services/error-recovery.js +174 -0
  34. package/services/operation-batch-compiler.js +394 -0
  35. package/services/operation-preflight.js +91 -72
  36. package/services/standalone-operation-runner.d.ts +17 -1
  37. package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
  38. package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -1399
  39. package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
  40. package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
  41. package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
  42. package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
  43. package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
  44. package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
  45. package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
  46. package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
  47. package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
  48. package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
  49. package/docs/test-comparison-dashboard.html +0 -4338
  50. package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
  51. package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
  52. package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
  53. package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
  54. package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
  55. package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
@@ -0,0 +1,878 @@
1
+ # Agent Knowledge Base
2
+
3
+ > Detailed reference for @ansonlai/docx-redline-js. All code and command paths
4
+ > are relative to the repository root. Start with `../AGENTS.md` for repository
5
+ > work or `AGENT_FAST_START.md` for ordinary document edits; open this file only
6
+ > when the quick guide points here or the task needs deeper behavior.
7
+
8
+ ## Start Here: Repository Layout
9
+
10
+ This is one JavaScript package with three supported public surfaces. Choose the
11
+ surface before following imports:
12
+
13
+ | Use case | Public entry point | Primary implementation |
14
+ |---|---|---|
15
+ | Paragraph, range, list, table, comment, and OOXML transforms in any DOM-capable runtime | `index.js` (`@ansonlai/docx-redline-js`) | `engine/`, `pipeline/`, `core/`, and focused `services/` |
16
+ | Operations against complete `word/document.xml` strings | `services/standalone-operation-runner.js` (`@ansonlai/docx-redline-js/standalone-runner`) | `services/document-operation-*.js` and `services/batch-operation-orchestrator.js` |
17
+ | Complete `.docx` buffers and the CLI in Node.js | `node/index.js` (`@ansonlai/docx-redline-js/node`) and `bin/docx-redline.js` | `node/docx-document.js`, `node/zip-archive.js`, and package-plumbing services |
18
+
19
+ The source tree is layered as follows:
20
+
21
+ ```text
22
+ index.js root, host-independent public exports
23
+ adapters/ injected XML, configuration, and logging adapters
24
+ core/ shared OOXML primitives, text views, targeting, validation
25
+ pipeline/ ingestion, diffing, markdown, lists, and serialization stages
26
+ engine/ paragraph/range reconciliation and surgical/reconstruction modes
27
+ orchestration/ route planning and structural list operation conversion
28
+ services/ document operations, comments, receipts, package artifacts
29
+ node/ Node-only ZIP and whole-DOCX facade; keep out of root imports
30
+ bin/ CLI launcher; behavior belongs in node/ or services/
31
+ tests/*.mjs directly runnable suites discovered by scripts/run-tests.mjs
32
+ tests/helpers/ shared test utilities; not standalone suites
33
+ tests/fixtures/ checked-in synthetic/golden inputs and expected outputs
34
+ scripts/ build, fixture generation, benchmarks, and Word automation
35
+ docs/ schemas, testing guidance, plans, and generated reports
36
+ dist/ generated bundle; do not edit by hand
37
+ ```
38
+
39
+ `AGENTS.md` is the committed repository-wide agent guide. `.agent/` is ignored
40
+ local-only agent configuration and must not be treated as package source or as
41
+ instructions that will exist in another clone or in CI.
42
+
43
+ ### Route Changes Without Exploring Everything
44
+
45
+ 1. Identify the public surface in the table above.
46
+ 2. Read its entry point, then follow only the symbol being changed.
47
+ 3. Start from the closest existing test named for the behavior. Do not scan all
48
+ of `tests/` or unpack every fixture to understand one code path.
49
+ 4. Use `rg` for symbols and filenames. Consult `ARCHITECTURE.md` for ownership
50
+ and `docs/TESTING.md` for test-lane selection before inventing a new route,
51
+ helper, test harness, or fixture generator.
52
+ 5. Keep dependency direction inward: shared implementation modules must not
53
+ import `index.js`, and host-independent code must not import `node/`.
54
+ 6. Do not inspect `dist/`, a vendored CLI bundle, or an installed plugin bundle
55
+ to discover public behavior. Use this file, `README.md`,
56
+ `docs/schemas/document-operations.schema.json`, and the unbundled source.
57
+
58
+ ### Task-to-Module Shortcuts
59
+
60
+ | Task | Start here | Common focused tests |
61
+ |---|---|---|
62
+ | Basic text redline, formatting, or route choice | `engine/oxml-engine.js`, then the selected `engine/*-mode.js` | `tests/engine_reliability_tests.mjs`, `tests/formatting_tests.mjs` |
63
+ | Run splitting or edits inside revisions | `engine/surgical-mode.js`, `engine/surgical-run-splitting.js`, `engine/surgical-diff-application.js` | `tests/revision_split_injection_tests.mjs`, `tests/cross_author_slicing_synthetic_tests.mjs` |
64
+ | Accepted/rejected/current text or paragraph targeting | `core/paragraph-text.js`, `core/paragraph-targeting.js` | `tests/canonical_paragraph_text_tests.mjs`, `tests/revision_view_target_tests.mjs` |
65
+ | Lists, numbering, or markdown structure | `pipeline/list-generation.js`, `pipeline/structured-content.js`, `services/numbering-helpers.js` | `tests/list_tests.mjs`, `tests/list_replacement_structure_tests.mjs`, `tests/structured_content_planner_tests.mjs` |
66
+ | Tables | `engine/table-mode.js`, `core/table-targeting.js`, `services/table-reconciliation.js` | `tests/table_tests.mjs`, `tests/table_targeting_and_format_flags.mjs` |
67
+ | Comments and replies | `services/comment-engine.js`, `services/comment-replies.js`, `services/comment-package.js` | `tests/comment_tests.mjs`, `tests/comment_reply_tests.mjs` |
68
+ | Full-document operation scheduling or rollback | `services/document-operation-applier.js`, `services/batch-operation-orchestrator.js`, `services/document-operation-session.js` | `tests/standalone_operation_runner_tests.mjs`, `tests/performance_phase1_session_tests.mjs` |
69
+ | DOCX ZIP wiring or CLI behavior | `node/docx-document.js`, `node/zip-archive.js`, `node/cli.js` | `tests/docx_package_facade_tests.mjs`, `tests/node_zip_archive_tests.mjs`, `tests/agent_cli_tests.mjs` |
70
+ | Validation, receipts, and lifecycle oracles | `core/redline-validation.js`, `services/receipt-collector.js`, `services/revision-comment-management.js` | `tests/redline_validation_tests.mjs`, `tests/mutation_receipt_tests.mjs`, `tests/roundtrip_oracle_tests.mjs` |
71
+
72
+ Run one focused suite with `node tests/<name>.mjs`. Run `npm test` only when the
73
+ change crosses several subsystems or before release-level handoff. The Word COM,
74
+ visual, corpus, coverage, and fixture-export commands are separate lanes; use
75
+ them only when `docs/TESTING.md` says that lane proves the behavior in question.
76
+
77
+ ## What This Package Does
78
+
79
+ 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.
80
+
81
+ ## Conceptual Model
82
+
83
+ ```
84
+ Input: (paragraph OOXML, original text, modified text, options)
85
+ |
86
+ v
87
+ Engine routes to: format-only | surgical | reconstruction | list | table mode
88
+ |
89
+ v
90
+ Output: { oxml: string, hasChanges: boolean, status?: string, error?: object, warnings?: string[] }
91
+ ```
92
+
93
+ The engine usually works at paragraph/range/table scope. For full-document
94
+ operations, use the standalone operation runner so the result is safe to write
95
+ back to `word/document.xml`.
96
+
97
+ ## Entry Point
98
+
99
+ ```js
100
+ import { applyRedlineToOxml, configureXmlProvider } from '@ansonlai/docx-redline-js';
101
+ ```
102
+
103
+ `index.js` is the primary host-independent entry point. Complete document XML
104
+ and `.docx` package workflows use the standalone runner and Node facade listed
105
+ in the repository-layout table above.
106
+
107
+ ## Required Setup (Node.js only)
108
+
109
+ ```js
110
+ import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
111
+ configureXmlProvider({ DOMParser, XMLSerializer });
112
+ ```
113
+
114
+ Browsers have native DOM APIs, so no provider injection is typically needed.
115
+
116
+ ## Key APIs by Use Case
117
+
118
+ ### Apply a text edit with tracked changes
119
+
120
+ ```js
121
+ const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
122
+ generateRedlines: true,
123
+ author: 'Agent Name'
124
+ });
125
+ ```
126
+
127
+ `existingRevisions` defaults to `'merge-same-author'`. When a target paragraph
128
+ contains tracked changes from the same author, prior revisions by that author are
129
+ reverted to the pre-revision baseline and re-diffed to the new text, cleanly
130
+ merging the edits without accumulating intermediate revisions or nesting markup.
131
+ If the paragraph contains revisions from a different reviewer, the edit fails
132
+ with `EXISTING_REVISIONS` to safeguard third-party marks. Pass
133
+ `existingRevisions: 'slice-cross-author'` (or `--existing-revisions slice-cross-author`)
134
+ to preserve the other reviewer's attribution while applying Word-native
135
+ insertions and deletions inside their pending insertion. Pass
136
+ `existingRevisions: 'accept-all-first'` (or `--existing-revisions accept-all-first`
137
+ via CLI) to normalize all prior revisions first, or `'reject-input'` to refuse any
138
+ paragraph with open revisions. Use `'accept-all-first-keep-normalized'` only when
139
+ accepted revisions should be returned as a real change even on a no-op edit.
140
+ Same-author merging also fails with `COMMENTED_CONTENT_MERGE` when the revised
141
+ paragraph contains comment anchors, because reverting the prior revision could
142
+ remove or orphan those comments. Resolve the comments before re-editing.
143
+
144
+ ### Apply a text edit without tracked changes (Direct Edits)
145
+
146
+ > [!IMPORTANT]
147
+ > **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).
148
+
149
+ ```js
150
+ const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
151
+ generateRedlines: false
152
+ });
153
+ ```
154
+
155
+ ### Convert OOXML to readable text or markdown
156
+
157
+ ```js
158
+ import { ingestWordOoxmlToPlainText, ingestWordOoxmlToMarkdown } from '@ansonlai/docx-redline-js';
159
+ const plainText = ingestWordOoxmlToPlainText(documentXml);
160
+ const markdown = ingestWordOoxmlToMarkdown(documentXml);
161
+ ```
162
+
163
+ ### Add a comment to OOXML
164
+
165
+ ```js
166
+ import { injectCommentsIntoOoxml } from '@ansonlai/docx-redline-js';
167
+ const result = injectCommentsIntoOoxml(paragraphOoxml, [
168
+ {
169
+ paragraphIndex: 1,
170
+ textToFind: 'force majeure',
171
+ commentContent: 'Review this clause'
172
+ }
173
+ ], { author: 'Agent' });
174
+ ```
175
+
176
+ `paragraphIndex` is 1-based within the supplied OOXML payload. The comment
177
+ author belongs in the options object and applies to the injected comments.
178
+
179
+ ### Accept tracked changes from one user (or all users)
180
+
181
+ ```js
182
+ import { acceptTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
183
+ const acceptedMine = acceptTrackedChangesInOoxml(documentXml, { author: 'Agent' });
184
+ const acceptedAll = acceptTrackedChangesInOoxml(documentXml, { allAuthors: true });
185
+ ```
186
+
187
+ ### Reject tracked changes from one user (or all users)
188
+
189
+ ```js
190
+ import { rejectTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
191
+ const rejectedMine = rejectTrackedChangesInOoxml(documentXml, { author: 'Agent' });
192
+ const rejectedAll = rejectTrackedChangesInOoxml(documentXml, { allAuthors: true });
193
+ ```
194
+
195
+ Move revisions are consumed too: accept removes `w:moveFrom` and unwraps
196
+ `w:moveTo`; reject unwraps `w:moveFrom` and removes `w:moveTo`.
197
+
198
+ ### Delete comments from one user (or all users)
199
+
200
+ ```js
201
+ import { deleteCommentsByAuthorInOoxml } from '@ansonlai/docx-redline-js';
202
+ const removedMine = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { author: 'Agent' });
203
+ const removedAll = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { allAuthors: true });
204
+ ```
205
+
206
+ ### Apply multiple operations to full document XML
207
+
208
+ ```js
209
+ import {
210
+ applyOperationToDocumentXml,
211
+ applyOperationsToDocumentXml
212
+ } from '@ansonlai/docx-redline-js/standalone-runner';
213
+
214
+ const result = await applyOperationsToDocumentXml(documentXml, operations, 'Agent', runtimeContext, options);
215
+ ```
216
+
217
+ The operation runner uses these field names:
218
+
219
+ ```js
220
+ const operations = [
221
+ { type: 'redline', target: 'Old paragraph text', modified: 'New paragraph text', targetRef: 12 },
222
+ { type: 'comment', target: 'Paragraph text', textToComment: 'anchor text', commentContent: 'Comment body', targetRef: 18 },
223
+ { type: 'highlight', target: 'Paragraph text', textToHighlight: 'anchor text', color: 'yellow', targetRef: 24 }
224
+ ];
225
+ ```
226
+
227
+ To counterpropose text for a paragraph wholly deleted by another reviewer,
228
+ use explicit restoration intent. A normal `redline` remains fail-closed with
229
+ `FOREIGN_PARAGRAPH_MARK_DELETION`:
230
+
231
+ ```js
232
+ const restoration = {
233
+ type: 'restore',
234
+ target: { paragraphId: '1A2B3C4D', exactText: 'Original deleted paragraph text.' },
235
+ modified: 'Restored or adjusted paragraph text.',
236
+ author: 'Editor'
237
+ };
238
+ ```
239
+
240
+ `restore` targets default to `revisionView: 'rejected'`, because that is where
241
+ the deleted source text is visible. Inspection/extraction fingerprints are
242
+ view-scoped and returned with a matching `revisionView`; copy the fingerprint,
243
+ exact text, and view together. Set `revisionView: 'accepted'` explicitly only
244
+ when intentionally using an accepted-view descriptor.
245
+
246
+ For a contiguous range, provide `targetEnd`/`targetEndRef` and one string per
247
+ source paragraph in `modified`. Restoration always uses tracked changes,
248
+ preserves the deleted source paragraph, and inserts the counterproposal after
249
+ the complete deleted source block with a fresh paragraph ID. Unchanged
250
+ pre-existing validation defects remain baseline diagnostics; a restore fails
251
+ with `GENERATED_OOXML_INVALID` only when it introduces a new validation error.
252
+
253
+ To insert run-level text inside content visible only in the rejected view, use
254
+ an explicit rejected-view `insert` operation:
255
+
256
+ ```js
257
+ const insertion = {
258
+ type: 'insert',
259
+ target: { paragraphId: '1A2B3C4D', revisionView: 'rejected' },
260
+ anchor: { exactText: 'must pay', occurrence: 1, offset: 5 },
261
+ modified: '[clarification] ',
262
+ author: 'Editor',
263
+ existingRevisions: 'slice-cross-author'
264
+ };
265
+ ```
266
+
267
+ The anchor offset is relative to `anchor.exactText`. Repeated anchors require an
268
+ explicit `occurrence`. The engine preserves the foreign deletion as sibling
269
+ `w:del` carriers around a top-level `w:ins`; unsupported comments, bookmarks,
270
+ fields, hyperlinks, moves, or non-text split boundaries fail closed.
271
+
272
+ `targetRef` is an optional 1-based paragraph reference used to disambiguate
273
+ duplicate text. An operation-level `author` overrides the batch author; batch
274
+ results report both `authorUsed` per item and the aggregate `authorsUsed` list.
275
+
276
+ For safer targeting, `target` may be a descriptor:
277
+
278
+ ```js
279
+ {
280
+ type: 'replace',
281
+ target: {
282
+ exactText: 'Repeated paragraph text',
283
+ paragraphId: '1A2B3C4D', // when present in the source OOXML
284
+ index: 12,
285
+ occurrence: 2,
286
+ inTable: false,
287
+ fingerprint: 'fnv1a32:...'
288
+ },
289
+ modified: 'Replacement text',
290
+ author: 'Editor'
291
+ }
292
+ ```
293
+
294
+ Call `preflightOperations(documentXml, operations, author)` when you want a
295
+ read-only inspection of an agent-generated batch before applying it. Preflight is
296
+ read-only and strict by default: duplicate exact text returns `AMBIGUOUS_TARGET`,
297
+ approximate text is not selected, and the result reports candidate targets,
298
+ missing anchors, existing revisions, authors, required artifacts, and
299
+ same-paragraph conflicts. Direct execution is progressive by default
300
+ (`atomic: false`); pass `{ atomic: true }` when the batch must roll back as a
301
+ unit. When permissive resolution
302
+ encounters duplicate candidate paragraphs, it emits an
303
+ `AMBIGUOUS_TARGET_HEURISTIC_USED` warning; migrate to `{ strictTargets: true }`
304
+ with strict descriptors (`paragraphId`, `index`, `occurrence`, or `fingerprint`)
305
+ before v1.0.0.
306
+
307
+ Whole-paragraph deletions targeting paragraphs with existing comments fail with
308
+ `COMMENTED_CONTENT_DELETE`. Resolve or remove the comments first.
309
+
310
+ Use `result.documentXml` from these APIs when replacing full `word/document.xml`.
311
+ For mixed batches, prefer `applyOperationsToDocumentXml(...)`; it applies comments
312
+ before replacements so earlier edits cannot invalidate their anchors.
313
+
314
+ 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).
315
+
316
+ 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`).
317
+
318
+ Internally, a batch uses one live document DOM and one revision allocator, then
319
+ serializes the full document once. Every operation has a DOM/allocator savepoint;
320
+ do not remove this isolation merely for speed. Redline accuracy, accepted and
321
+ rejected text, and exact rollback take precedence over throughput.
322
+
323
+ Every operation produces a commit-aware `receipt` (and batch-level `receipts`)
324
+ enumerating exact allocated `revisionItems`, `commentIds`, `numberingIds`,
325
+ `relationshipIds`, `affectedTargets`, and `warnings`. The output reconciliation
326
+ oracle (`reconcileReceiptsAgainstOutput`) validates that all reported durable IDs
327
+ are present in the serialized output; any discrepancy triggers rollback and fails closed.
328
+
329
+ Always inspect `status` and `error`, not only `hasChanges`. A failed transform
330
+ can return `{ hasChanges: false, status: 'error', error: ... }`. Missing or
331
+ ambiguous comment anchors are structured errors and roll back atomic batches;
332
+ `no_change` is reserved for genuine no-ops. Continue to inspect warnings for
333
+ non-fatal diagnostics.
334
+
335
+ ### Detect existing tracked changes
336
+
337
+ ```js
338
+ import { containsTrackedChanges } from '@ansonlai/docx-redline-js';
339
+ const hasTrackedChanges = containsTrackedChanges(xmlDoc);
340
+ ```
341
+
342
+ ### Inspect document parts before editing
343
+
344
+ ```js
345
+ import { inspectDocumentParts } from '@ansonlai/docx-redline-js';
346
+ const inspection = inspectDocumentParts({ documentXml, commentsXml, numberingXml });
347
+ ```
348
+
349
+ Reuse `exactText` plus `paragraphId` or `fingerprint` in an operation. Computed
350
+ list labels and excerpts are for display, not replacements for exact targets.
351
+
352
+ ### Safely edit a complete DOCX in Node
353
+
354
+ ```js
355
+ import { openDocx } from '@ansonlai/docx-redline-js/node';
356
+ const document = openDocx(inputBuffer);
357
+ const result = await document.applyOperations(operations, {
358
+ author: 'Agent', atomic: true, validate: true
359
+ });
360
+ if (!result.written) throw new Error(result.error?.message || 'No output written');
361
+ const outputBuffer = result.toBuffer();
362
+ ```
363
+
364
+ This facade defaults to strict targets, allocates package-safe comment IDs,
365
+ merges numbering, updates relationships/content types, and rolls back to the
366
+ original buffer when an atomic transaction fails.
367
+
368
+ ### Designing a thin agent wrapper
369
+
370
+ New wrappers should delegate at a package boundary instead of copying internal
371
+ algorithms:
372
+
373
+ - Shell/file wrappers invoke the `docx-redline` CLI and preserve its JSON stdout
374
+ and exit code.
375
+ - Node byte-oriented wrappers use `openDocx`, `inspect`, `applyOperations`,
376
+ `resolveRevisions`, `deleteComments`, and `toBuffer`.
377
+ - XML-only hosts use the standalone runner and remain responsible for package
378
+ artifacts returned beside `documentXml`.
379
+ - Paragraph/range hosts use root exports and remain responsible for deciding how
380
+ the returned OOXML is inserted into a larger document.
381
+
382
+ A wrapper that vendors the CLI should perform a startup compatibility handshake
383
+ with `docx-redline version`. Pin the minimum CLI `contractVersion` and only the
384
+ capabilities that the wrapper's workflow actually requires. If the runtime is
385
+ too old, fail closed with an upgrade instruction; do not inspect the bundled
386
+ implementation or fall back to direct ZIP/XML mutation.
387
+
388
+ A wrapper may choose product defaults for author, atomic mode, output naming,
389
+ and accepted operation subsets. It must preserve strict targeting, exact text,
390
+ structured errors, per-operation results, receipts, warnings, validation, and
391
+ rollback behavior. It must not infer success from `hasChanges` alone or turn a
392
+ partial progressive result into an unconditional success.
393
+
394
+ Document those choices as wrapper policy and pass them explicitly. In
395
+ particular, do not describe a wrapper's preferred revision or atomicity policy
396
+ as though it were the underlying CLI or facade default.
397
+
398
+ Target handles are scoped to the exact package version that produced them. A
399
+ Node wrapper should return the package-scoped token from
400
+ `document.getRevisionToken()` with every inspection and pass it back as
401
+ `expectedRevision` when applying the planned operations. Do not substitute the
402
+ document-parts token included in `document.inspect()`; that token has a different
403
+ scope and the Node facade rejects it. A shell wrapper should ensure extraction
404
+ and application use the same unchanged path, and re-extract after switching to a
405
+ derived working copy.
406
+
407
+ For agent-facing function tools, prefer separate inspection, application, and
408
+ review-resolution tools. Describe each tool's use case, required inputs, side
409
+ effects, retry safety, success criteria, and common error codes. Convenience
410
+ tools should build operations defined by
411
+ `docs/schemas/document-operations.schema.json` and delegate to the same apply
412
+ path rather than implementing custom mutations.
413
+
414
+ For a `restore_deleted_paragraph` convenience tool, force inspection to
415
+ `revisionView: 'rejected'`, copy the exact target descriptor from that view, and
416
+ emit a canonical `restore` operation with the same explicit revision view.
417
+ Wholly foreign-deleted paragraphs appear empty in accepted/current inspection;
418
+ this is expected, not evidence that the paragraph is untargetable. Never reuse a
419
+ restore descriptor from a different source or earlier working-copy version. On
420
+ `TARGET_TEXT_MISMATCH`, re-extract from the exact package being applied rather
421
+ than inspecting implementation bundles or retrying the same operation.
422
+
423
+ ### Agent Document Workflow (CLI)
424
+
425
+ Use the `docx-redline` CLI for complete `.docx` files. It emits JSON on stdout,
426
+ keeps exact text intact, and never overwrites the source unless `--in-place` is
427
+ explicitly supplied.
428
+
429
+ #### Operation Model: Choose by Output Shape
430
+
431
+ Every text-bearing operation targets existing content. Its `modified` field is
432
+ the complete desired accepted-view content for that target, not merely the new
433
+ fragment to insert. Do not choose a type from its English name alone.
434
+
435
+ | Requested result | Operation shape |
436
+ |---|---|
437
+ | Change text within one paragraph or replace its content | `{ type: 'redline', target, modified }` (`replace` is a compatibility alias) |
438
+ | Delete a whole paragraph | `{ type: 'delete', target }` (normalized to a redline with `modified: ''`) |
439
+ | Change native list structure | `{ type: 'list-change', target, modified: '<complete Markdown list>' }` |
440
+ | Reconcile a Word table | `{ type: 'table-reconciliation', target, modified: '<complete Markdown table>' }` |
441
+ | Comment or highlight existing text | `comment` with `commentContent`, or `highlight` with `textToHighlight` |
442
+ | Change character or paragraph formatting | `character-format`/`format` with `textToFormat` and `properties`, or `paragraph-format` with `properties` |
443
+ | Counterpropose a paragraph wholly deleted by another author | `restore` with a rejected-view target |
444
+ | Insert text inside another author's rejected-view content | `insert` with `target.revisionView: 'rejected'`, an exact `anchor`, and `existingRevisions: 'slice-cross-author'` |
445
+
446
+ `insert`, `list-change`, `table-reconciliation`, `replace`, and text-bearing
447
+ `format` are accepted compatibility types, but they normally normalize to the
448
+ same redline operation path. In particular, ordinary `{ type: 'insert' }` does
449
+ not mean “create a new sibling paragraph”; without a rejected-view target and
450
+ anchor, `modified` is still interpreted as the complete replacement text.
451
+
452
+ To append a native sibling item after an existing list item, prefer an explicit
453
+ `list-change` whose `modified` value contains the complete affected list block:
454
+
455
+ ```json
456
+ {
457
+ "type": "list-change",
458
+ "target": {
459
+ "exactText": "Review the report.",
460
+ "paragraphId": "1A2B3C4D"
461
+ },
462
+ "modified": "1. Review the report.\n2. Record the approval decision."
463
+ }
464
+ ```
465
+
466
+ The Markdown markers describe structure; visible labels are generated from the
467
+ document's numbering. Do not put `m)` or another computed label into target text.
468
+ For a single adjacent item in an existing list, the runner also accepts a
469
+ one-line `redline` where `modified` is the exact current item followed by the
470
+ new item's unnumbered text. Preserve the current item verbatim, omit the new
471
+ label, and make the new item at least six words so the adjacency form is
472
+ unambiguous. Use the explicit Markdown list form when adding multiple items,
473
+ nesting, or changing levels.
474
+
475
+ The canonical machine-readable contract is
476
+ `docs/schemas/document-operations.schema.json`. Read that schema or the examples
477
+ here instead of grepping bundled implementation code.
478
+
479
+ #### Standard Workflow (Fast & Direct)
480
+
481
+ 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:
482
+
483
+ The short route for a document-editing request is:
484
+
485
+ 1. Run one focused `extract` for the clauses or range being edited. Search is a
486
+ case-insensitive substring match; add `--around 3` when surrounding drafting
487
+ context is needed. Copy `exactText` plus `paragraphId` or `fingerprint`.
488
+ 2. Build the final operations from the operation table above. Use one operation
489
+ per target paragraph, and consolidate multiple changes to that paragraph.
490
+ 3. Run `apply` once per stable batch. Strong inspected targets are bound against
491
+ the batch-start document, so independent operations do not need manual
492
+ bottom-up sorting around structural edits. Consolidate multiple complete
493
+ desired states for the same source. A unique exact reference to paragraph
494
+ text created elsewhere in the batch is scheduled automatically; use explicit
495
+ captures/selectors for non-unique or advanced created-content dependencies.
496
+ 4. Walk every result and require `completion: true`, `written: true`, and no
497
+ per-operation error.
498
+ 5. Run a focused `extract` on changed clauses only when placement or list/table
499
+ structure needs confirmation.
500
+
501
+ Do not probe operation behavior with disposable apply commands or read a vendor
502
+ bundle before this route. If `apply` returns an error, use the recovery matrix
503
+ below and make one cause-specific correction.
504
+
505
+ ```bash
506
+ # 1. Focused contextual discovery
507
+ docx-redline extract contract.docx --search "termination" --around 3
508
+
509
+ # 2. Inline one-liner edit (fastest for 1–2 edits; no JSON file needed)
510
+ docx-redline apply contract.docx --target "Original clause" --modified "New clause" --output reviewed.docx
511
+
512
+ # 3. Direct edit without tracked changes (clean text, no revision clutter)
513
+ docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
514
+
515
+ # 4. Cross-author edit inside another reviewer's pending insertion
516
+ docx-redline apply contract.docx --target "Pending clause text" --modified "Updated clause text" --existing-revisions slice-cross-author --output reviewed.docx
517
+
518
+ # 5. Batch operations with ops.json
519
+ docx-redline apply contract.docx --operations operations.json --output reviewed.docx
520
+
521
+ # 6. Serializer-backed stdin with compact stdout
522
+ node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
523
+ ```
524
+
525
+ Key CLI defaults and behaviors:
526
+ - **Author**: Automatically defaults to `'AI Redliner'` (overridable via `--author` or `DOCX_REDLINE_AUTHOR` environment variable).
527
+ - **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.
528
+ - **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.
529
+ - **Tracked changes**: Defaults to `generateRedlines: true`. When clean direct text is needed, pass `--no-redlines`.
530
+ - **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`.
531
+ - **Complete-success exit (optional)**: Pass `--require-complete` when `partial` must exit nonzero (`3`). Errors exit `2`; the legacy zero exit for partial results remains when the flag is omitted.
532
+ - **Agent profile**: `--profile agent` enables complete-success exit behavior but preserves progressive execution and the ordinary revision policy. Compose it with `--atomic` or an explicit `--existing-revisions` choice when intended; resolved values appear in `effectiveOptions`.
533
+ - **Operation transport**: A UTF-8 operations file and serializer-backed `--operations -` are peers. Use whichever the host can construct without interpolating legal text in the shell.
534
+ - **Compact mutation JSON**: `apply`, `accept`, `reject`, and `delete-comments` omit document/package XML, full validation arrays, and duplicate nested receipts. Top-level `receipts` is authoritative. Pass `--compact` for one-line JSON; run `validate` for full issue records.
535
+ - Check `completion: true`, `written: true`, and a non-null `outputPath` on stdout. `completion` is derived from the write result, top-level status, and every operation status, so failed, partial, and unwritten work cannot appear complete. If an error occurs, inspect `error.code` or `results[i].error.code` (e.g. `TARGET_NOT_FOUND`, `ANCHOR_NOT_FOUND`) before correcting the cause and re-applying.
536
+
537
+ 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.
538
+
539
+ #### High-Assurance / Staged Verification Workflow (Optional)
540
+
541
+ This is an opt-in, higher-latency path for cases like large automated batch
542
+ migrations or workflows where the user specifically requests a non-mutating dry run
543
+ and an independent baseline audit report. **Never switch into it on your own initiative**
544
+ (not even for "high-stakes" contracts); unless the user explicitly requests it, stick with the
545
+ Standard workflow above. Use the extended verification cycle:
546
+
547
+ ```bash
548
+ docx-redline inspect contract.docx --non-empty
549
+ docx-redline extract contract.docx --range 10:30 > paragraphs.json
550
+ docx-redline preflight contract.docx --operations operations.json --author "Editor"
551
+ docx-redline apply contract.docx --operations operations.json --author "Editor" --output reviewed.docx
552
+ docx-redline validate reviewed.docx --baseline contract.docx
553
+ ```
554
+
555
+ Copy `exactText`, `paragraphId`, and `fingerprint` from `extract` into operation
556
+ targets. For most unique clauses, `"target": "exact paragraph text"` is
557
+ sufficient; use discriminators (`paragraphId`, `fingerprint`, `index`, or
558
+ `occurrence`) when duplicate paragraph text appears in the document. Never
559
+ normalize or reconstruct `exactText`. Operation files follow
560
+ [`docs/schemas/document-operations.schema.json`](schemas/document-operations.schema.json).
561
+
562
+ #### Commands
563
+
564
+ - `inspect` returns the structured inventory, comments, authors, and counts.
565
+ - `extract` returns a compact target inventory with exact text.
566
+ - `preflight` checks targets, anchors, revisions, conflicts, authors, and needed artifacts without mutation (read-only).
567
+ - `apply` applies an operation file transactionally with automatic rollback and internal markup validation.
568
+ - `accept` and `reject` resolve revisions selected by `--author` or `--all-authors`.
569
+ - `delete-comments` removes matching definitions and document anchors together.
570
+ - A whole-paragraph delete stops with `COMMENTED_CONTENT_DELETE` when the
571
+ paragraph has an existing comment. Surface the returned reviewer and comment
572
+ text for human follow-up; do not silently convert this into comment removal.
573
+ - `validate` audits revision markup and DOCX package wiring, optionally comparing against a `--baseline`.
574
+
575
+ Paragraph indexes are 1-based. Inspection filters are `--index 12`,
576
+ `--range 10:30`, `--indexes 2,5,8`, `--search text`, `--revised`, `--table`,
577
+ `--body`, `--non-empty`, and `--view accepted|rejected|current`. Search is
578
+ case-insensitive. Add `--around N` (`--context N` or `-C N`) to a search;
579
+ context records are labeled separately and do not consume the direct-hit
580
+ `--limit`. Continue a bounded result with `--after <paragraph-index>`. Ordinary
581
+ unscoped CLI inspection defaults to 20 direct records and a 48 KiB soft budget;
582
+ `--all` deliberately opts out. A malformed filter or unknown option is an error
583
+ rather than an unfiltered fallback.
584
+
585
+ Mutating commands use `--author`, operation-level authors, then
586
+ `DOCX_REDLINE_AUTHOR`, falling back visibly to `AI Redliner`; review-resolution
587
+ commands may use `--all-authors` where applicable. Without `--output`, a sibling
588
+ such as `contract.redlined.docx` is chosen. Existing outputs are refused unless
589
+ `--force` is present. `--in-place` is the only way to overwrite the input.
590
+
591
+ Treat a nonzero exit code or JSON `status: "error"` as failure. A failed atomic
592
+ operation reports `written: false` and does not write an output file.
593
+ Missing or repeated comment anchors are errors rather than no-ops. Explicit
594
+ anchors match exact text first and then a unique ordinary-space/NBSP equivalent;
595
+ omit `textToComment` to comment the entire resolved paragraph.
596
+
597
+ To reply inside an existing Word comment thread, use the comment ID returned by
598
+ `inspect` and do not supply a paragraph target:
599
+
600
+ ```json
601
+ { "type": "comment_reply", "parentCommentId": "8", "commentContent": "Agreed; updated.", "author": "Editor" }
602
+ ```
603
+
604
+ Replies are represented in `word/commentsExtended.xml` and deliberately add no
605
+ new `commentRangeStart`, `commentRangeEnd`, or `commentReference` to the body.
606
+
607
+ #### Legacy skill wrapper migration
608
+
609
+ Older skills that invoke `scripts/extract_text.mjs` and
610
+ `scripts/apply_changes.mjs` should use the compatibility entrypoints published
611
+ with this package rather than carrying copied targeting or ZIP logic. The
612
+ legacy positional apply form remains supported:
613
+
614
+ ```bash
615
+ node scripts/apply_changes.mjs input.docx changes.json output.docx --author "Editor"
616
+ ```
617
+
618
+ Operation files may contain an array, an `operations` array, or a legacy
619
+ `changes` array. The wrapper delegates to the same strict, atomic, validated
620
+ CLI described above. If `--author` and operation authors are absent, its
621
+ compatibility fallback is `DOCX_REDLINE_AUTHOR` and then `Agent`. Consumers
622
+ must use the JSON status and process exit code; failed atomic work has
623
+ `written: false`, `outputPath: null`, and does not modify the output path.
624
+
625
+ #### Safe Operations File Creation (JSON vs. Shell Heredocs)
626
+
627
+ When composing batch operation JSON, whether for a file or stdin:
628
+
629
+ - **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`).
630
+ - **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.
631
+
632
+ #### Walking Progressive Batch Results (Status & Partial Execution)
633
+
634
+ In default progressive mode (`atomic: false`), operations execute independently: valid operations commit to the document while failing operations report errors without aborting the batch:
635
+
636
+ - **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.
637
+ - **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.
638
+ - **`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.
639
+ - **Follow `retryPlan`**: `base: "original"` means use the unchanged source and replay the corrected batch. `base: "output"` means retain committed progressive edits and submit only failed/unattempted indexes. `sameArgumentsSafe` is always false for failures.
640
+ - **Follow the recovery envelope**: Use `error.recovery.action`, `requiresReinspection`, and `requiresUserAuthorization` rather than deriving a retry from the prose message. The envelope is versioned by `recoveryVersion`.
641
+
642
+ #### Human-Readable References vs. Internal Machine Handles
643
+
644
+ 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:
645
+
646
+ - **Never surface `P11`, `P42`, or bare paragraph numbers** in user-facing prose, comments, redline summaries, or negotiation notes.
647
+ - Instead, cite locations using the human-readable fields provided by `inspect` / `extract`:
648
+ - **`provision`**: Lead with section/clause numbers when present (e.g., `§14.1 Entire Agreement`).
649
+ - **`nearestHeading` + ordinal offset**: When `provision` is absent, describe position relative to the nearest heading (e.g., `under "Limitation of Liability", 2nd paragraph`).
650
+ - **Structural context**: For unnumbered clauses prior to the first heading, use plain language (e.g., `opening recital, before Section 1`).
651
+ - **`humanReference`**: Use the pre-joined citation string provided directly on inspected paragraph objects.
652
+
653
+ #### Actionable Error Recovery Matrix
654
+
655
+ When the CLI or runner returns an error code, follow these specific recovery actions:
656
+
657
+ | Error Code | Meaning | Actionable Recovery |
658
+ |---|---|---|
659
+ | `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`). |
660
+ | `AMBIGUOUS_TARGET` | Multiple paragraphs match identical text. | Disambiguate by supplying `paragraphId`, `fingerprint`, `occurrence`, or `index` in the target descriptor. |
661
+ | `ANCHOR_NOT_FOUND` / `AMBIGUOUS_ANCHOR` | A comment or rejected-view insertion anchor was not uniquely matched. | For comments, narrow `textToComment` or omit it to anchor the whole paragraph. For rejected-view insertion, copy exact rejected text and provide `anchor.occurrence`. |
662
+ | `OVERLAPPING_SOURCE_TARGETS` / `OVERLAPPING_TEXT_EDITS` | Multiple complete text operations target the same batch-start source. | Consolidate all changes to that paragraph into one `redline` or `replace`; the library cannot choose between incompatible complete desired states. |
663
+ | `REVISION_ORDER_CONFLICT` | A text rewrite and formatting/highlight operation overlap the same source. | Consolidate or split the work at an intentional created-content dependency; do not try a different arbitrary order. |
664
+ | `CAPTURE_FANOUT_CONFLICT` | Multiple mutating consumers share one capture without distinct selectors. | Give consumers distinct selectors, chain them explicitly, or split the batch. |
665
+ | `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. |
666
+ | `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. |
667
+ | `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. |
668
+ | `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. |
669
+ | `REJECTED_INSERTION_STATE_REQUIRED` / `UNSAFE_REVISION_BOUNDARY` | An explicit rejected-view insertion did not resolve to supported plain run text inside a wholly foreign-deleted paragraph. | Do not fall back to a generic edit. Narrow the exact anchor/offset, or handle comments, bookmarks, fields, hyperlinks, moves, or other structural boundaries manually. |
670
+ | `GENERATED_OOXML_INVALID` | The operation introduced a new validation error relative to its baseline. | Treat the operation as unapplied and inspect `generatedIssues`; correct the generating operation or builder rather than repairing or accepting the source document's unrelated baseline defects. |
671
+ | `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. |
672
+ | `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. |
673
+ | `INVALID_OPERATION` | Operation object violates schema or has incompatible fields. | Validate the JSON structure against [`document-operations.schema.json`](schemas/document-operations.schema.json) before targeting is attempted. |
674
+ | `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. |
675
+
676
+ **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.
677
+
678
+ #### Document Scope & Boundary Invariants
679
+
680
+ The `docx-redline` engine and CLI operate specifically on the **main document body**:
681
+
682
+ - **Supported Content**: Body paragraphs, numbered/bulleted lists, tables and table cells, comments, and comment replies.
683
+ - **Unsupported Content**: Headers, footers, footnotes, endnotes, floating text boxes, shape drawings, watermarks, and embedded macros.
684
+ - 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.
685
+
686
+ ### Convert paragraph text into a Word list
687
+
688
+ ```js
689
+ const result = await applyRedlineToOxml(oxml, 'Item text', '1. Item text', {
690
+ generateRedlines: true
691
+ });
692
+ ```
693
+
694
+ ### Insert a large mixed-content block safely
695
+
696
+ Do not send a long attachment containing literal pipe rows, headings, lists,
697
+ and paragraphs as an unchecked replacement. Plan it first:
698
+
699
+ ```js
700
+ import { planStructuredReplacement } from '@ansonlai/docx-redline-js';
701
+
702
+ const plan = planStructuredReplacement(targetDescriptor, markdown, {
703
+ author: 'Agent'
704
+ });
705
+ if (!plan.valid || !plan.operation) {
706
+ throw new Error(plan.issues.map(issue => issue.message).join(' '));
707
+ }
708
+ const result = await document.applyOperations([plan.operation], {
709
+ author: 'Agent', atomic: true, validate: true
710
+ });
711
+ ```
712
+
713
+ Use blank lines between paragraphs, `#`/`##` for headings, normal Markdown
714
+ markers for lists, and a separator row immediately after every table header:
715
+
716
+ ```markdown
717
+ | Agency | Contact |
718
+ | --- | --- |
719
+ | BCHD | Dr. Jenkins |
720
+ ```
721
+
722
+ The planner returns typed `blocks`, counts, normalized Markdown, and structured
723
+ issues. `TABLE_SEPARATOR_REQUIRED` is an error: never remove `structuredContent`
724
+ or retry the same content as plain text merely to make the operation pass. Keep
725
+ the result as one atomic replacement operation so the first inserted block does
726
+ not invalidate the anchor for later blocks. After applying, require real
727
+ `w:tbl`, positive list `w:numId` values, valid redline OOXML, and independent
728
+ Accept/Reject checks.
729
+
730
+ ### Reconcile a table
731
+
732
+ ```js
733
+ import { reconcileMarkdownTableOoxml } from '@ansonlai/docx-redline-js';
734
+ const result = await reconcileMarkdownTableOoxml(tableOoxml, originalText, markdownTable);
735
+ ```
736
+
737
+ ## Detailed Architecture
738
+
739
+ Use the layout and task shortcuts at the top of this file for normal work.
740
+ `ARCHITECTURE.md` is the maintained detailed module-ownership map and describes
741
+ the end-to-end dependency flow. Do not infer ownership from directory names or
742
+ reconstruct the architecture by repeatedly listing the repository.
743
+
744
+ ## Common Patterns
745
+
746
+ ### Options and Defaults Reference
747
+
748
+ | Option | Type | Default | Description |
749
+ |--------|------|---------|-------------|
750
+ | `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. |
751
+ | `author` | `string` | `'AI Redliner'` | Reviewer/author name stamped on generated tracked changes and comments. Overridable via `DOCX_REDLINE_AUTHOR` environment variable. |
752
+ | `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. |
753
+ | `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. |
754
+ | `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. |
755
+ | `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. |
756
+ | `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. |
757
+ | `removeFormatting` | `boolean` | `false` | When `true` and the text is unchanged with no Markdown hints, strips existing bold/italic/underline/strikethrough formatting. |
758
+ | `sanitizeInput` | `boolean` | `false` | Opt-in removal of standalone leading assistant-preface lines. Literal dollar signs and `\n` sequences are always preserved. |
759
+
760
+ ### Options shape
761
+
762
+ ```js
763
+ {
764
+ generateRedlines: true,
765
+ author: 'AI Redliner',
766
+ atomic: false,
767
+ structuredContent: true,
768
+ pairReplacements: true,
769
+ existingRevisions: 'merge-same-author',
770
+ removeFormatting: false,
771
+ sanitizeInput: false
772
+ }
773
+ ```
774
+
775
+ ### Typical return shape
776
+
777
+ ```js
778
+ {
779
+ oxml: string,
780
+ hasChanges: boolean,
781
+ status?: 'ok' | 'no-op' | 'error',
782
+ error?: { code: string, message: string },
783
+ warnings?: string[],
784
+ numberingXml?: string,
785
+ useNativeApi?: boolean
786
+ }
787
+ ```
788
+
789
+ Known error codes include `PARSE_ERROR`, `TARGET_NOT_FOUND`, `PARTIAL_TARGET`,
790
+ `EXISTING_REVISIONS`, `COMMENTED_CONTENT_MERGE`, `UNSAFE_REVISION_NESTING`, `UNSUPPORTED_REVISION_VIEW_MUTATION`,
791
+ `UNSAFE_PARAGRAPH_BOUNDARY`, `DIFF_TOKEN_LIMIT`, and `BATCH_OPERATION_FAILED`.
792
+
793
+ For ingestion that must distinguish an empty document from malformed OOXML,
794
+ use `ingestWordOoxmlToPlainTextResult` or
795
+ `ingestWordOoxmlToMarkdownResult`. The legacy ingestion helpers intentionally
796
+ retain their string-only return type and return `''` for parse failures.
797
+
798
+ ### Target text versus replacement text
799
+
800
+ Target resolution may normalize surrounding or repeated whitespace while
801
+ matching a paragraph. Replacement text is not normalized: tabs, line breaks,
802
+ non-breaking spaces, repeated spaces, and leading/trailing whitespace become
803
+ part of the requested edit. When editing extracted document text, copy the
804
+ exact paragraph text and modify it in place rather than round-tripping it
805
+ through a formatter that may change whitespace.
806
+
807
+ The normalized caller target is never used for mutation offsets in a text-bearing
808
+ edit. After target selection, the engine uses the resolved paragraph's byte-exact
809
+ JavaScript string as the source coordinate system. The legacy format-only
810
+ fallback for field-code paragraphs with no extractable accepted-view spans is
811
+ not a text-replacement path. If an ASCII-space target selected an NBSP
812
+ source, `resolvedTarget.targetTextMatch` reports `space_equivalent`, escaped
813
+ source/request excerpts, and differing code points. An NBSP-to-space request is
814
+ tracked as a replacement; it must not retain the NBSP and append another space.
815
+ The CLI keeps these bounded diagnostics but removes resolved clause text.
816
+
817
+ For ordinary insertions and deletions, target the visible accepted view:
818
+ inserted `w:t` text is visible and deleted `w:delText` is not. Move revisions
819
+ and other complex structures require additional care until targeting and
820
+ ingestion share one canonical text extractor. Prefer a `targetRef` plus the full
821
+ paragraph text when duplicate paragraphs are possible. Current text-only
822
+ matching can select the first matching paragraph, so callers that cannot
823
+ disambiguate safely should stop instead of guessing.
824
+
825
+ ### OOXML wrapping for Word insertOoxml scenarios
826
+
827
+ ```js
828
+ import { wrapInDocumentFragment } from '@ansonlai/docx-redline-js';
829
+ const wrapped = wrapInDocumentFragment(rawOoxml, { includeNumbering: true, numberingXml });
830
+ ```
831
+
832
+ ### Output shape guardrail (important for packaging)
833
+
834
+ When consuming `result.oxml`, do not assume the payload is always safe to write
835
+ directly into `word/document.xml`.
836
+
837
+ - Paragraph/range/table APIs can return a fragment, `<w:document>`, or package payload (`<pkg:package>`).
838
+ - `applyOperationToDocumentXml(...).documentXml` is the document-safe path when you need a full `word/document.xml` replacement.
839
+ - Use `extractReplacementNodesFromOoxml(payload)` to normalize unknown payloads.
840
+ - If `sourceType === 'package'` or the payload starts with `<pkg:package`, do not write it into `word/document.xml` as-is.
841
+
842
+ ## Gotchas
843
+
844
+ 1. Call `configureXmlProvider` first in Node.js.
845
+ 2. `applyRedlineToOxml` is async.
846
+ 3. Paragraph APIs expect paragraph-level OOXML, not full `word/document.xml` in all cases.
847
+ 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.
848
+ That replacement behavior is deprecated and will become an error in the next major version.
849
+ 5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
850
+ 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.
851
+ 7. If output begins with `<pkg:package`, treat it as package-level OOXML and normalize it before writing anything back to `word/document.xml`.
852
+ 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.
853
+ 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.
854
+ 10. Hyperlinks, bookmarks, comment markers, tabs/breaks, and footnote/endnote references are structural OOXML and should survive adjacent redline edits.
855
+ 11. Internally, create Word elements through `createWordElement` and tracked-change metadata through `createRevisionMetadata`.
856
+ 12. Revision IDs are document-scoped in public operation paths. Thread the
857
+ internal allocator through new string-serialization paths; generated
858
+ `w:id` values are not stable across documents.
859
+ 13. Splitting or cloning a run can duplicate nested `w:rPrChange` metadata.
860
+ Preserve the original ID on at most one resulting run and allocate fresh
861
+ IDs for every additional clone through the document-scoped allocator.
862
+ 14. Run `validateRedlineOoxml` on generated markup before packaging it, then
863
+ run `validateDocxPackage` after merging comments and numbering artifacts.
864
+
865
+ ## Validation Commands
866
+
867
+ ```bash
868
+ npm test
869
+ npm run test:isolation
870
+ npm run check:types
871
+ node scripts/export-validation-fixtures.mjs
872
+ ```
873
+
874
+ Optional Windows/Word smoke test for a completed `.docx`:
875
+
876
+ ```bash
877
+ npm run smoke:word -- path/to/file.docx
878
+ ```