@ansonlai/docx-redline-js 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,180 +387,155 @@ 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 (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. For an exact mechanical change with known old and new literals, use localized
486
- `--find`/`--replace`. Omit the target for global fail-closed resolution, or
487
- scope it with a longer, fairly unique nearby `--search` phrase and a
488
- directional range such as `--context-range 1:3`. Generic or repeated anchors
489
- widen the unioned scope and may fail with `AMBIGUOUS_TARGET`, costing another
490
- turn. `--occurrence` selects only within one uniquely resolved paragraph.
491
- 2. For semantic drafting, run one focused `extract`. Add `--around 3` when
492
- surrounding context on either side is needed, then copy `exactText` plus
493
- `paragraphId` or `fingerprint`.
494
- 3. Build the final operations from the operation table above. Use one operation
495
- per target paragraph, and consolidate multiple changes to that paragraph.
496
- 4. Run `apply` once per stable batch. Strong inspected targets are bound against
497
- the batch-start document, so independent operations do not need manual
498
- bottom-up sorting around structural edits. Consolidate multiple complete
499
- desired states for the same source. A unique exact reference to paragraph
500
- text created elsewhere in the batch is scheduled automatically; use explicit
501
- captures/selectors for non-unique or advanced created-content dependencies.
502
- 5. Walk every result and require `completion: true`, `written: true`, and no
503
- per-operation error. For localized patches also require
504
- `results[i].change.committed: true`, `finalDisposition: "applied"`, and
505
- positive accepted-view verification. If `anchorMatchCount > 1`, confirm the
506
- returned location and excerpts because the anchor was repeated.
507
- 6. Run a focused `extract` on changed clauses only when placement or list/table
508
- structure needs confirmation.
509
-
510
- Do not probe operation behavior with disposable apply commands or read a vendor
511
- bundle before this route. If `apply` returns an error, use the recovery matrix
512
- below and make one cause-specific correction.
513
-
514
- ```bash
515
- # 1. Globally unique mechanical edit in one call
516
- docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
517
-
518
- # 2. Directional edit using a longer phrase with a higher chance of uniqueness
519
- 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
520
-
521
- # 3. Focused contextual discovery for semantic drafting
522
- docx-redline extract contract.docx --search "termination" --around 3
523
-
524
- # 4. Inline complete-paragraph edit
525
- docx-redline apply contract.docx --target "Original clause" --modified "New clause" --output reviewed.docx
526
-
527
- # 5. Direct edit without tracked changes (clean text, no revision clutter)
528
- docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
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`, 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
+ ```
529
491
 
530
- # 6. Cross-author edit inside another reviewer's pending insertion
531
- docx-redline apply contract.docx --target "Pending clause text" --modified "Updated clause text" --existing-revisions slice-cross-author --output reviewed.docx
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.
532
529
 
533
- # 7. Batch operations with ops.json
534
- docx-redline apply contract.docx --operations operations.json --output reviewed.docx
535
-
536
- # 8. Serializer-backed stdin with compact stdout
537
- node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
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
538
539
  ```
539
540
 
540
541
  Key CLI defaults and behaviors:
@@ -542,14 +543,41 @@ Key CLI defaults and behaviors:
542
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.
543
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.
544
545
  - **Tracked changes**: Defaults to `generateRedlines: true`. When clean direct text is needed, pass `--no-redlines`.
545
- - **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`.
546
- - **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.
547
- - **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`.
548
- - **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.
549
- - **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.
550
- - 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.
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.
551
554
 
552
- 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.
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.
553
581
 
554
582
  #### High-Assurance / Staged Verification Workflow (Optional)
555
583
 
@@ -572,7 +600,7 @@ targets. For most unique clauses, `"target": "exact paragraph text"` is
572
600
  sufficient; use discriminators (`paragraphId`, `fingerprint`, `index`, or
573
601
  `occurrence`) when duplicate paragraph text appears in the document. Never
574
602
  normalize or reconstruct `exactText`. Operation files follow
575
- [`docs/schemas/document-operations.schema.json`](schemas/document-operations.schema.json).
603
+ [`docs/schemas/document-operations.schema.json`](schemas/document-operations.schema.json).
576
604
 
577
605
  #### Commands
578
606
 
@@ -587,33 +615,33 @@ normalize or reconstruct `exactText`. Operation files follow
587
615
  text for human follow-up; do not silently convert this into comment removal.
588
616
  - `validate` audits revision markup and DOCX package wiring, optionally comparing against a `--baseline`.
589
617
 
590
- Paragraph indexes are 1-based. Inspection filters are `--index 12`,
591
- `--range 10:30`, `--indexes 2,5,8`, `--search text`, `--revised`, `--table`,
592
- `--body`, `--non-empty`, and `--view accepted|rejected|current`. Search is
593
- case-insensitive. When a search returns 0 matches in the active view but matches
594
- exist in the alternate view (such as searching for a deleted clause to restore),
595
- `selection.hint` provides immediate guidance to re-run with `--view rejected`.
596
- Compact `extract --revised` results retain `hasRevisions`, `revisionAuthors`,
597
- and `deleted`/`inserted` cues, so an empty `exactText` is identifiable as content
598
- hidden by the selected revision view rather than a failed filter.
599
- For one strongly identified rejected-view paragraph, `apply --restore
600
- --target-id ID` restores the rejected text verbatim. One exact
601
- `--find`/`--replace` pair may adjust it without restating the paragraph. The
602
- shortcut refuses ranges, captures, weak targets, and inferred adjacent content.
603
- Add `--around N` (`--context N` or `-C N`) to a search;
604
- context records are labeled separately and do not consume the direct-hit
605
- `--limit`. Continue a bounded result with `--after <paragraph-index>`. Ordinary
606
- unscoped CLI inspection defaults to 20 direct records and a 48 KiB soft budget;
607
- `--all` deliberately opts out. A malformed filter or unknown option is an error
608
- rather than an unfiltered fallback. CLI help commands (`apply --help`,
609
- `extract --help`, `--help`) report canonical GitHub repository URLs in their
610
- `documentation` array.
611
-
612
- Mutating commands use `--author`, operation-level authors, then
613
- `DOCX_REDLINE_AUTHOR`, falling back visibly to `AI Redliner`; review-resolution
614
- commands may use `--all-authors` where applicable. Without `--output`, a sibling
615
- such as `contract.redlined.docx` is chosen. Existing outputs are refused unless
616
- `--force` is present. `--in-place` is the only way to overwrite the input.
618
+ Paragraph indexes are 1-based. Inspection filters are `--index 12`,
619
+ `--range 10:30`, `--indexes 2,5,8`, `--search text`, `--revised`, `--table`,
620
+ `--body`, `--non-empty`, and `--view accepted|rejected|current`. Search is
621
+ case-insensitive. When a search returns 0 matches in the active view but matches
622
+ exist in the alternate view (such as searching for a deleted clause to restore),
623
+ `selection.hint` provides immediate guidance to re-run with `--view rejected`.
624
+ Compact `extract --revised` results retain `hasRevisions`, `revisionAuthors`,
625
+ and `deleted`/`inserted` cues, so an empty `exactText` is identifiable as content
626
+ hidden by the selected revision view rather than a failed filter.
627
+ For one strongly identified rejected-view paragraph, `apply --restore
628
+ --target-id ID` restores the rejected text verbatim. One exact
629
+ `--find`/`--replace` pair may adjust it without restating the paragraph. The
630
+ shortcut refuses ranges, captures, weak targets, and inferred adjacent content.
631
+ Add `--around N` (`--context N` or `-C N`) to a search;
632
+ context records are labeled separately and do not consume the direct-hit
633
+ `--limit`. Continue a bounded result with `--after <paragraph-index>`. Ordinary
634
+ unscoped CLI inspection defaults to 20 direct records and a 48 KiB soft budget;
635
+ `--all` deliberately opts out. A malformed filter or unknown option is an error
636
+ rather than an unfiltered fallback. CLI help commands (`apply --help`,
637
+ `extract --help`, `--help`) report canonical GitHub repository URLs in their
638
+ `documentation` array.
639
+
640
+ Mutating commands use `--author`, operation-level authors, then
641
+ `DOCX_REDLINE_AUTHOR`, falling back visibly to `AI Redliner`; review-resolution
642
+ commands may use `--all-authors` where applicable. Without `--output`, a sibling
643
+ such as `contract.redlined.docx` is chosen. Existing outputs are refused unless
644
+ `--force` is present. `--in-place` is the only way to overwrite the input.
617
645
 
618
646
  Treat a nonzero exit code or JSON `status: "error"` as failure. A failed atomic
619
647
  operation reports `written: false` and does not write an output file.
@@ -651,7 +679,7 @@ must use the JSON status and process exit code; failed atomic work has
651
679
 
652
680
  #### Safe Operations File Creation (JSON vs. Shell Heredocs)
653
681
 
654
- When composing batch operation JSON, whether for a file or stdin:
682
+ When composing batch operation JSON, whether for a file or stdin:
655
683
 
656
684
  - **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`).
657
685
  - **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.
@@ -662,9 +690,9 @@ In default progressive mode (`atomic: false`), operations execute independently:
662
690
 
663
691
  - **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.
664
692
  - **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.
665
- - **`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.
666
- - **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.
667
- - **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`.
693
+ - **`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.
694
+ - **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.
695
+ - **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`.
668
696
 
669
697
  #### Human-Readable References vs. Internal Machine Handles
670
698
 
@@ -685,19 +713,19 @@ When the CLI or runner returns an error code, follow these specific recovery act
685
713
  |---|---|---|
686
714
  | `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`). |
687
715
  | `AMBIGUOUS_TARGET` | Multiple paragraphs match identical text. | Disambiguate by supplying `paragraphId`, `fingerprint`, `occurrence`, or `index` in the target descriptor. |
688
- | `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`. |
689
- | `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. |
690
- | `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. |
691
- | `CAPTURE_FANOUT_CONFLICT` | Multiple mutating consumers share one capture without distinct selectors. | Give consumers distinct selectors, chain them explicitly, or split the batch. |
692
- | `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. |
693
- | `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. |
694
- | `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. |
695
- | `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. |
696
- | `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. |
697
- | `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. |
698
- | `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. |
716
+ | `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`. |
717
+ | `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. |
718
+ | `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. |
719
+ | `CAPTURE_FANOUT_CONFLICT` | Multiple mutating consumers share one capture without distinct selectors. | Give consumers distinct selectors, chain them explicitly, or split the batch. |
720
+ | `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. |
721
+ | `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. |
722
+ | `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. |
723
+ | `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. |
724
+ | `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. |
725
+ | `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. |
726
+ | `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. |
699
727
  | `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. |
700
- | `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. |
728
+ | `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. |
701
729
  | `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. |
702
730
 
703
731
  **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.
@@ -761,12 +789,12 @@ import { reconcileMarkdownTableOoxml } from '@ansonlai/docx-redline-js';
761
789
  const result = await reconcileMarkdownTableOoxml(tableOoxml, originalText, markdownTable);
762
790
  ```
763
791
 
764
- ## Detailed Architecture
765
-
766
- Use the layout and task shortcuts at the top of this file for normal work.
767
- `ARCHITECTURE.md` is the maintained detailed module-ownership map and describes
768
- the end-to-end dependency flow. Do not infer ownership from directory names or
769
- reconstruct the architecture by repeatedly listing the repository.
792
+ ## Detailed Architecture
793
+
794
+ Use the layout and task shortcuts at the top of this file for normal work.
795
+ `ARCHITECTURE.md` is the maintained detailed module-ownership map and describes
796
+ the end-to-end dependency flow. Do not infer ownership from directory names or
797
+ reconstruct the architecture by repeatedly listing the repository.
770
798
 
771
799
  ## Common Patterns
772
800
 
@@ -824,22 +852,22 @@ retain their string-only return type and return `''` for parse failures.
824
852
 
825
853
  ### Target text versus replacement text
826
854
 
827
- Target resolution may normalize surrounding or repeated whitespace while
828
- matching a paragraph. Replacement text is not normalized: tabs, line breaks,
829
- non-breaking spaces, repeated spaces, and leading/trailing whitespace become
830
- part of the requested edit. When editing extracted document text, copy the
831
- exact paragraph text and modify it in place rather than round-tripping it
832
- through a formatter that may change whitespace.
833
-
834
- The normalized caller target is never used for mutation offsets in a text-bearing
835
- edit. After target selection, the engine uses the resolved paragraph's byte-exact
836
- JavaScript string as the source coordinate system. The legacy format-only
837
- fallback for field-code paragraphs with no extractable accepted-view spans is
838
- not a text-replacement path. If an ASCII-space target selected an NBSP
839
- source, `resolvedTarget.targetTextMatch` reports `space_equivalent`, escaped
840
- source/request excerpts, and differing code points. An NBSP-to-space request is
841
- tracked as a replacement; it must not retain the NBSP and append another space.
842
- The CLI keeps these bounded diagnostics but removes resolved clause text.
855
+ Target resolution may normalize surrounding or repeated whitespace while
856
+ matching a paragraph. Replacement text is not normalized: tabs, line breaks,
857
+ non-breaking spaces, repeated spaces, and leading/trailing whitespace become
858
+ part of the requested edit. When editing extracted document text, copy the
859
+ exact paragraph text and modify it in place rather than round-tripping it
860
+ through a formatter that may change whitespace.
861
+
862
+ The normalized caller target is never used for mutation offsets in a text-bearing
863
+ edit. After target selection, the engine uses the resolved paragraph's byte-exact
864
+ JavaScript string as the source coordinate system. The legacy format-only
865
+ fallback for field-code paragraphs with no extractable accepted-view spans is
866
+ not a text-replacement path. If an ASCII-space target selected an NBSP
867
+ source, `resolvedTarget.targetTextMatch` reports `space_equivalent`, escaped
868
+ source/request excerpts, and differing code points. An NBSP-to-space request is
869
+ tracked as a replacement; it must not retain the NBSP and append another space.
870
+ The CLI keeps these bounded diagnostics but removes resolved clause text.
843
871
 
844
872
  For ordinary insertions and deletions, target the visible accepted view:
845
873
  inserted `w:t` text is visible and deleted `w:delText` is not. Move revisions