@ansonlai/docx-redline-js 0.7.2 → 0.8.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 (53) hide show
  1. package/AGENTS.md +15 -10
  2. package/ARCHITECTURE.md +30 -10
  3. package/CHANGELOG.md +539 -431
  4. package/README.md +331 -309
  5. package/adapters/xml-adapter.js +84 -56
  6. package/core/sha256.js +120 -0
  7. package/dist/docx-redline-js.esm.js +8982 -693
  8. package/dist/docx-redline-js.esm.js.map +4 -4
  9. package/dist/docx-redline-js.esm.min.js +102 -89
  10. package/dist/docx-redline-js.esm.min.js.map +4 -4
  11. package/dist/docx-redline.bundle.cjs +28305 -0
  12. package/dist/docx-redline.bundle.cjs.map +7 -0
  13. package/dist/docx-redline.bundle.js +28296 -0
  14. package/dist/docx-redline.bundle.js.map +7 -0
  15. package/docs/AGENT_FAST_START.md +2 -1
  16. package/docs/AGENT_KNOWLEDGE_BASE.md +480 -393
  17. package/docs/SKILL_AUTHORING.md +5 -2
  18. package/docs/TESTING.md +2 -1
  19. package/docs/schemas/document-operations.schema.json +29 -0
  20. package/document/docx-document.d.ts +39 -0
  21. package/document/docx-document.js +1010 -0
  22. package/document/zip-archive.js +258 -0
  23. package/engine/reconstruction-writer.js +18 -0
  24. package/index.d.ts +598 -531
  25. package/index.js +349 -340
  26. package/node/cli-help.js +5 -5
  27. package/node/cli.js +760 -754
  28. package/node/docx-document.js +11 -355
  29. package/node/index.d.ts +1 -1
  30. package/node/zip-archive.js +28 -38
  31. package/package.json +14 -11
  32. package/scripts/build.mjs +40 -5
  33. package/scripts/generate-word-comment-edge-case-fixture.ps1 +58 -0
  34. package/scripts/generate-word-comment-thread-fixture.ps1 +36 -0
  35. package/scripts/generate-word-header-footer-fixture.ps1 +32 -0
  36. package/scripts/generate-word-multi-section-fixture.ps1 +27 -0
  37. package/scripts/generate-word-package-integrity-cases.mjs +141 -0
  38. package/scripts/generate-word-thread-state-fixtures.ps1 +50 -0
  39. package/scripts/word-com-package-integrity.ps1 +98 -0
  40. package/services/batch-operation-orchestrator.js +262 -259
  41. package/services/comment-replies.js +96 -95
  42. package/services/comment-thread-parts.js +280 -0
  43. package/services/document-inspection.js +366 -365
  44. package/services/document-operation-applier.js +230 -202
  45. package/services/document-operation-contract.js +17 -2
  46. package/services/error-recovery.js +6 -0
  47. package/services/headers-footers.js +206 -0
  48. package/services/operation-batch-compiler.js +1 -0
  49. package/services/operation-preflight.js +231 -218
  50. package/services/package-parts.js +80 -0
  51. package/services/revision-token.js +11 -7
  52. package/services/standalone-docx-plumbing.js +631 -569
  53. package/services/standalone-operation-runner.d.ts +19 -3
@@ -1,78 +1,81 @@
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.
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
+ | Universal complete `.docx` packages (`Uint8Array`) across Node, browsers, edge, and sandboxes | `index.js` (`@ansonlai/docx-redline-js`) or `document/docx-document.js` | `document/docx-document.js`, `document/zip-archive.js`, `core/sha256.js` |
18
+ | Zero-dependency standalone sandbox bundles (n8n, Cloudflare Workers, edge) | `dist/docx-redline.bundle.js` / `.cjs` (`@ansonlai/docx-redline-js/bundle`) | Standalone inlined bundle (no Node built-ins) |
19
+ | Complete `.docx` buffers and the CLI in Node.js (backward compatibility) | `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 |
20
+
21
+ The source tree is layered as follows:
22
+
23
+ ```text
24
+ index.js root, host-independent public exports & universal facade
25
+ adapters/ XML (auto DOMParser fallback), config, and logging adapters
26
+ core/ shared OOXML primitives, text views, targeting, SHA-256, validation
27
+ document/ universal Uint8Array DOCX facade & fflate ZIP container
28
+ pipeline/ ingestion, diffing, markdown, lists, and serialization stages
29
+ engine/ paragraph/range reconciliation and surgical/reconstruction modes
30
+ orchestration/ route planning and structural list operation conversion
31
+ services/ document operations, comments, receipts, package artifacts
32
+ node/ Node CLI and backward-compatibility facade re-exports
33
+ bin/ CLI launcher; behavior belongs in node/ or services/
34
+ tests/*.mjs directly runnable suites discovered by scripts/run-tests.mjs
35
+ tests/helpers/ shared test utilities; not standalone suites
36
+ tests/fixtures/ checked-in synthetic/golden inputs and expected outputs
37
+ scripts/ build, fixture generation, benchmarks, and Word automation
38
+ docs/ schemas, testing guidance, plans, and generated reports
39
+ dist/ generated universal ESM and zero-dependency sandbox bundles
40
+ ```
41
+
42
+ `AGENTS.md` is the committed repository-wide agent guide. `.agent/` is ignored
43
+ local-only agent configuration and must not be treated as package source or as
44
+ instructions that will exist in another clone or in CI.
45
+
46
+ ### Route Changes Without Exploring Everything
47
+
48
+ 1. Identify the public surface in the table above.
49
+ 2. Read its entry point, then follow only the symbol being changed.
50
+ 3. Start from the closest existing test named for the behavior. Do not scan all
51
+ of `tests/` or unpack every fixture to understand one code path.
52
+ 4. Use `rg` for symbols and filenames. Consult `ARCHITECTURE.md` for ownership
53
+ and `docs/TESTING.md` for test-lane selection before inventing a new route,
54
+ helper, test harness, or fixture generator.
55
+ 5. Keep dependency direction inward: shared implementation modules must not
56
+ import `index.js`, and host-independent code must not import `node/`.
57
+ 6. Do not inspect `dist/`, a vendored CLI bundle, or an installed plugin bundle
58
+ to discover public behavior. Use this file, `README.md`,
59
+ `docs/schemas/document-operations.schema.json`, and the unbundled source.
60
+
61
+ ### Task-to-Module Shortcuts
62
+
63
+ | Task | Start here | Common focused tests |
64
+ |---|---|---|
65
+ | 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` |
66
+ | 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` |
67
+ | 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` |
68
+ | 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` |
69
+ | Tables | `engine/table-mode.js`, `core/table-targeting.js`, `services/table-reconciliation.js` | `tests/table_tests.mjs`, `tests/table_targeting_and_format_flags.mjs` |
70
+ | Comments and replies | `services/comment-engine.js`, `services/comment-replies.js`, `services/comment-package.js` | `tests/comment_tests.mjs`, `tests/comment_reply_tests.mjs` |
71
+ | 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` |
72
+ | 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` |
73
+ | 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` |
74
+
75
+ Run one focused suite with `node tests/<name>.mjs`. Run `npm test` only when the
76
+ change crosses several subsystems or before release-level handoff. The Word COM,
77
+ visual, corpus, coverage, and fixture-export commands are separate lanes; use
78
+ them only when `docs/TESTING.md` says that lane proves the behavior in question.
76
79
 
77
80
  ## What This Package Does
78
81
 
@@ -97,21 +100,44 @@ back to `word/document.xml`.
97
100
  ## Entry Point
98
101
 
99
102
  ```js
100
- import { applyRedlineToOxml, configureXmlProvider } from '@ansonlai/docx-redline-js';
103
+ import { applyRedlineToOxml, openDocx, configureXmlProvider } from '@ansonlai/docx-redline-js';
101
104
  ```
102
105
 
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
+ `index.js` is the primary host-independent entry point, exposing both paragraph-level
107
+ reconciliation (`applyRedlineToOxml`) and universal complete-document operations
108
+ (`openDocx`, `DocxDocument`, `computePackageRevisionToken`).
109
+
110
+ ## XML Provider Setup (Zero Configuration)
111
+
112
+ Zero configuration is required out of the box. The library automatically:
113
+ 1. Uses `globalThis.DOMParser` and `globalThis.XMLSerializer` when available in standard web runtimes.
114
+ 2. Transparently falls back to pure-JS `@xmldom/xmldom` in Node.js, edge workers, and sandboxes.
106
115
 
107
- ## Required Setup (Node.js only)
116
+ Consumers can optionally override these providers if a custom DOM implementation is desired:
108
117
 
109
118
  ```js
110
- import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
111
- configureXmlProvider({ DOMParser, XMLSerializer });
119
+ import { configureXmlProvider } from '@ansonlai/docx-redline-js';
120
+ configureXmlProvider({ DOMParser: MyDOMParser, XMLSerializer: MyXMLSerializer });
112
121
  ```
113
122
 
114
- Browsers have native DOM APIs, so no provider injection is typically needed.
123
+ ## Universal Complete-DOCX Operations (`openDocx`)
124
+
125
+ Complete `.docx` documents can be opened directly from a `Uint8Array` (or Node `Buffer`) across all environments without Node built-in dependencies:
126
+
127
+ ```js
128
+ import { openDocx } from '@ansonlai/docx-redline-js';
129
+
130
+ const doc = openDocx(docxUint8Array);
131
+ const inspection = doc.inspect();
132
+ const result = await doc.applyOperations([
133
+ { type: 'replace', target: { paragraphId: '00000001' }, modified: 'Updated clause text' }
134
+ ], { author: 'AI Editor' });
135
+
136
+ if (result.written) {
137
+ const outputBytes = doc.toUint8Array(); // canonical Uint8Array
138
+ // In Node.js: const outputBuf = doc.toBuffer();
139
+ }
140
+ ```
115
141
 
116
142
  ## Key APIs by Use Case
117
143
 
@@ -217,57 +243,57 @@ const result = await applyOperationsToDocumentXml(documentXml, operations, 'Agen
217
243
  The operation runner uses these field names:
218
244
 
219
245
  ```js
220
- const operations = [
246
+ const operations = [
221
247
  { type: 'redline', target: 'Old paragraph text', modified: 'New paragraph text', targetRef: 12 },
222
248
  { type: 'comment', target: 'Paragraph text', textToComment: 'anchor text', commentContent: 'Comment body', targetRef: 18 },
223
249
  { 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.
250
+ ];
251
+ ```
252
+
253
+ To counterpropose text for a paragraph wholly deleted by another reviewer,
254
+ use explicit restoration intent. A normal `redline` remains fail-closed with
255
+ `FOREIGN_PARAGRAPH_MARK_DELETION`:
256
+
257
+ ```js
258
+ const restoration = {
259
+ type: 'restore',
260
+ target: { paragraphId: '1A2B3C4D', exactText: 'Original deleted paragraph text.' },
261
+ modified: 'Restored or adjusted paragraph text.',
262
+ author: 'Editor'
263
+ };
264
+ ```
265
+
266
+ `restore` targets default to `revisionView: 'rejected'`, because that is where
267
+ the deleted source text is visible. Inspection/extraction fingerprints are
268
+ view-scoped and returned with a matching `revisionView`; copy the fingerprint,
269
+ exact text, and view together. Set `revisionView: 'accepted'` explicitly only
270
+ when intentionally using an accepted-view descriptor.
271
+
272
+ For a contiguous range, provide `targetEnd`/`targetEndRef` and one string per
273
+ source paragraph in `modified`. Restoration always uses tracked changes,
274
+ preserves the deleted source paragraph, and inserts the counterproposal after
275
+ the complete deleted source block with a fresh paragraph ID. Unchanged
276
+ pre-existing validation defects remain baseline diagnostics; a restore fails
277
+ with `GENERATED_OOXML_INVALID` only when it introduces a new validation error.
278
+
279
+ To insert run-level text inside content visible only in the rejected view, use
280
+ an explicit rejected-view `insert` operation:
281
+
282
+ ```js
283
+ const insertion = {
284
+ type: 'insert',
285
+ target: { paragraphId: '1A2B3C4D', revisionView: 'rejected' },
286
+ anchor: { exactText: 'must pay', occurrence: 1, offset: 5 },
287
+ modified: '[clarification] ',
288
+ author: 'Editor',
289
+ existingRevisions: 'slice-cross-author'
290
+ };
291
+ ```
292
+
293
+ The anchor offset is relative to `anchor.exactText`. Repeated anchors require an
294
+ explicit `occurrence`. The engine preserves the foreign deletion as sibling
295
+ `w:del` carriers around a top-level `w:ins`; unsupported comments, bookmarks,
296
+ fields, hyperlinks, moves, or non-text split boundaries fail closed.
271
297
 
272
298
  `targetRef` is an optional 1-based paragraph reference used to disambiguate
273
299
  duplicate text. An operation-level `author` overrides the batch author; batch
@@ -296,9 +322,9 @@ read-only inspection of an agent-generated batch before applying it. Preflight i
296
322
  read-only and strict by default: duplicate exact text returns `AMBIGUOUS_TARGET`,
297
323
  approximate text is not selected, and the result reports candidate targets,
298
324
  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
325
+ same-paragraph conflicts. Direct execution is progressive by default
326
+ (`atomic: false`); pass `{ atomic: true }` when the batch must roll back as a
327
+ unit. When permissive resolution
302
328
  encounters duplicate candidate paragraphs, it emits an
303
329
  `AMBIGUOUS_TARGET_HEURISTIC_USED` warning; migrate to `{ strictTargets: true }`
304
330
  with strict descriptors (`paragraphId`, `index`, `occurrence`, or `fingerprint`)
@@ -349,7 +375,7 @@ const inspection = inspectDocumentParts({ documentXml, commentsXml, numberingXml
349
375
  Reuse `exactText` plus `paragraphId` or `fingerprint` in an operation. Computed
350
376
  list labels and excerpts are for display, not replacements for exact targets.
351
377
 
352
- ### Safely edit a complete DOCX in Node
378
+ ### Safely edit a complete DOCX in Node
353
379
 
354
380
  ```js
355
381
  import { openDocx } from '@ansonlai/docx-redline-js/node';
@@ -361,197 +387,197 @@ if (!result.written) throw new Error(result.error?.message || 'No output written
361
387
  const outputBuffer = result.toBuffer();
362
388
  ```
363
389
 
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 (Extract Then Apply)
480
-
481
- Use this for ordinary edits. `apply` validates the resulting package and
482
- revision markup internally before writing, so do not add `preflight` or baseline
483
- `validate` merely for reassurance.
484
-
485
- 1. Run one focused `extract`. Add `--around 3` when surrounding context is
486
- needed. Retain `exactText` for drafting and target with `paragraphId` plus
487
- `fingerprint`.
488
- 2. Build one final operation per target. Use complete `modified` text for broad
489
- semantic revisions or localized `replacements` for small literal changes in
490
- an inspected target. Consolidate multiple changes to one paragraph.
491
- 3. Run `apply` once per stable batch. Independent strong targets bind against
492
- the batch-start document and do not need bottom-up sorting. Use captures only
493
- for intentional created-content dependencies.
494
- 4. Require `completion: true` and a non-null `outputPath`. The CLI derives
495
- completion from write status, operation outcomes, commit disposition, and
496
- localized accepted-view verification.
497
- 5. Run a focused post-apply extraction only when placement or structural output
498
- needs confirmation.
499
-
500
- If `apply` returns an error, follow the recovery envelope below and make one
501
- cause-specific correction. Do not probe behavior with disposable mutations or
502
- read a vendor bundle.
503
-
504
- ```bash
505
- # 1. Focused contextual discovery
506
- docx-redline extract contract.docx --search "termination" --around 3
507
-
508
- # 2. Serializer-backed apply against the inspected target
509
- node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
510
-
511
- # Optional clean edit without tracked changes
512
- docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
513
- ```
390
+ This facade defaults to strict targets, allocates package-safe comment IDs,
391
+ merges numbering, updates relationships/content types, and rolls back to the
392
+ original buffer when an atomic transaction fails.
393
+
394
+ ### Designing a thin agent wrapper
395
+
396
+ New wrappers should delegate at a package boundary instead of copying internal
397
+ algorithms:
398
+
399
+ - Shell/file wrappers invoke the `docx-redline` CLI and preserve its JSON stdout
400
+ and exit code.
401
+ - Node byte-oriented wrappers use `openDocx`, `inspect`, `applyOperations`,
402
+ `resolveRevisions`, `deleteComments`, `resolveComment`, and `toBuffer`.
403
+ - XML-only hosts use the standalone runner and remain responsible for package
404
+ artifacts returned beside `documentXml`.
405
+ - Paragraph/range hosts use root exports and remain responsible for deciding how
406
+ the returned OOXML is inserted into a larger document.
407
+
408
+ A wrapper that vendors the CLI should perform a startup compatibility handshake
409
+ with `docx-redline version`. Pin the minimum CLI `contractVersion` and only the
410
+ capabilities that the wrapper's workflow actually requires. If the runtime is
411
+ too old, fail closed with an upgrade instruction; do not inspect the bundled
412
+ implementation or fall back to direct ZIP/XML mutation.
413
+
414
+ A wrapper may choose product defaults for author, atomic mode, output naming,
415
+ and accepted operation subsets. It must preserve strict targeting, exact text,
416
+ structured errors, per-operation results, receipts, warnings, validation, and
417
+ rollback behavior. It must not infer success from `hasChanges` alone or turn a
418
+ partial progressive result into an unconditional success.
419
+
420
+ Document those choices as wrapper policy and pass them explicitly. In
421
+ particular, do not describe a wrapper's preferred revision or atomicity policy
422
+ as though it were the underlying CLI or facade default.
423
+
424
+ Target handles are scoped to the exact package version that produced them. A
425
+ Node wrapper should return the package-scoped token from
426
+ `document.getRevisionToken()` with every inspection and pass it back as
427
+ `expectedRevision` when applying the planned operations. Do not substitute the
428
+ document-parts token included in `document.inspect()`; that token has a different
429
+ scope and the Node facade rejects it. A shell wrapper should ensure extraction
430
+ and application use the same unchanged path, and re-extract after switching to a
431
+ derived working copy.
432
+
433
+ For agent-facing function tools, prefer separate inspection, application, and
434
+ review-resolution tools. Describe each tool's use case, required inputs, side
435
+ effects, retry safety, success criteria, and common error codes. Convenience
436
+ tools should build operations defined by
437
+ `docs/schemas/document-operations.schema.json` and delegate to the same apply
438
+ path rather than implementing custom mutations.
439
+
440
+ For a `restore_deleted_paragraph` convenience tool, force inspection to
441
+ `revisionView: 'rejected'`, copy the exact target descriptor from that view, and
442
+ emit a canonical `restore` operation with the same explicit revision view.
443
+ Wholly foreign-deleted paragraphs appear empty in accepted/current inspection;
444
+ this is expected, not evidence that the paragraph is untargetable. Never reuse a
445
+ restore descriptor from a different source or earlier working-copy version. On
446
+ `TARGET_TEXT_MISMATCH`, re-extract from the exact package being applied rather
447
+ than inspecting implementation bundles or retrying the same operation.
448
+
449
+ ### Agent Document Workflow (CLI)
450
+
451
+ Use the `docx-redline` CLI for complete `.docx` files. It emits JSON on stdout,
452
+ keeps exact text intact, and never overwrites the source unless `--in-place` is
453
+ explicitly supplied.
454
+
455
+ #### Operation Model: Choose by Output Shape
456
+
457
+ Every text-bearing operation targets existing content. Its `modified` field is
458
+ the complete desired accepted-view content for that target, not merely the new
459
+ fragment to insert. Do not choose a type from its English name alone.
460
+
461
+ | Requested result | Operation shape |
462
+ |---|---|
463
+ | Change text within one paragraph or replace its content | `{ type: 'redline', target, modified }` (`replace` is a compatibility alias) |
464
+ | Delete a whole paragraph | `{ type: 'delete', target }` (normalized to a redline with `modified: ''`) |
465
+ | Change native list structure | `{ type: 'list-change', target, modified: '<complete Markdown list>' }` |
466
+ | Reconcile a Word table | `{ type: 'table-reconciliation', target, modified: '<complete Markdown table>' }` |
467
+ | Comment or highlight existing text | `comment` with `commentContent`, or `highlight` with `textToHighlight` |
468
+ | Change character or paragraph formatting | `character-format`/`format` with `textToFormat` and `properties`, or `paragraph-format` with `properties` |
469
+ | Counterpropose a paragraph wholly deleted by another author | `restore` with a rejected-view target |
470
+ | Insert text inside another author's rejected-view content | `insert` with `target.revisionView: 'rejected'`, an exact `anchor`, and `existingRevisions: 'slice-cross-author'` |
471
+
472
+ `insert`, `list-change`, `table-reconciliation`, `replace`, and text-bearing
473
+ `format` are accepted compatibility types, but they normally normalize to the
474
+ same redline operation path. In particular, ordinary `{ type: 'insert' }` does
475
+ not mean “create a new sibling paragraph”; without a rejected-view target and
476
+ anchor, `modified` is still interpreted as the complete replacement text.
477
+
478
+ To append a native sibling item after an existing list item, prefer an explicit
479
+ `list-change` whose `modified` value contains the complete affected list block:
480
+
481
+ ```json
482
+ {
483
+ "type": "list-change",
484
+ "target": {
485
+ "exactText": "Review the report.",
486
+ "paragraphId": "1A2B3C4D"
487
+ },
488
+ "modified": "1. Review the report.\n2. Record the approval decision."
489
+ }
490
+ ```
491
+
492
+ The Markdown markers describe structure; visible labels are generated from the
493
+ document's numbering. Do not put `m)` or another computed label into target text.
494
+ For a single adjacent item in an existing list, the runner also accepts a
495
+ one-line `redline` where `modified` is the exact current item followed by the
496
+ new item's unnumbered text. Preserve the current item verbatim, omit the new
497
+ label, and make the new item at least six words so the adjacency form is
498
+ unambiguous. Use the explicit Markdown list form when adding multiple items,
499
+ nesting, or changing levels.
500
+
501
+ The canonical machine-readable contract is
502
+ `docs/schemas/document-operations.schema.json`. Read that schema or the examples
503
+ here instead of grepping bundled implementation code.
504
+
505
+ #### Standard Workflow (Extract Then Apply)
506
+
507
+ Use this for ordinary edits. `apply` validates the resulting package and
508
+ revision markup internally before writing, so do not add `preflight` or baseline
509
+ `validate` merely for reassurance.
510
+
511
+ 1. Run one focused `extract`. Add `--around 3` when surrounding context is
512
+ needed. Retain `exactText` for drafting and target with `paragraphId` plus
513
+ `fingerprint`.
514
+ 2. Build one final operation per target. Use complete `modified` text for broad
515
+ semantic revisions or localized `replacements` for small literal changes in
516
+ an inspected target. Consolidate multiple changes to one paragraph.
517
+ 3. Run `apply` once per stable batch. Independent strong targets bind against
518
+ the batch-start document and do not need bottom-up sorting. Use captures only
519
+ for intentional created-content dependencies.
520
+ 4. Require `completion: true` and a non-null `outputPath`. The CLI derives
521
+ completion from write status, operation outcomes, commit disposition, and
522
+ localized accepted-view verification.
523
+ 5. Run a focused post-apply extraction only when placement or structural output
524
+ needs confirmation.
525
+
526
+ If `apply` returns an error, follow the recovery envelope below and make one
527
+ cause-specific correction. Do not probe behavior with disposable mutations or
528
+ read a vendor bundle.
529
+
530
+ ```bash
531
+ # 1. Focused contextual discovery
532
+ docx-redline extract contract.docx --search "termination" --around 3
533
+
534
+ # 2. Serializer-backed apply against the inspected target
535
+ node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
536
+
537
+ # Optional clean edit without tracked changes
538
+ docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
539
+ ```
514
540
 
515
541
  Key CLI defaults and behaviors:
516
542
  - **Author**: Automatically defaults to `'AI Redliner'` (overridable via `--author` or `DOCX_REDLINE_AUTHOR` environment variable).
517
543
  - **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.
518
544
  - **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.
519
545
  - **Tracked changes**: Defaults to `generateRedlines: true`. When clean direct text is needed, pass `--no-redlines`.
520
- - **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`.
521
- - **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.
522
- - **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`.
523
- - **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.
524
- - **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.
525
- - Check `completion: true` and a non-null `outputPath` on stdout. Failed,
526
- partial, rolled-back, unverified, and unwritten work cannot appear complete.
527
- If an error occurs, follow `error.recovery.action` before correcting the cause.
528
-
529
- For multi-clause or multi-page reviews, use focused batches rather than one
530
- massive batch. This keeps context compact and simplifies error diagnosis.
531
-
532
- #### Optional Speculative Localized Apply
533
-
534
- Targetless localized apply remains available to expert users and harnesses with
535
- a measured confidence policy. It is not the ordinary agent workflow:
536
-
537
- ```bash
538
- # Globally unique literal
539
- docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
540
-
541
- # Optional directional scope using a longer, fairly unique phrase
542
- docx-redline apply contract.docx --search "distinctive nearby heading or phrase" --context-range 1:3 --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
543
- ```
544
-
545
- Omitting a target asks the CLI to resolve one eligible paragraph. Generic or
546
- repeated anchors union their windows and may produce `AMBIGUOUS_TARGET`, costing
547
- a recovery turn. `--around 3` is symmetric; `--occurrence` disambiguates only
548
- inside an already selected paragraph. `anchorMatchCount > 1` signals a repeated
549
- anchor even after a successful edit. Matching is exact and case-sensitive first,
550
- then permits only ASCII-space/NBSP equivalence; ambiguity still fails closed.
551
-
552
- Localized `change` evidence retains bounded before/after excerpts, commit
553
- disposition, and accepted-view verification for audit consumers. Ordinary
554
- agents rely on the computed top-level `completion` signal.
546
+ - **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`.
547
+ - **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.
548
+ - **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`.
549
+ - **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.
550
+ - **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.
551
+ - Check `completion: true` and a non-null `outputPath` on stdout. Failed,
552
+ partial, rolled-back, unverified, and unwritten work cannot appear complete.
553
+ If an error occurs, follow `error.recovery.action` before correcting the cause.
554
+
555
+ For multi-clause or multi-page reviews, use focused batches rather than one
556
+ massive batch. This keeps context compact and simplifies error diagnosis.
557
+
558
+ #### Optional Speculative Localized Apply
559
+
560
+ Targetless localized apply remains available to expert users and harnesses with
561
+ a measured confidence policy. It is not the ordinary agent workflow:
562
+
563
+ ```bash
564
+ # Globally unique literal
565
+ docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
566
+
567
+ # Optional directional scope using a longer, fairly unique phrase
568
+ docx-redline apply contract.docx --search "distinctive nearby heading or phrase" --context-range 1:3 --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
569
+ ```
570
+
571
+ Omitting a target asks the CLI to resolve one eligible paragraph. Generic or
572
+ repeated anchors union their windows and may produce `AMBIGUOUS_TARGET`, costing
573
+ a recovery turn. `--around 3` is symmetric; `--occurrence` disambiguates only
574
+ inside an already selected paragraph. `anchorMatchCount > 1` signals a repeated
575
+ anchor even after a successful edit. Matching is exact and case-sensitive first,
576
+ then permits only ASCII-space/NBSP equivalence; ambiguity still fails closed.
577
+
578
+ Localized `change` evidence retains bounded before/after excerpts, commit
579
+ disposition, and accepted-view verification for audit consumers. Ordinary
580
+ agents rely on the computed top-level `completion` signal.
555
581
 
556
582
  #### High-Assurance / Staged Verification Workflow (Optional)
557
583
 
@@ -574,7 +600,7 @@ targets. For most unique clauses, `"target": "exact paragraph text"` is
574
600
  sufficient; use discriminators (`paragraphId`, `fingerprint`, `index`, or
575
601
  `occurrence`) when duplicate paragraph text appears in the document. Never
576
602
  normalize or reconstruct `exactText`. Operation files follow
577
- [`docs/schemas/document-operations.schema.json`](schemas/document-operations.schema.json).
603
+ [`docs/schemas/document-operations.schema.json`](schemas/document-operations.schema.json).
578
604
 
579
605
  #### Commands
580
606
 
@@ -583,39 +609,41 @@ normalize or reconstruct `exactText`. Operation files follow
583
609
  - `preflight` checks targets, anchors, revisions, conflicts, authors, and needed artifacts without mutation (read-only).
584
610
  - `apply` applies an operation file transactionally with automatic rollback and internal markup validation.
585
611
  - `accept` and `reject` resolve revisions selected by `--author` or `--all-authors`.
586
- - `delete-comments` removes matching definitions and document anchors together.
612
+ - `delete-comments` removes matching definitions and document anchors together, selected by `--author`,
613
+ `--all-authors`, or `--comment-id <id[,id]>` (ids from `inspect`). Deleting a thread root also deletes its
614
+ replies; unknown ids fail with `COMMENT_NOT_FOUND`.
587
615
  - A whole-paragraph delete stops with `COMMENTED_CONTENT_DELETE` when the
588
616
  paragraph has an existing comment. Surface the returned reviewer and comment
589
617
  text for human follow-up; do not silently convert this into comment removal.
590
618
  - `validate` audits revision markup and DOCX package wiring, optionally comparing against a `--baseline`.
591
619
 
592
- Paragraph indexes are 1-based. Inspection filters are `--index 12`,
593
- `--range 10:30`, `--indexes 2,5,8`, `--search text`, `--revised`, `--table`,
594
- `--body`, `--non-empty`, and `--view accepted|rejected|current`. Search is
595
- case-insensitive. When a search returns 0 matches in the active view but matches
596
- exist in the alternate view (such as searching for a deleted clause to restore),
597
- `selection.hint` provides immediate guidance to re-run with `--view rejected`.
598
- Compact `extract --revised` results retain `hasRevisions`, `revisionAuthors`,
599
- and `deleted`/`inserted` cues, so an empty `exactText` is identifiable as content
600
- hidden by the selected revision view rather than a failed filter.
601
- For one strongly identified rejected-view paragraph, `apply --restore
602
- --target-id ID` restores the rejected text verbatim. One exact
603
- `--find`/`--replace` pair may adjust it without restating the paragraph. The
604
- shortcut refuses ranges, captures, weak targets, and inferred adjacent content.
605
- Add `--around N` (`--context N` or `-C N`) to a search;
606
- context records are labeled separately and do not consume the direct-hit
607
- `--limit`. Continue a bounded result with `--after <paragraph-index>`. Ordinary
608
- unscoped CLI inspection defaults to 20 direct records and a 48 KiB soft budget;
609
- `--all` deliberately opts out. A malformed filter or unknown option is an error
610
- rather than an unfiltered fallback. CLI help commands (`apply --help`,
611
- `extract --help`, `--help`) report canonical GitHub repository URLs in their
612
- `documentation` array.
613
-
614
- Mutating commands use `--author`, operation-level authors, then
615
- `DOCX_REDLINE_AUTHOR`, falling back visibly to `AI Redliner`; review-resolution
616
- commands may use `--all-authors` where applicable. Without `--output`, a sibling
617
- such as `contract.redlined.docx` is chosen. Existing outputs are refused unless
618
- `--force` is present. `--in-place` is the only way to overwrite the input.
620
+ Paragraph indexes are 1-based. Inspection filters are `--index 12`,
621
+ `--range 10:30`, `--indexes 2,5,8`, `--search text`, `--revised`, `--table`,
622
+ `--body`, `--non-empty`, and `--view accepted|rejected|current`. Search is
623
+ case-insensitive. When a search returns 0 matches in the active view but matches
624
+ exist in the alternate view (such as searching for a deleted clause to restore),
625
+ `selection.hint` provides immediate guidance to re-run with `--view rejected`.
626
+ Compact `extract --revised` results retain `hasRevisions`, `revisionAuthors`,
627
+ and `deleted`/`inserted` cues, so an empty `exactText` is identifiable as content
628
+ hidden by the selected revision view rather than a failed filter.
629
+ For one strongly identified rejected-view paragraph, `apply --restore
630
+ --target-id ID` restores the rejected text verbatim. One exact
631
+ `--find`/`--replace` pair may adjust it without restating the paragraph. The
632
+ shortcut refuses ranges, captures, weak targets, and inferred adjacent content.
633
+ Add `--around N` (`--context N` or `-C N`) to a search;
634
+ context records are labeled separately and do not consume the direct-hit
635
+ `--limit`. Continue a bounded result with `--after <paragraph-index>`. Ordinary
636
+ unscoped CLI inspection defaults to 20 direct records and a 48 KiB soft budget;
637
+ `--all` deliberately opts out. A malformed filter or unknown option is an error
638
+ rather than an unfiltered fallback. CLI help commands (`apply --help`,
639
+ `extract --help`, `--help`) report canonical GitHub repository URLs in their
640
+ `documentation` array.
641
+
642
+ Mutating commands use `--author`, operation-level authors, then
643
+ `DOCX_REDLINE_AUTHOR`, falling back visibly to `AI Redliner`; review-resolution
644
+ commands may use `--all-authors` where applicable. Without `--output`, a sibling
645
+ such as `contract.redlined.docx` is chosen. Existing outputs are refused unless
646
+ `--force` is present. `--in-place` is the only way to overwrite the input.
619
647
 
620
648
  Treat a nonzero exit code or JSON `status: "error"` as failure. A failed atomic
621
649
  operation reports `written: false` and does not write an output file.
@@ -630,8 +658,57 @@ To reply inside an existing Word comment thread, use the comment ID returned by
630
658
  { "type": "comment_reply", "parentCommentId": "8", "commentContent": "Agreed; updated.", "author": "Editor" }
631
659
  ```
632
660
 
633
- Replies are represented in `word/commentsExtended.xml` and deliberately add no
634
- new `commentRangeStart`, `commentRangeEnd`, or `commentReference` to the body.
661
+ A reply is defined in `word/comments.xml` and linked to its thread in `word/commentsExtended.xml`
662
+ (`w15:paraIdParent`, keyed on the last paragraph of each comment, as Word does). Word only displays a
663
+ reply that also has its own `commentRangeStart`, `commentRangeEnd` and `commentReference` around the same
664
+ range as its thread, so the operation adds those markers next to the thread's existing ones. A reply to a
665
+ reply joins the root thread. If the parent has no anchor in the body the operation fails with
666
+ `PARENT_ANCHOR_NOT_FOUND`.
667
+
668
+ #### Headers and footers
669
+
670
+ `inspect().headersFooters` lists every header/footer part the document uses, with its paragraph text:
671
+ `path`, `kind`, `type` (`default`/`first`/`even`), `appliesToSections`, `active`, `hasFields`. Add `part` to an
672
+ operation to edit one instead of the body; everything else in the operation is unchanged:
673
+
674
+ ```json
675
+ { "type": "replace", "part": { "kind": "footer", "type": "default", "section": 0 },
676
+ "target": { "exactText": "Draft v1" }, "modified": "Draft v2" }
677
+ ```
678
+
679
+ `part` is a selector (`kind`, optional `type` defaulting to `default`, optional zero-based `section`) or a part path such as
680
+ `word/footer2.xml`. Omitting `part` always targets the body; headers and footers are opt-in. Rules:
681
+
682
+ - A footer or header shared by several sections is one part; the result lists `partSections`. A section with no reference of its
683
+ own inherits the previous section's part (Word's "link to previous"), and `section` finds it.
684
+ - Several distinct parts matching a selector fail with `PART_AMBIGUOUS` (add `section` or use the path); none fail with
685
+ `PART_NOT_FOUND`. A bad selector rejects the whole request before anything is written.
686
+ - Fields are atomic. An edit that would change a `PAGE`/`NUMPAGES`/`DATE` field's text fails with `FIELD_EDIT_REFUSED`; edit the
687
+ text around the field instead.
688
+ - `comment`, `comment_reply` and `comment_resolve` with `part` fail with `COMMENT_IN_HEADER_FOOTER` (Word has no comments in headers or footers).
689
+ - Body and part operations can share a batch; results keep the original operation indexes and carry `part`. Atomic batches roll
690
+ back across body and parts. Revision ids stay unique across the whole package.
691
+ - `accept` / `reject` (API and CLI) also resolve tracked changes inside headers and footers.
692
+ - Creating a header/footer that does not exist is not supported.
693
+ - `exactText` in a header or footer is the whole paragraph text as `headersFooters[].paragraphs` shows it, including the
694
+ current result of any field (for example `Page 1`).
695
+
696
+ #### Resolving comment threads
697
+
698
+ Resolve (or reopen with `"resolved": false`) the thread containing a comment id from `inspect`:
699
+
700
+ ```json
701
+ { "type": "comment_resolve", "commentId": "8" }
702
+ ```
703
+
704
+ The API form is `await doc.resolveComment(commentId, { resolved })`. Word resolves whole threads, so any id in
705
+ the thread updates the root and every reply together; a reply added to a resolved thread is written resolved,
706
+ as Word does. Only `word/commentsExtended.xml` changes. Resolving an already-resolved thread is `no_change`.
707
+ Unknown ids fail with `COMMENT_NOT_FOUND`, in `preflight` as well as `apply`. Operations run in batch order, so
708
+ `comment_reply` followed by `comment_resolve` closes the thread including the new reply. Like any comment
709
+ operation it is refused with `part`.
710
+
711
+ Resolving and deleting other reviewers' comments are review decisions: do them only when the user asked.
635
712
 
636
713
  #### Legacy skill wrapper migration
637
714
 
@@ -653,7 +730,7 @@ must use the JSON status and process exit code; failed atomic work has
653
730
 
654
731
  #### Safe Operations File Creation (JSON vs. Shell Heredocs)
655
732
 
656
- When composing batch operation JSON, whether for a file or stdin:
733
+ When composing batch operation JSON, whether for a file or stdin:
657
734
 
658
735
  - **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`).
659
736
  - **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.
@@ -664,9 +741,9 @@ In default progressive mode (`atomic: false`), operations execute independently:
664
741
 
665
742
  - **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.
666
743
  - **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.
667
- - **`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.
668
- - **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.
669
- - **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`.
744
+ - **`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.
745
+ - **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.
746
+ - **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`.
670
747
 
671
748
  #### Human-Readable References vs. Internal Machine Handles
672
749
 
@@ -687,30 +764,40 @@ When the CLI or runner returns an error code, follow these specific recovery act
687
764
  |---|---|---|
688
765
  | `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`). |
689
766
  | `AMBIGUOUS_TARGET` | Multiple paragraphs match identical text. | Disambiguate by supplying `paragraphId`, `fingerprint`, `occurrence`, or `index` in the target descriptor. |
690
- | `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`. |
691
- | `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. |
692
- | `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. |
693
- | `CAPTURE_FANOUT_CONFLICT` | Multiple mutating consumers share one capture without distinct selectors. | Give consumers distinct selectors, chain them explicitly, or split the batch. |
694
- | `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. |
695
- | `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. |
696
- | `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. |
697
- | `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. |
698
- | `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. |
699
- | `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. |
700
- | `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. |
767
+ | `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`. |
768
+ | `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. |
769
+ | `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. |
770
+ | `CAPTURE_FANOUT_CONFLICT` | Multiple mutating consumers share one capture without distinct selectors. | Give consumers distinct selectors, chain them explicitly, or split the batch. |
771
+ | `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. |
772
+ | `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. |
773
+ | `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. |
774
+ | `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. |
775
+ | `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. |
776
+ | `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. |
777
+ | `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. |
778
+ | `PART_NOT_FOUND` / `PART_AMBIGUOUS` | The `part` selector matched no header/footer, or several. | Re-run `inspect` and use `headersFooters[].path`, or add `section`. |
779
+ | `FIELD_EDIT_REFUSED` | The edit would change a field (PAGE, NUMPAGES, DATE) in a header or footer. | Edit the text around the field. |
780
+ | `COMMENT_IN_HEADER_FOOTER` | A comment operation carried `part`. | Comment on body text instead. |
781
+ | `COMMENT_NOT_FOUND` | `comment_resolve` or `delete-comments --comment-id` named an id that is not in the document. | Re-run `inspect` and use `comments[].id`. |
782
+ | `PARENT_COMMENT_NOT_FOUND` / `PARENT_ANCHOR_NOT_FOUND` | A reply's parent id is unknown, or the parent has no anchor in the body. | Re-run `inspect`; reply to a comment that is anchored in the body. |
701
783
  | `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. |
702
- | `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. |
784
+ | `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. |
703
785
  | `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. |
704
786
 
705
787
  **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.
706
788
 
707
789
  #### Document Scope & Boundary Invariants
708
790
 
709
- The `docx-redline` engine and CLI operate specifically on the **main document body**:
791
+ The `docx-redline` engine and CLI operate on the **main document body** and, when an operation sets `part`, on
792
+ **existing header and footer parts**:
710
793
 
711
- - **Supported Content**: Body paragraphs, numbered/bulleted lists, tables and table cells, comments, and comment replies.
712
- - **Unsupported Content**: Headers, footers, footnotes, endnotes, floating text boxes, shape drawings, watermarks, and embedded macros.
713
- - 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.
794
+ - **Supported Content**: Body paragraphs, numbered/bulleted lists, tables and table cells, comments, comment replies,
795
+ resolving/reopening comment threads, and text in existing headers and footers (see *Headers and footers*).
796
+ - **Unsupported Content**: Footnotes, endnotes, floating text boxes, shape drawings, images, watermarks, embedded macros,
797
+ creating a header or footer that does not exist, editing field codes or results (`PAGE`, `NUMPAGES`, `DATE`), and
798
+ comments in headers or footers (Word has none).
799
+ - Do not attempt to target footnote citations or text boxes with `docx-redline`. Header/footer text is edited only with
800
+ an explicit `part`; body searches never match it.
714
801
 
715
802
  ### Convert paragraph text into a Word list
716
803
 
@@ -763,12 +850,12 @@ import { reconcileMarkdownTableOoxml } from '@ansonlai/docx-redline-js';
763
850
  const result = await reconcileMarkdownTableOoxml(tableOoxml, originalText, markdownTable);
764
851
  ```
765
852
 
766
- ## Detailed Architecture
767
-
768
- Use the layout and task shortcuts at the top of this file for normal work.
769
- `ARCHITECTURE.md` is the maintained detailed module-ownership map and describes
770
- the end-to-end dependency flow. Do not infer ownership from directory names or
771
- reconstruct the architecture by repeatedly listing the repository.
853
+ ## Detailed Architecture
854
+
855
+ Use the layout and task shortcuts at the top of this file for normal work.
856
+ `ARCHITECTURE.md` is the maintained detailed module-ownership map and describes
857
+ the end-to-end dependency flow. Do not infer ownership from directory names or
858
+ reconstruct the architecture by repeatedly listing the repository.
772
859
 
773
860
  ## Common Patterns
774
861
 
@@ -826,22 +913,22 @@ retain their string-only return type and return `''` for parse failures.
826
913
 
827
914
  ### Target text versus replacement text
828
915
 
829
- Target resolution may normalize surrounding or repeated whitespace while
830
- matching a paragraph. Replacement text is not normalized: tabs, line breaks,
831
- non-breaking spaces, repeated spaces, and leading/trailing whitespace become
832
- part of the requested edit. When editing extracted document text, copy the
833
- exact paragraph text and modify it in place rather than round-tripping it
834
- through a formatter that may change whitespace.
835
-
836
- The normalized caller target is never used for mutation offsets in a text-bearing
837
- edit. After target selection, the engine uses the resolved paragraph's byte-exact
838
- JavaScript string as the source coordinate system. The legacy format-only
839
- fallback for field-code paragraphs with no extractable accepted-view spans is
840
- not a text-replacement path. If an ASCII-space target selected an NBSP
841
- source, `resolvedTarget.targetTextMatch` reports `space_equivalent`, escaped
842
- source/request excerpts, and differing code points. An NBSP-to-space request is
843
- tracked as a replacement; it must not retain the NBSP and append another space.
844
- The CLI keeps these bounded diagnostics but removes resolved clause text.
916
+ Target resolution may normalize surrounding or repeated whitespace while
917
+ matching a paragraph. Replacement text is not normalized: tabs, line breaks,
918
+ non-breaking spaces, repeated spaces, and leading/trailing whitespace become
919
+ part of the requested edit. When editing extracted document text, copy the
920
+ exact paragraph text and modify it in place rather than round-tripping it
921
+ through a formatter that may change whitespace.
922
+
923
+ The normalized caller target is never used for mutation offsets in a text-bearing
924
+ edit. After target selection, the engine uses the resolved paragraph's byte-exact
925
+ JavaScript string as the source coordinate system. The legacy format-only
926
+ fallback for field-code paragraphs with no extractable accepted-view spans is
927
+ not a text-replacement path. If an ASCII-space target selected an NBSP
928
+ source, `resolvedTarget.targetTextMatch` reports `space_equivalent`, escaped
929
+ source/request excerpts, and differing code points. An NBSP-to-space request is
930
+ tracked as a replacement; it must not retain the NBSP and append another space.
931
+ The CLI keeps these bounded diagnostics but removes resolved clause text.
845
932
 
846
933
  For ordinary insertions and deletions, target the visible accepted view:
847
934
  inserted `w:t` text is visible and deleted `w:delText` is not. Move revisions