@ansonlai/docx-redline-js 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/AGENTS.md +82 -667
  2. package/ARCHITECTURE.md +51 -4
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +176 -39
  5. package/core/paragraph-revision-safety.js +10 -8
  6. package/core/paragraph-targeting.js +14 -2
  7. package/core/redline-validation.js +7 -4
  8. package/core/revision-cloning.js +21 -0
  9. package/core/validation-delta.js +23 -0
  10. package/dist/docx-redline-js.esm.js +275 -45
  11. package/dist/docx-redline-js.esm.js.map +3 -3
  12. package/dist/docx-redline-js.esm.min.js +82 -82
  13. package/dist/docx-redline-js.esm.min.js.map +4 -4
  14. package/docs/AGENT_FAST_START.md +59 -0
  15. package/docs/AGENT_KNOWLEDGE_BASE.md +868 -0
  16. package/docs/TESTING.md +20 -1
  17. package/docs/schemas/document-operations.schema.json +16 -2
  18. package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +82 -0
  19. package/engine/oxml-engine.js +80 -13
  20. package/engine/run-builders.js +5 -15
  21. package/engine/surgical-mode.js +148 -3
  22. package/engine/surgical-run-splitting.js +19 -7
  23. package/engine/surgical-spans.js +2 -1
  24. package/index.d.ts +17 -1
  25. package/node/cli.js +235 -36
  26. package/node/docx-document.js +137 -83
  27. package/node/index.d.ts +6 -2
  28. package/package.json +10 -3
  29. package/pipeline/diff-engine.js +15 -0
  30. package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
  31. package/services/batch-operation-orchestrator.js +215 -120
  32. package/services/document-inspection.js +5 -3
  33. package/services/document-operation-applier.js +99 -36
  34. package/services/document-operation-contract.js +50 -6
  35. package/services/document-operation-mutations.js +404 -41
  36. package/services/document-operation-session.js +4 -0
  37. package/services/error-recovery.js +174 -0
  38. package/services/operation-batch-compiler.js +394 -0
  39. package/services/operation-preflight.js +91 -72
  40. package/services/standalone-operation-runner.d.ts +35 -1
  41. package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
  42. package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -856
  43. package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
  44. package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
  45. package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
  46. package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
  47. package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
  48. package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
  49. package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
  50. package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
  51. package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
  52. package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
  53. package/docs/test-comparison-dashboard.html +0 -4338
  54. package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
  55. package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
  56. package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
  57. package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
  58. package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
  59. package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
package/ARCHITECTURE.md CHANGED
@@ -69,7 +69,13 @@ No Word add-in entrypoints or host-specific integration layers are part of this
69
69
 
70
70
  ## Entry Points
71
71
 
72
- - `index.js` (Root exports containing the reconciliation logic)
72
+ - `index.js`: primary host-independent exports for OOXML reconciliation.
73
+ - `services/standalone-operation-runner.js`: stable complete-document XML
74
+ operations, exported as `@ansonlai/docx-redline-js/standalone-runner`.
75
+ - `node/index.js`: Node-only complete-DOCX buffer facade, exported as
76
+ `@ansonlai/docx-redline-js/node`.
77
+ - `bin/docx-redline.js`: CLI launcher; command implementation lives in
78
+ `node/cli.js` and shared operation behavior lives in `services/`.
73
79
 
74
80
  ## Module Responsibilities
75
81
 
@@ -94,6 +100,12 @@ No Word add-in entrypoints or host-specific integration layers are part of this
94
100
  fingerprints, document order, and table context for deterministic reuse.
95
101
  - `core/redline-validation.js`
96
102
  - Runtime structural validation (`validateRedlineOoxml`) mirroring the test-suite invariants: no nested revisions, `w:delText` inside `w:del`, complete revision metadata, unique revision ids, preserved boundary whitespace.
103
+ - `core/validation-delta.js`
104
+ - Stable issue signatures and multiset subtraction for classifying baseline
105
+ versus generated validation issues without hiding added duplicate errors.
106
+ - `core/revision-cloning.js`
107
+ - Shared effective-property cloning strips historical revision descendants;
108
+ intentional revision-bearing splits refresh cloned property-change IDs.
97
109
  - `engine/oxml-engine.js`
98
110
  - Main reconciliation router, mode selection, existing-revision policy gate, and status/error result handling.
99
111
  - `engine/route-selection.js`
@@ -102,7 +114,10 @@ No Word add-in entrypoints or host-specific integration layers are part of this
102
114
  - `engine/run-builders.js`
103
115
  - Shared builders for insertion/deletion wrappers, paragraph-mark revisions, visible run content, and run-property changes.
104
116
  - `engine/surgical-*.js`
105
- - Surgical run splitting, diff application, and span helpers for localized edits that preserve surrounding markup.
117
+ - Surgical run splitting, diff application, and span helpers for localized edits that preserve surrounding markup. Plain-text edit groups execute from right to left with a fresh live span index, while space/NBSP-only replacement hunks are refined to character-local changes so unchanged hyperlink containers survive.
118
+ The shared carrier splitter also handles explicit rejected-view deletion
119
+ splits, retaining `w:delText`, tabs, breaks, soft/non-breaking hyphens,
120
+ formatting, foreign metadata, and fresh trailing/property-change IDs.
106
121
  - `engine/formatting-removal.js`
107
122
  - Shared formatting removal and highlight helpers.
108
123
  - `pipeline/list-markers.js`
@@ -130,10 +145,21 @@ No Word add-in entrypoints or host-specific integration layers are part of this
130
145
  preflight, single-operation application, batch application, and scheduling.
131
146
  - `services/document-operation-applier.js`
132
147
  - Canonical single-operation validation, author resolution, dispatch, and
133
- result metadata assembly.
148
+ result metadata assembly. Before commit it validates the entire live
149
+ document against the operation savepoint and refuses newly generated
150
+ structural errors, including duplicate revision IDs.
134
151
  - `services/document-operation-mutations.js`
135
152
  - Coupled OOXML mutation implementations for redline, highlight, and comment
136
- operations. These use leaf-module imports and never import the root entry.
153
+ operations. For text-bearing edits, the resolved paragraph's exact
154
+ accepted-view text is the source coordinate system even when target
155
+ selection used normalized whitespace;
156
+ bounded match-mode/code-point diagnostics are attached to resolved target
157
+ metadata. Explicit rejected-view insertion splits a supported direct
158
+ foreign `w:del` at an anchor-relative offset into sibling deletion,
159
+ insertion, and deletion carriers. Paragraph restoration emits its inserted
160
+ block after the untouched source range and verifies baseline-delta,
161
+ mutation-envelope, and lifecycle postconditions. These use leaf-module
162
+ imports and never import the root entry.
137
163
  - `services/batch-operation-orchestrator.js`
138
164
  - Comment-first stable scheduling, atomic policy, artifact aggregation,
139
165
  per-operation results, one final document serialization, and deferred
@@ -157,11 +183,16 @@ No Word add-in entrypoints or host-specific integration layers are part of this
157
183
  revision authors, table context, and advisory visible numbering.
158
184
  - `node/docx-document.js`
159
185
  - Transactional whole-DOCX editing, artifact wiring, validation, and rollback.
186
+ OOXML and package issues are classified as baseline/generated multisets, so
187
+ unchanged source defects remain diagnostics while new defects block writes.
160
188
  This surface is excluded from the browser/root dependency graph.
161
189
  - `node/cli.js` and `bin/docx-redline.js`
162
190
  - Cross-platform, JSON-only agent command boundary. Read commands never
163
191
  mutate; write commands require attribution, use package transactions, and
164
192
  only overwrite source files under explicit `--in-place` authorization.
193
+ Mutation commands expose a compact contract: package/XML payloads and full
194
+ validation arrays remain internal, while stdout contains durability fields,
195
+ per-operation evidence, validation counts, and a derived completion flag.
165
196
  - `orchestration/*`
166
197
  - Route planning and list fallback orchestration utilities.
167
198
 
@@ -309,6 +340,22 @@ still be re-exported from `index.js`.
309
340
  - Operation-level authors override the batch author. Runtime results expose
310
341
  `authorUsed`, `authorsUsed`, `operationType`, `resolvedBy`, and resolved target
311
342
  metadata so integrations can audit what the engine actually selected.
343
+ - Target fingerprints are revision-view scoped. Inspection returns a
344
+ `revisionView` beside every paragraph and computes its fingerprint from the
345
+ same text/view pair. Restore normalization defaults omitted target and range
346
+ endpoint views to `rejected`; all other operations default to `accepted`.
347
+ Explicit views remain authoritative, and cross-view text/fingerprint mistakes
348
+ return an actionable mismatch hint rather than silently weakening targeting.
349
+ - A normalized target match does not become an edit coordinate system for a
350
+ text-bearing mutation. Mutation uses canonical accepted-view source text, and `resolvedTarget.targetTextMatch`
351
+ records `exact`, `space_equivalent`, or `normalized` selection plus bounded
352
+ invisible-character diagnostics.
353
+ - CLI contract version 3 is deliberately narrower than library result objects.
354
+ `apply`, `accept`, `reject`, and `delete-comments` omit `documentXml`, package
355
+ parts, inspection text, and full issue arrays. `completion` is true only when
356
+ `written === true`, top-level status is neither error nor partial, and every
357
+ operation result is non-error. The `validate` command remains the full issue
358
+ reporting surface.
312
359
  - `preflightOperations` is the read-only safety boundary for agent-generated
313
360
  batches. It uses strict targeting by default; mutation APIs retain permissive
314
361
  legacy targeting unless `strictTargets: true` is requested. In v1.0.0,
package/CHANGELOG.md CHANGED
@@ -5,10 +5,21 @@
5
5
  ### Safety Fixes
6
6
 
7
7
  - **Foreign deleted-paragraph resurrection guard**: Refuses non-empty same-paragraph edits when another author owns the paragraph-mark deletion and all existing paragraph content is deleted. The operation now returns `FOREIGN_PARAGRAPH_MARK_DELETION` with the owning author instead of emitting lifecycle-unsafe OOXML; atomic document operations roll back byte-for-byte. `validateRedlineOoxml` reports already-authored instances as warnings.
8
+ - **Source-truth whitespace replacement alignment (WP09a)**: Strict target resolution may equate ordinary spaces with NBSPs, but mutation offsets now always come from the resolved paragraph's exact accepted view. Space-equivalent word replacement hunks are refined to character-local edits, and multiple plain-text edits are applied right-to-left against refreshed live spans. This tracks NBSP-to-space substitutions exactly while preserving unchanged hyperlinks and prevents earlier run splits from invalidating later anchors.
9
+ - **Exact mismatch diagnostics**: `PATCH_ROUNDTRIP_MISMATCH` now includes the expected and actual code points at the first mismatch. Document-operation results also report bounded `targetTextMatch` diagnostics when target selection used equivalent whitespace.
10
+ - **Baseline-delta validation (WP09c)**: Restoration, operation, and package validation now compare issue multisets against the source. Unchanged legacy defects remain visible without blocking safe work, while any added occurrence or mutation-envelope error fails closed with `GENERATED_OOXML_INVALID` before an operation is reported as applied.
11
+ - **Revision identity sanitation (WP09d)**: New paragraph/list builders inherit effective `pPr`/`rPr` formatting without cloning historical revision descendants. Operation-level whole-document validation catches duplicate revision IDs and rolls back the operation savepoint before committing its receipt.
12
+ - **View-consistent restore targeting**: `restore` targets now default to the rejected view, where a wholly deleted paragraph's source text exists. Rejected-view inspection now computes fingerprints from rejected-view text and reports the view beside each paragraph. Explicit cross-view text or fingerprint mistakes retain strict refusal while returning an actionable view hint.
8
13
 
9
14
  ### New Features
10
15
 
16
+ - **Batch-start source binding (WP-04)**: Strong document-operation targets are compiled once against the immutable source DOM and carried through structural index/fingerprint drift with rollback-aware session identities. Independent edits no longer need manual bottom-up sorting; same-source writes, incompatible revision/format combinations, and unsafe capture fan-out fail before mutation with causal conflict codes. Unique references to paragraph text created elsewhere in the same batch become explicit internal capture dependencies.
17
+ - **Machine-actionable recovery (WP-05)**: Operation, package-facade, example-session, and compact CLI failures now carry recovery envelope version 1 with stable stage/category/action metadata, bounded corrective context, and authorization flags. Failed and partial mutation results include an explicit original-vs-output retry plan and operation indexes. `apply --require-complete` exits with code 3 for partial work while preserving legacy behavior without the flag.
18
+ - **Compact shell-agent protocol (WP-06)**: CLI contract version 5 adds `operations-stdin` and `agent-profile-v1`. `--operations -` accepts UTF-8 operation arrays/envelopes without a temporary operations file, while explicit `--profile agent` combines atomic rollback and complete-success exit behavior and reports its effective settings. `AGENTS.md` is now a 527-word routing card, and the 349-word `docs/AGENT_FAST_START.md` carries the ordinary edit contract. Ambient project configuration was deliberately deferred because it would save only the profile flag while adding hidden state.
19
+ - **Agent rollout audit (WP-07)**: The observational benchmark now compares canonical Node, legacy file-based CLI, compact stdin/profile CLI, and the development session example while verifying accepted/rejected text, comment preservation, independent batch permutations, recovery behavior, and cross-author attribution. The checked run records 82.75% fewer ordinary instruction words, one fewer shell tool turn (33.33%), and 82.22–89.96% smaller localized session requests than canonical Node envelopes without claiming unmeasured provider/model latency.
11
20
  - **Explicit paragraph restoration (`type: 'restore'`)**: Restores or counterproposes another reviewer's pending whole-paragraph deletion as a separately tracked sibling paragraph. The source deletion remains untouched; the restored paragraph receives its own inserted paragraph mark, content insertion, sanitized paragraph properties, and fresh `w14:paraId`. Single paragraphs and contiguous ranges are supported, with full Accept/Reject lifecycle verification and structured refusals at unsafe table-row, move, section-break, and terminal-paragraph boundaries.
21
+ - **Compact mutation CLI contract (WP09b)**: CLI contract version 3 removes `documentXml`, OOXML/package artifacts, full inspection data, and full validation issue arrays from normal `apply`, `accept`, `reject`, and `delete-comments` stdout. Mutation responses retain actionable errors, per-operation receipts, output durability fields, code/count validation summaries, and a derived `completion` flag that cannot report success for failed, partial, or unwritten work.
22
+ - **Word-native deleted-section editing (WP09e)**: An explicit rejected-view `insert` operation can split a foreign deletion at an exact anchor-relative offset into sibling `del(A) / ins(B) / del(A)` carriers. Contiguous paragraph restorations now follow their untouched deleted source block, matching Microsoft Word's ordering. Ambiguous anchors and unsupported structural split boundaries remain fail-closed.
12
23
 
13
24
  ## 0.5.1
14
25
 
package/README.md CHANGED
@@ -20,13 +20,45 @@ Converts AI-generated or programmatic text/markdown edits into valid Office Open
20
20
  - Zero host dependencies: works in Node.js, browsers, Deno, and similar JS runtimes with DOM parsing support
21
21
  ## Documentation Index
22
22
 
23
- | Document | Description |
24
- |---|---|
25
- | **[README.md](./README.md)** | Library overview, installation, quick start, and public API reference |
26
- | **[AGENTS.md](./AGENTS.md)** | AI coding agent quick reference, targeting rules, and complete CLI workflow |
23
+ | Document | Description |
24
+ |---|---|
25
+ | **[README.md](./README.md)** | Library overview, installation, quick start, and public API reference |
26
+ | **[docs/AGENT_FAST_START.md](./docs/AGENT_FAST_START.md)** | Compact ordinary-edit protocol for structured tools and shell-only agents |
27
+ | **[AGENTS.md](./AGENTS.md)** | Short repository launch card for task routing and contributor verification |
28
+ | **[docs/AGENT_KNOWLEDGE_BASE.md](./docs/AGENT_KNOWLEDGE_BASE.md)** | Full agent reference, CLI workflow, operation examples, error recovery, options, and gotchas |
27
29
  | **[ARCHITECTURE.md](./ARCHITECTURE.md)** | Contributor architecture, module responsibilities, end-to-end data flow, and contracts |
28
30
  | **[docs/TESTING.md](./docs/TESTING.md)** | Complete testing guide, test lanes, independent oracle validation, and Word visual review checklist |
29
- | **[CHANGELOG.md](./CHANGELOG.md)** | Release history, breaking changes, and migration notes |
31
+ | **[CHANGELOG.md](./CHANGELOG.md)** | Release history, breaking changes, and migration notes |
32
+
33
+ ## Repository Layout
34
+
35
+ The package exposes three levels of API:
36
+
37
+ | Level | Entry point | Use it for |
38
+ |---|---|---|
39
+ | Host-independent OOXML API | `index.js` | Paragraph/range transforms and exported OOXML utilities in browsers, Node.js, or another DOM-capable runtime |
40
+ | Standalone document XML runner | `services/standalone-operation-runner.js` | Applying operations to a complete `word/document.xml` string |
41
+ | Node/DOCX API and CLI | `node/index.js`, `bin/docx-redline.js` | Reading, changing, validating, and writing complete `.docx` ZIP packages |
42
+
43
+ Implementation folders have distinct roles: `core/` holds shared OOXML and
44
+ targeting primitives; `pipeline/` handles ingestion, diffing, markdown, lists,
45
+ and serialization; `engine/` performs reconciliation; `services/` coordinates
46
+ document operations and package artifacts; `node/` contains Node-only ZIP and
47
+ whole-document code. Tests are directly runnable `tests/*.mjs` files, while
48
+ `tests/helpers/` and `tests/fixtures/` contain support code and data.
49
+
50
+ Contributors and coding agents should start with the routing table in
51
+ [AGENTS.md](./AGENTS.md#pick-the-route) before exploring the tree. The full
52
+ dependency and ownership map is in [ARCHITECTURE.md](./ARCHITECTURE.md).
53
+
54
+ For document-operation JSON, choose operations by the desired output structure,
55
+ not by the everyday meaning of the type name. `redline` and `replace` provide
56
+ ordinary text replacement; `list-change` and `table-reconciliation` provide
57
+ structural intent; and ordinary `insert` is a compatibility alias of the
58
+ redline path unless it includes a rejected-view target and anchor. In every
59
+ ordinary text-bearing operation, `modified` is the complete desired content for
60
+ the target. See the [operation model](./docs/AGENT_KNOWLEDGE_BASE.md#operation-model-choose-by-output-shape)
61
+ and the [JSON schema](./docs/schemas/document-operations.schema.json).
30
62
 
31
63
  ## Install
32
64
 
@@ -128,15 +160,56 @@ const result = await document.applyOperations(operations, {
128
160
  const outputBuffer = result.toBuffer();
129
161
  ```
130
162
 
131
- The Node facade performs edits, artifact merges, package wiring, validation,
132
- and commit as one transaction. It defaults to strict targets and returns the
133
- untouched input with `written: false` on atomic failure. It is isolated from
134
- the root/browser dependency graph.
135
-
136
- Install `@xmldom/xmldom` alongside the package when using the Node facade or
137
- CLI; it remains an optional peer so browser consumers do not install a DOM shim.
138
-
139
- ### Agent CLI
163
+ The Node facade performs edits, artifact merges, package wiring, validation,
164
+ and commit as one transaction. It defaults to strict targets and returns the
165
+ untouched input with `written: false` on atomic failure. It is isolated from
166
+ the root/browser dependency graph.
167
+
168
+ Install `@xmldom/xmldom` alongside the package when using the Node facade or
169
+ CLI; it remains an optional peer so browser consumers do not install a DOM shim.
170
+
171
+ #### Example agent session wrapper (development only)
172
+
173
+ [`examples/agent-session-wrapper.mjs`](./examples/agent-session-wrapper.mjs)
174
+ demonstrates how a custom agent harness can keep one `DocxDocument` open, return
175
+ short revision-bound target handles, apply safe defaults once, and translate a
176
+ narrow edit request into canonical document operations:
177
+
178
+ ```js
179
+ import { readFile } from 'node:fs/promises';
180
+ import { createExampleAgentSession } from './examples/agent-session-wrapper.mjs';
181
+
182
+ const session = createExampleAgentSession(await readFile('contract.docx'), {
183
+ profile: { author: 'Editor' }
184
+ });
185
+ const inspection = session.inspect({ search: 'termination', around: 2 });
186
+ const clause = inspection.targets.find(target => target.role === 'match');
187
+ const result = await session.applyEdits([{
188
+ target: clause.handle,
189
+ replacements: [{
190
+ find: 'The Company may terminate',
191
+ replace: 'Either party may terminate'
192
+ }]
193
+ }]);
194
+ ```
195
+
196
+ This file is a testable sample, not a package export or supported alternate
197
+ mutation engine. It delegates to `@ansonlai/docx-redline-js/node`, is excluded
198
+ from the published package files, and is intended to help MCP servers, Claude
199
+ skills, OpenCode tools, and other custom harnesses design thin integrations.
200
+ The sample binds each handle to the inspected package revision and view, returns
201
+ new handles after successful mutations, and expands localized exact replacements
202
+ into complete desired paragraph text before delegating to the canonical redline
203
+ operation. Duplicate matches require an explicit `occurrence`; missing,
204
+ ambiguous, overlapping, and conflicting patches fail before document mutation.
205
+ From a source checkout, run `npm run benchmark:agent` to compare its native
206
+ execution and serialized request size with a canonical stateless Node workflow.
207
+ The example and its benchmark are excluded from the published package. The
208
+ benchmark explicitly does not claim to measure LLM reasoning or provider/tool
209
+ latency. Checked comparative results are in the
210
+ [agent protocol rollout audit](./docs/validation-reports/2026-09-12-agent-protocol-rollout.md).
211
+
212
+ ### Agent CLI
140
213
 
141
214
  ```bash
142
215
  docx-redline extract contract.docx --range 10:30
@@ -155,20 +228,32 @@ docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --n
155
228
  # Cross-author edit inside another reviewer's pending insertion
156
229
  docx-redline apply contract.docx --target "Another author's clause" --modified "Revised clause" --existing-revisions slice-cross-author --output reviewed.docx
157
230
 
158
- # High-assurance atomic batch
159
- docx-redline apply contract.docx --operations operations.json --atomic --output reviewed.docx
231
+ # High-assurance atomic batch with nonzero exit on any incomplete result
232
+ docx-redline apply contract.docx --operations operations.json --atomic --require-complete --output reviewed.docx
233
+
234
+ # Agent shell path: JSON is emitted by a serializer, not interpolated by the shell
235
+ node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --output reviewed.docx
160
236
  ```
161
237
 
162
- All commands emit JSON on stdout. `apply` defaults:
238
+ All commands emit JSON on stdout. `apply` defaults:
163
239
  - **Author**: Defaults to `'AI Redliner'` (or `DOCX_REDLINE_AUTHOR` environment variable).
164
240
  - **Output overwrite**: Destination files provided via `--output` overwrite by default. Pass `--no-overwrite` or `--no-clobber` to safeguard existing destination files. The source input is never overwritten unless `--in-place` is specified.
165
241
  - **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.
166
- - **Transactionality**: Defaults to `atomic: false` (applies valid operations and reports any failures). Pass `--atomic` for all-or-nothing rollback on any operation error.
242
+ - **Transactionality**: Defaults to `atomic: false` (applies valid operations and reports any failures). Pass `--atomic` for all-or-nothing rollback on any operation error.
243
+ - **Complete-success exit**: Pass `--require-complete` when a progressive `partial` result must exit with code `3`; errors exit with code `2`. Without the flag, partial results retain the legacy zero exit code, so always inspect `completion`.
244
+ - **Agent profile**: `--profile agent` explicitly enables atomic rollback and complete-success exit behavior while retaining strict targeting, validation, tracked changes, and `merge-same-author`. It reports the resolved `effectiveOptions`; explicit flags take precedence.
245
+ - **Operations from stdin**: `--operations -` reads the same array or `{ operations, expectedRevision }` envelope accepted from a file. Feed it from a JSON serializer or structured process API, not shell-interpolated legal text.
167
246
  - **Tracked changes**: Defaults to `generateRedlines: true`. Pass `--no-redlines` when clean direct text edits are desired.
168
- - **Inline edits**: Use `--target <text>` with `--modified <text>` or `--comment <text>` for quick one-liners without creating a JSON file.
247
+ - **Inline edits**: Use `--target <text>` with `--modified <text>` or `--comment <text>` for quick one-liners without creating a JSON file.
248
+ - **Compact mutation results**: `apply`, `accept`, `reject`, and `delete-comments` omit full OOXML/package payloads and inspection text from stdout. They report `written`, `outputPath`, per-operation results and receipts, compact validation counts, and a derived `completion` boolean. Use `validate` when full issue arrays are needed.
249
+
250
+ `docx-redline version` reports contract version 5 and the additive
251
+ `batch-start-source-binding`, `recovery-envelope-v1`, and
252
+ `require-complete-exit`, `operations-stdin`, and `agent-profile-v1`
253
+ capabilities. Wrappers should negotiate only the capabilities they use.
169
254
 
170
- See [the agent workflow in AGENTS.md](./AGENTS.md#agent-document-workflow-cli) and the
171
- [operation JSON Schema](docs/schemas/document-operations.schema.json).
255
+ See the [compact agent fast start](./docs/AGENT_FAST_START.md) and the
256
+ [operation JSON Schema](docs/schemas/document-operations.schema.json).
172
257
 
173
258
  ### Configuration (call once at startup)
174
259
 
@@ -202,9 +287,10 @@ Common result fields:
202
287
  | Field | Purpose |
203
288
  |-------|---------|
204
289
  | `status` | Operation status: `'ok'`, `'partial'`, `'no-op'`, or `'error'`. |
205
- | `error` | Present on failure; includes a stable `code` such as `PARSE_ERROR`, `TARGET_NOT_FOUND`, `PARTIAL_TARGET`, `EXISTING_REVISIONS`, `DIFF_TOKEN_LIMIT`, or `BATCH_OPERATION_FAILED`. |
206
- | `written` | CLI/facade boolean indicating whether the output file was successfully written to disk. |
207
- | `rolledBack` | Present and `true` when an atomic batch encountered an error and rolled back all changes. |
290
+ | `error` | Present on failure; retains a stable `code` and adds recovery envelope version, stage, category, bounded context, and a machine-readable recovery action. |
291
+ | `written` | CLI/facade boolean indicating whether the output file was successfully written to disk. |
292
+ | `completion` | CLI-only boolean that is `true` only when a destination was written, top-level status is neither error nor partial, and no operation result failed. |
293
+ | `rolledBack` | Present and `true` when an atomic batch encountered an error and rolled back all changes. |
208
294
 
209
295
  Word diffs are deterministic by default (no wall-clock timeout). Inputs above
210
296
  the safe ceiling of 262,144 unique diff tokens return `DIFF_TOKEN_LIMIT` with
@@ -216,11 +302,11 @@ silent text loss.
216
302
  During multi-round legal negotiations, a reviewer often needs to edit text that was previously inserted by another reviewer whose revision is still pending. Pass `existingRevisions: 'slice-cross-author'` (or `--existing-revisions slice-cross-author` via CLI) to edit inside another author's pending insertion without erasing their attribution or requiring prior acceptance:
217
303
 
218
304
  ```js
219
- const result = await applyRedlineToOxml(paragraphOoxml, originalText, modifiedText, {
220
- generateRedlines: true,
221
- author: 'Anson Lai',
222
- existingRevisions: 'slice-cross-author'
223
- });
305
+ const result = await applyRedlineToOxml(paragraphOoxml, originalText, modifiedText, {
306
+ generateRedlines: true,
307
+ author: 'Reviewer B',
308
+ existingRevisions: 'slice-cross-author'
309
+ });
224
310
  ```
225
311
 
226
312
  The engine applies Microsoft Word Desktop-native tracked change structures:
@@ -346,6 +432,12 @@ If a structural boundary prevents exact reconstruction, the transform returns
346
432
  Pure insertion-only slicing uses an exact character-local diff so repeated words
347
433
  cannot move an insertion to a different occurrence. Leading/trailing spaces,
348
434
  tabs, and non-breaking spaces are treated as real changes rather than no-ops.
435
+ For text-bearing replacements, the exact accepted-view text of the resolved
436
+ paragraph—not a space-normalized caller target—defines mutation offsets. Word-level replacement
437
+ hunks that differ only by ordinary spaces and NBSPs are refined to character
438
+ edits so unchanged hyperlinks and their relationship attributes stay in place.
439
+ Runner results expose bounded `resolvedTarget.targetTextMatch` code-point
440
+ diagnostics when equivalent whitespace was used to identify the target.
349
441
 
350
442
  ### Deep Imports
351
443
 
@@ -361,7 +453,17 @@ import {
361
453
  import { getParagraphText } from '@ansonlai/docx-redline-js/core/paragraph-targeting.js';
362
454
  ```
363
455
 
364
- Use `applyOperationsToDocumentXml(...)` for mixed batches. It stably runs comments before text-changing operations so replacements cannot invalidate their original anchors. Other operation types retain their relative order. Batch results retain each operation's original 1-based index and expose the actual `executionOrder`.
456
+ Use `applyOperationsToDocumentXml(...)` for mixed batches. It stably runs comments before text-changing operations so replacements cannot invalidate their original anchors. Other operation types retain their relative order. Batch results retain each operation's original 1-based index and expose the actual `executionOrder`.
457
+
458
+ Before mutation, the runner resolves strong source descriptors against the
459
+ immutable batch-start document and binds them to session-local source
460
+ identities. Independent edits therefore do not need to be manually sorted when
461
+ an earlier structural rewrite changes later paragraph indexes or fingerprints.
462
+ Targets that deliberately refer to uniquely created paragraph text are compiled
463
+ into an internal capture dependency. True overlap is not guessed: incompatible
464
+ writes to one source fail before mutation with `OVERLAPPING_SOURCE_TARGETS` or
465
+ `REVISION_ORDER_CONFLICT`, and mutating capture fan-out without distinct
466
+ selectors fails with `CAPTURE_FANOUT_CONFLICT`.
365
467
 
366
468
  Threaded replies use a comment operation with no body target:
367
469
 
@@ -384,12 +486,23 @@ serialization. Accuracy remains the controlling constraint: each operation has
384
486
  an internal savepoint so an error or no-op cannot leak a partial edit or consumed
385
487
  revision ID into later operations.
386
488
 
387
- Batches are atomic by default: any operation error returns the original
388
- `documentXml`, `hasChanges: false`, empty package artifacts, and
389
- `rolledBack: true`. The default `continueOnError: true` still attempts the full
390
- batch so `results` describes what would have applied. Callers that intentionally
391
- consume partial results must pass `{ atomic: false }`; use
392
- `{ continueOnError: false }` to stop after the first error.
489
+ Batches are progressive by default (`atomic: false`): valid operations commit
490
+ while failed operations remain unapplied and are reported in `results`. Pass
491
+ `{ atomic: true }` when any operation error must return the original
492
+ `documentXml`, `hasChanges: false`, empty package artifacts, and
493
+ `rolledBack: true`. The default `continueOnError: true` still attempts the full
494
+ batch so `results` describes every operation; use `{ continueOnError: false }`
495
+ to stop after the first error.
496
+
497
+ Every failed or partial mutation includes `retryPlan`. Its `base` is `original`
498
+ after rollback/no commit and `output` after a progressive partial commit;
499
+ `committedIndexes`, `failedIndexes`, and `unattemptedIndexes` identify the safe
500
+ replay scope. Errors use recovery envelope version 1 and always report
501
+ `sameArgumentsSafe: false`. Follow `error.recovery.action`; do not infer a retry
502
+ from prose. Authorization-sensitive actions, such as resolving comments, are
503
+ marked with `requiresUserAuthorization: true`. For `EXISTING_REVISIONS`, the
504
+ non-normalizing surgical recommendation is `slice-cross-author`; accepting or
505
+ rejecting prior revisions still requires explicit authority.
393
506
 
394
507
  Comment anchors use exact matching first, then a unique ASCII-space/NBSP
395
508
  equivalent match that preserves source offsets and text. Missing anchors return
@@ -533,11 +646,33 @@ const restoration = await applyOperationToDocumentXml(
533
646
  documentXml,
534
647
  {
535
648
  type: 'restore',
536
- target: { paragraphId: '1A2B3C4D' },
649
+ // restore targets default to the rejected view, where deleted text exists
650
+ target: { paragraphId: '1A2B3C4D', exactText: 'Original deleted paragraph text.' },
537
651
  modified: 'Restored or adjusted paragraph text.'
538
652
  },
539
653
  'Editor'
540
654
  );
655
+
656
+ // A single restoration follows its deleted source paragraph. A range
657
+ // restoration follows the complete deleted source block. Unchanged legacy
658
+ // validation defects are retained as baseline issues; newly generated errors
659
+ // fail closed before commit.
660
+ // inspect/extract descriptors report their revisionView, and each fingerprint
661
+ // is computed from the same view as exactText. Keep those fields together.
662
+
663
+ // To insert run-level text at a location visible only in the rejected view,
664
+ // provide explicit rejected-view intent and an exact anchor-relative offset.
665
+ const deletedTextInsertion = await applyOperationToDocumentXml(
666
+ documentXml,
667
+ {
668
+ type: 'insert',
669
+ target: { paragraphId: '1A2B3C4D', revisionView: 'rejected' },
670
+ anchor: { exactText: 'must pay', occurrence: 1, offset: 5 },
671
+ modified: '[clarification] ',
672
+ existingRevisions: 'slice-cross-author'
673
+ },
674
+ 'Editor'
675
+ );
541
676
 
542
677
  // applyOperationToDocumentXml(...) returns a full w:document payload.
543
678
  zip.file('word/document.xml', opResult.documentXml);
@@ -623,8 +758,10 @@ invariant with a fresh seed. See [Release validation in docs/TESTING.md](./docs/
623
758
 
624
759
  ## Architecture & Contributing
625
760
 
626
- - **[ARCHITECTURE.md](./ARCHITECTURE.md)**: Detailed module layout, end-to-end reconciliation flow, and contributor fast orientation.
627
- - **[AGENTS.md](./AGENTS.md)**: Concise quick reference for AI coding agents and CLI automation.
761
+ - **[ARCHITECTURE.md](./ARCHITECTURE.md)**: Detailed module layout, end-to-end reconciliation flow, and contributor fast orientation.
762
+ - **[AGENTS.md](./AGENTS.md)**: Fast-start routing and operational guardrails for AI coding agents.
763
+ - **[docs/AGENT_FAST_START.md](./docs/AGENT_FAST_START.md)**: Minimal ordinary-edit contract for agent integrations.
764
+ - **[docs/AGENT_KNOWLEDGE_BASE.md](./docs/AGENT_KNOWLEDGE_BASE.md)**: Full agent reference for APIs, operations, CLI automation, recovery, and gotchas.
628
765
  - **[docs/TESTING.md](./docs/TESTING.md)**: Comprehensive testing model, test lanes, independent oracle checks, and visual review checklist.
629
766
  - **[CHANGELOG.md](./CHANGELOG.md)**: Version history, migration guides, and deprecation schedules.
630
767
 
@@ -128,7 +128,7 @@ export function inspectForeignDeletedParagraphTarget(paragraph, mutationAuthor)
128
128
  };
129
129
  }
130
130
 
131
- export function getParagraphRestorationRefusal(paragraph) {
131
+ export function getParagraphRestorationRefusal(paragraph, options = {}) {
132
132
  const pPr = directChild(paragraph, 'pPr');
133
133
  if (directChild(pPr, 'sectPr')) {
134
134
  return {
@@ -169,13 +169,15 @@ export function getParagraphRestorationRefusal(paragraph) {
169
169
  };
170
170
  }
171
171
 
172
- let sibling = paragraph?.nextSibling || null;
173
- while (sibling && (sibling.nodeType !== 1 || localNameOf(sibling) !== 'p')) sibling = sibling.nextSibling;
174
- if (!sibling) {
175
- return {
176
- code: 'UNSAFE_PARAGRAPH_PLACEMENT',
177
- message: 'Refusing to restore a deleted paragraph without a following paragraph in the same structural container.'
178
- };
172
+ if (options.requireFollowingParagraph !== false) {
173
+ let sibling = paragraph?.nextSibling || null;
174
+ while (sibling && (sibling.nodeType !== 1 || localNameOf(sibling) !== 'p')) sibling = sibling.nextSibling;
175
+ if (!sibling) {
176
+ return {
177
+ code: 'UNSAFE_PARAGRAPH_PLACEMENT',
178
+ message: 'Refusing to restore a deleted paragraph without a following paragraph in the same structural container.'
179
+ };
180
+ }
179
181
  }
180
182
 
181
183
  return null;
@@ -424,9 +424,14 @@ export function resolveTargetParagraph(xmlDoc, options = {}) {
424
424
  );
425
425
  }
426
426
  if (descriptor.fingerprint && descriptor.fingerprint !== actualFingerprint) {
427
+ const alternateView = revisionView === 'rejected' ? 'accepted' : 'rejected';
428
+ const alternateFingerprint = createParagraphFingerprint(byId, { revisionView: alternateView });
429
+ const viewHint = descriptor.fingerprint === alternateFingerprint
430
+ ? ` The supplied fingerprint matches the ${alternateView} view; set target.revisionView to "${alternateView}" or use a fingerprint extracted from the ${revisionView} view.`
431
+ : '';
427
432
  throw createTargetError(
428
433
  'TARGET_FINGERPRINT_MISMATCH',
429
- `Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint.`,
434
+ `Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint.${viewHint}`,
430
435
  cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
431
436
  );
432
437
  }
@@ -438,9 +443,16 @@ export function resolveTargetParagraph(xmlDoc, options = {}) {
438
443
  );
439
444
  }
440
445
  if (cleanTargetText && actualText !== normalizeWhitespaceForTargeting(cleanTargetText)) {
446
+ const alternateView = revisionView === 'rejected' ? 'accepted' : 'rejected';
447
+ const alternateText = normalizeWhitespaceForTargeting(
448
+ extractCanonicalParagraphText(byId, { revisionView: alternateView })
449
+ );
450
+ const viewHint = alternateText === normalizeWhitespaceForTargeting(cleanTargetText)
451
+ ? ` The supplied text matches the ${alternateView} view; set target.revisionView to "${alternateView}".`
452
+ : '';
441
453
  throw createTargetError(
442
454
  'TARGET_TEXT_MISMATCH',
443
- `Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text.`,
455
+ `Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text.${viewHint}`,
444
456
  cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
445
457
  );
446
458
  }
@@ -65,19 +65,22 @@ function parseOoxmlForValidation(oxml) {
65
65
  * Issue severities: 'error' issues indicate output Word may repair or
66
66
  * mis-resolve; 'warning' issues are suspicious but tolerated by Word.
67
67
  *
68
- * @param {string} oxml - OOXML string (fragment, document, or package scope)
68
+ * @param {string|Document|Element} oxml - OOXML string or an already-parsed DOM
69
69
  * @returns {{ valid: boolean, issues: Array<{ code: string, severity: 'error'|'warning', message: string }> }}
70
70
  */
71
71
  export function validateRedlineOoxml(oxml) {
72
72
  const issues = [];
73
73
  const addIssue = (code, severity, message) => issues.push({ code, severity, message });
74
74
 
75
- if (typeof oxml !== 'string' || oxml.trim() === '') {
76
- addIssue('PARSE_ERROR', 'error', 'Input is not a non-empty OOXML string.');
75
+ const isDomNode = oxml && typeof oxml === 'object' && (oxml.nodeType === 9 || oxml.nodeType === 1);
76
+ if (!isDomNode && (typeof oxml !== 'string' || oxml.trim() === '')) {
77
+ addIssue('PARSE_ERROR', 'error', 'Input is not non-empty OOXML or a parsed XML DOM.');
77
78
  return { valid: false, issues };
78
79
  }
79
80
 
80
- const { doc, error } = parseOoxmlForValidation(oxml);
81
+ const { doc, error } = isDomNode
82
+ ? { doc: oxml.nodeType === 9 ? oxml : oxml.ownerDocument }
83
+ : parseOoxmlForValidation(oxml);
81
84
  if (!doc) {
82
85
  addIssue('PARSE_ERROR', 'error', `OOXML does not parse as XML: ${error}`);
83
86
  return { valid: false, issues };
@@ -6,6 +6,26 @@ import {
6
6
  } from './types.js';
7
7
  import { isWordElement } from './word-xml.js';
8
8
 
9
+ const REVISION_HISTORY_ELEMENTS = new Set([
10
+ 'ins', 'del', 'moveFrom', 'moveTo', 'pPrChange', 'rPrChange',
11
+ 'tblPrChange', 'trPrChange', 'tcPrChange', 'sectPrChange'
12
+ ]);
13
+
14
+ /**
15
+ * Clones effective paragraph/run properties without copying tracked history
16
+ * into a newly created paragraph or run.
17
+ */
18
+ export function clonePropertiesWithoutRevisionHistory(root) {
19
+ if (!root) return null;
20
+ const clone = root.cloneNode(true);
21
+ const candidates = [clone, ...Array.from(clone.getElementsByTagName?.('*') || [])];
22
+ for (const node of candidates.reverse()) {
23
+ if (!REVISION_HISTORY_ELEMENTS.has(String(node.localName || node.nodeName || '').replace(/^.*:/, ''))) continue;
24
+ node.parentNode?.removeChild(node);
25
+ }
26
+ return clone;
27
+ }
28
+
9
29
  /**
10
30
  * Assigns fresh document-scoped IDs to w:rPrChange elements in a cloned
11
31
  * run-properties subtree. This preserves formatting-revision metadata while
@@ -32,6 +52,7 @@ export function refreshRunPropertyChangeIds(root, allocator = null) {
32
52
  } else {
33
53
  node.setAttribute('w:id', nextId);
34
54
  }
55
+ resolvedAllocator._receiptCollector?.recordRevision(Number(nextId), 'rPrChange');
35
56
  }
36
57
 
37
58
  return root;
@@ -0,0 +1,23 @@
1
+ function issueKey(issue) {
2
+ return `${issue?.source || ''}\u001f${issue?.severity || ''}\u001f${issue?.code || ''}\u001f${issue?.message || ''}`;
3
+ }
4
+
5
+ /** Subtracts baseline validation issues as a multiset, preserving duplicates. */
6
+ export function subtractValidationIssueMultiset(outputIssues = [], baselineIssues = []) {
7
+ const remaining = new Map();
8
+ for (const issue of baselineIssues || []) {
9
+ const key = issueKey(issue);
10
+ remaining.set(key, (remaining.get(key) || 0) + 1);
11
+ }
12
+ return (outputIssues || []).filter(issue => {
13
+ const key = issueKey(issue);
14
+ const count = remaining.get(key) || 0;
15
+ if (count === 0) return true;
16
+ remaining.set(key, count - 1);
17
+ return false;
18
+ });
19
+ }
20
+
21
+ export function validationErrors(issues = []) {
22
+ return (issues || []).filter(issue => issue?.severity === 'error');
23
+ }