@ansonlai/docx-redline-js 0.8.3 → 0.9.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 (71) hide show
  1. package/ARCHITECTURE.md +221 -12
  2. package/CHANGELOG.md +803 -586
  3. package/README.md +1018 -901
  4. package/core/current-properties.js +136 -0
  5. package/core/paragraph-text.js +211 -209
  6. package/core/types.js +9 -3
  7. package/core/word-revision-semantics.js +256 -0
  8. package/dist/docx-redline-js.esm.js +4551 -967
  9. package/dist/docx-redline-js.esm.js.map +4 -4
  10. package/dist/docx-redline-js.esm.min.js +112 -106
  11. package/dist/docx-redline-js.esm.min.js.map +4 -4
  12. package/dist/docx-redline.bundle.cjs +4690 -1106
  13. package/dist/docx-redline.bundle.cjs.map +4 -4
  14. package/dist/docx-redline.bundle.js +4690 -1106
  15. package/dist/docx-redline.bundle.js.map +4 -4
  16. package/docs/AGENT_FAST_START.md +2 -0
  17. package/docs/AGENT_KNOWLEDGE_BASE.md +23 -4
  18. package/docs/SKILL_AUTHORING.md +165 -150
  19. package/docs/TESTING.md +1142 -1059
  20. package/docs/schemas/document-operations.schema.json +221 -168
  21. package/document/docx-document.js +1022 -1010
  22. package/engine/oxml-engine.js +567 -560
  23. package/engine/paragraph-mark-revisions.js +292 -0
  24. package/engine/reconstruction-mapper.js +312 -311
  25. package/engine/reconstruction-mode.js +183 -183
  26. package/engine/reconstruction-writer.js +67 -10
  27. package/engine/rpr-helpers.js +42 -0
  28. package/engine/run-builders.js +0 -73
  29. package/engine/surgical-diff-application.js +604 -605
  30. package/engine/table-mode.js +63 -63
  31. package/index.d.ts +51 -1
  32. package/index.js +47 -3
  33. package/node/cli-help.js +1 -0
  34. package/node/cli.js +992 -974
  35. package/orchestration/list-structural-fallback.js +67 -9
  36. package/package.json +1 -1
  37. package/pipeline/diff-engine.js +130 -1
  38. package/pipeline/ingestion-export.js +227 -243
  39. package/pipeline/ingestion-paragraph.js +93 -98
  40. package/pipeline/list-generation.js +415 -419
  41. package/pipeline/pipeline.js +206 -204
  42. package/pipeline/serialization.js +111 -94
  43. package/scripts/export-validation-fixtures.mjs +19 -6
  44. package/scripts/generate-word-revision-semantics-matrix.mjs +134 -0
  45. package/scripts/lib/word-coverage-catalogue.mjs +7 -2
  46. package/scripts/render-multilevel-bullet-visual.ps1 +2 -2
  47. package/scripts/word-com-corpus-visual-suite.ps1 +2 -2
  48. package/scripts/word-com-differential.ps1 +443 -335
  49. package/scripts/word-com-visual-suite.ps1 +2 -2
  50. package/scripts/word-revision-semantics-matrix.ps1 +108 -0
  51. package/services/batch-operation-orchestrator.js +26 -0
  52. package/services/capture-engine.js +228 -226
  53. package/services/document-operation-applier.js +215 -0
  54. package/services/document-operation-contract.js +628 -537
  55. package/services/document-operation-mutations.js +789 -46
  56. package/services/error-recovery.js +17 -0
  57. package/services/list-conversion-diagnostics.js +296 -0
  58. package/services/list-format-change.js +586 -0
  59. package/services/list-format-mutation.js +416 -0
  60. package/services/list-format.js +305 -0
  61. package/services/numbering-allocation.js +642 -0
  62. package/services/numbering-helpers.js +354 -524
  63. package/services/operation-batch-compiler.js +612 -436
  64. package/services/operation-heuristics.js +197 -173
  65. package/services/operation-preflight.js +1 -0
  66. package/services/receipt-collector.js +288 -288
  67. package/services/revision-comment-management.js +634 -594
  68. package/services/revision-fidelity-verifier.js +498 -0
  69. package/services/standalone-docx-plumbing.js +627 -631
  70. package/services/standalone-operation-runner.d.ts +606 -427
  71. package/services/table-reconciliation.js +103 -49
package/CHANGELOG.md CHANGED
@@ -1,586 +1,803 @@
1
- # Changelog
2
-
3
- ## 0.8.3
4
-
5
- ### Fixed
6
-
7
- - **Reject All no longer leaves an empty paragraph after an inserted paragraph:** `modified: "<original>\n<new>"` on a plain
8
- paragraph did not track the new paragraph's mark as inserted. It now does.
9
- - **Reject All no longer merges source paragraphs after a list range replacement:** replacing a multi-paragraph list range
10
- (`target` + `targetEnd`) collapsed the sources into one deleted paragraph, so Reject All gave results such as
11
- `Bullet Root ABullet Insertion Anchor`, `AlphaGamma` for an empty middle paragraph, and lost both paragraphs of an
12
- all-empty range. Each source paragraph, empty ones included, now keeps its own tracked-deleted paragraph. Confirmed in Word.
13
- - **Inspection no longer reports historical paragraph properties as current:** `list`, `styleId` and `headingLevel` (and list
14
- targeting) could read `numPr`, `pStyle` or `outlineLvl` stored under `w:pPrChange`. Only the paragraph's direct `w:pPr`
15
- is read now.
16
- - **`openDocx` list range replacement no longer renumbers or re-points lists:** deleted source paragraphs were remapped to a
17
- new generic `numId` (Reject All restored them with the wrong list type), new items started a separate list (untouched
18
- following items restarted at 1), and unused numbering definitions were added. Deleted source paragraphs now keep their
19
- `numPr`, and a same-kind replacement reuses the source `numId`, leaving `numbering.xml` unchanged. Reuse applies when the
20
- range uses one `numId` and the generated level formats match; format-changing edits still allocate new definitions, and
21
- definitions created earlier in the same batch are not reused. Confirmed in Word.
22
- - **Explicit list start values are applied:** generated list fragments without `xmlns:w` failed to parse in
23
- `orchestration/list-structural-fallback.js`, so `7. Header` lost its `startOverride` and xmldom logged parse warnings.
24
-
25
- ### Tests
26
-
27
- - New `tests/list_reject_fidelity_tests.mjs` and `tests/facade_list_numbering_tests.mjs`, with the Word-authored fixture
28
- `tests/fixtures/agentic-lists/nested-lists-source.docx`; extended inspection and list structural fallback suites.
29
-
30
- ### Known limitations
31
-
32
- - Plain paragraph to list conversion and list format changes are not yet supported canonically; see
33
- `docs/plans/2026-09-30-canonical-list-operations.md`.
34
- - `1. Header` to `1. Header` on a bare `document.xml` (not a packaged `.docx`) can fail with `RECEIPT_RECONCILIATION_FAILED`.
35
-
36
- ## 0.8.2
37
-
38
- ### Fixed
39
-
40
- - **Replacement next to a hyperlink no longer pulls adjacent plain text into the link** ([#3](https://github.com/AnsonLai/docx-redline-js/issues/3)):
41
- replacing `example.org` with `example.net` (localized `replacements` or full `modified`) in a hyperlink followed by a
42
- plain `.` run produced a link showing `example.net.` after Accept All. The reconstruction-mode word diff treated
43
- `example.org.` as one token spanning the hyperlink edge. Where a replacement's shared prefix or suffix reaches a wrapper
44
- boundary inside the deleted text, it is now kept unchanged.
45
- - **Reject All no longer reorders text around a manual line break** ([#4](https://github.com/AnsonLai/docx-redline-js/issues/4)):
46
- replacing `tabbed` with `aligned` in `<tab>tabbed<w:br/>Line with ` rejected to `<tab>tabbedLine<break> with `, moving
47
- deleted text across the break. Break and note-reference placeholders are now standalone word-diff tokens (new internal
48
- `atomicChars` option), and change groups are split at references present on both sides. Covered by `tests/reconstruction_boundary_fidelity_tests.mjs`.
49
-
50
- ## 0.8.1
51
-
52
- ### Added
53
-
54
- - **Header and footer editing:** add `part` to an operation (a selector `{ kind, type?, section? }` or a part path) to edit a
55
- header or footer; `inspect().headersFooters` lists the parts with their text, the sections they apply to (including
56
- inherited ones) and whether they contain fields. Body edits are unchanged and headers/footers are opt-in. Fields such as
57
- PAGE are atomic (`FIELD_EDIT_REFUSED`), comments are refused (`COMMENT_IN_HEADER_FOOTER`), bad selectors fail closed
58
- (`PART_NOT_FOUND`, `PART_AMBIGUOUS`), atomic batches roll back across body and parts, revision ids stay unique across the
59
- package, receipts name the part, `accept`/`reject` also resolve revisions inside headers and footers, and
60
- `validateDocxPackage` checks header/footer references, relationships and content types.
61
- - **Resolve and reopen comment threads:** `doc.resolveComment(id, { resolved })` and the batch operation
62
- `{ "type": "comment_resolve", "commentId": "0", "resolved": true }`, so the CLI can resolve through `apply --operations`.
63
- Word resolves whole threads, so the root and every reply change together, whichever id is given. Only
64
- `word/commentsExtended.xml` changes; `document.xml` stays byte-identical. Unknown ids fail with `COMMENT_NOT_FOUND`
65
- (also in `preflight`).
66
- - **Delete comments by id:** `doc.deleteComments({ ids })` and `docx-redline delete-comments --comment-id 3,4`. Deleting a
67
- thread root also deletes its replies; unknown ids fail closed with `COMMENT_NOT_FOUND`.
68
- - CLI capabilities `comment-resolve-operation-v1`, `delete-comments-by-id-v1` and `header-footer-parts-v1` (additive; the
69
- contract version is unchanged).
70
- - `services/package-parts.js`, a registry of package part paths, content types and relationship types checked against
71
- Word-saved fixtures (`tests/fixtures/word-authored/`), and `npm run test:word:package`, which opens generated outputs in
72
- real Word with repair disabled and checks comment counts, thread parents, resolved state and header/footer revisions
73
- through Word's object model.
74
-
75
- ### Fixed
76
-
77
- - **Word "unreadable content" prompt on commented documents:** `word/commentsExtended.xml` was relabeled
78
- `application/vnd.ms-word.commentsExtended+xml` on every save of any document that already had comments.
79
- Word expects `application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml` and
80
- offered to repair the file. The constant is corrected, an edit that does not change comment threading no
81
- longer rewrites the extended part or its content type/relationship, and files already damaged by earlier
82
- versions are repaired on the next save (`repairKnownContentTypes`, also exported). `validateDocxPackage`
83
- now rejects the wrong type instead of accepting it. `tests/package_content_type_contract_tests.mjs` checks this
84
- against a Word-authored content-type table on every OS.
85
- - **Comment threads keyed on the last paragraph:** Word joins `comments.xml` to `commentsExtended.xml` and
86
- `commentsIds.xml` through the `w14:paraId` of a comment's LAST paragraph. Inspection, package validation,
87
- replies and delete-cascade used the first, so Word's own multi-paragraph threaded documents failed
88
- validation and replies to them attached to the wrong key.
89
- - **Edits no longer drop comment anchors:** a mid-paragraph edit of a commented paragraph dropped the trailing
90
- `commentRangeEnd` and `commentReference` (markers at the very end of the paragraph text were only emitted when the
91
- last diff part was an insert), leaving the comment unanchored and failing package validation. Found by
92
- opening our output in real Word; covered by `tests/comment_marker_preservation_tests.mjs`.
93
- - **Replies are now visible in Word:** `comment_reply` only wrote `comments.xml`/`commentsExtended.xml`, and Word
94
- does not show a reply that has no body markers. Replies now get their own `commentRangeStart`/`commentRangeEnd`/
95
- `commentReference` next to the thread's, laid out like Word writes them. A reply to a reply joins the root thread.
96
- A parent with no body anchor fails with `PARENT_ANCHOR_NOT_FOUND`.
97
- - **Replies to a resolved thread are resolved too**, as Word writes them (`reply-to-resolved.docx` fixture). They were
98
- written open, leaving a thread that was half resolved.
99
- - `commentsIds.xml` and `commentsExtensible.xml` are kept consistent with `comments.xml` on add/delete when present.
100
- - `deleteComments` and `resolveRevisions` report `artifactsChanged` from a real diff of the package (they listed a fixed
101
- set of parts and missed `commentsIds.xml`, `commentsExtensible.xml` and `[Content_Types].xml`).
102
-
103
- ## 0.8.0
104
-
105
- ### Universal Document Facade & Zero-Node Architecture
106
-
107
- - **Universal `DocxDocument` & `openDocx`:** Implemented complete-DOCX lifecycle operations
108
- (`openDocx`, `doc.inspect()`, `doc.applyOperations()`, `doc.resolveRevisions()`, `doc.deleteComments()`,
109
- `doc.toUint8Array()`, `doc.getRevisionToken()`) on standard `Uint8Array` in `document/docx-document.js`,
110
- re-exported directly from `index.js`. Enables complete-document redline workflows in any JavaScript
111
- runtime without Node.js dependencies.
112
- - **Pure-JS ZIP container (`fflate`):** Replaced `node:zlib` with lightweight, zero-dependency `fflate`
113
- in `document/zip-archive.js`, providing cross-runtime raw DEFLATE / INFLATE compression and decompression
114
- with full OOXML archive compatibility.
115
- - **Pure-JS FIPS 180-4 SHA-256:** Created zero-dependency, synchronous SHA-256 implementation in
116
- `core/sha256.js`, removing `node:crypto` from package revision token calculations while retaining
117
- exact deterministic hash parity with standard NIST vectors.
118
- - **Transparent XML DOM fallback:** Added `resolveDomParserConstructor()` and
119
- `resolveXmlSerializerConstructor()` in `adapters/xml-adapter.js`, automatically utilizing native
120
- `globalThis.DOMParser`/`globalThis.XMLSerializer` when present and falling back transparently to
121
- `@xmldom/xmldom` in headless, server, edge, and sandbox environments without requiring manual provider setup.
122
- - **Zero-dependency sandbox bundles:** Configured `scripts/build.mjs` with `esbuild` (`platform: 'neutral'`)
123
- to produce single-file, zero-dependency standalone bundles in both ESM (`dist/docx-redline.bundle.js`)
124
- and CommonJS (`dist/docx-redline.bundle.cjs`), exported as `@ansonlai/docx-redline-js/bundle` for direct
125
- use in workflow engines (e.g., n8n Code nodes), Cloudflare Workers, AWS Lambda, Deno, and QuickJS sandboxes.
126
- - **Strict runtime boundary enforcement:** Removed `node:zlib` and `node:crypto` from allowed external
127
- imports in `tests/no_word_api_index_check.mjs`. Automated sandbox isolation tests
128
- (`tests/sandbox_bundle_isolation_tests.mjs`) verify zero Node built-in imports and full document
129
- reconciliation in restricted VM contexts without `process`, `Buffer`, or `require`.
130
- - **Backward compatibility:** Preserved full API and `Buffer` compatibility for `@ansonlai/docx-redline-js/node`
131
- consumers and `doc.toBuffer()`.
132
-
133
- ## 0.7.2
134
-
135
- ### Agent Surface Simplification
136
-
137
- - **Extraction-first golden path:** Re-established the single deterministic two-turn
138
- "extract-then-apply" sequence as the default agent workflow across `AGENTS.md`,
139
- `docs/AGENT_FAST_START.md`, and `docs/SKILL_AUTHORING.md`. Eliminates the
140
- upfront "choice tax" and model deliberation overhead.
141
- - **Localized replacements after inspection:** Decoupled localized patching from
142
- speculation. Agents inspect the target paragraph first, then supply compact
143
- `replacements` instructions, achieving 60.6%–90.8% payload reductions without
144
- speculative ambiguity risk.
145
- - **Computed success gate:** Consolidated standard success verification onto
146
- `completion: true` and non-null `outputPath`, removing multi-field verification
147
- checklists from primary prompts while retaining full `change` audit evidence.
148
- - **Advanced features relocated:** Speculative 1-turn apply, directional context
149
- windows (`--context-range`), and anchor diagnostics are relocated to
150
- `docs/AGENT_KNOWLEDGE_BASE.md` and CLI help as opt-in harness policies.
151
- - **Observed-state restoration:** Restructured restoration as a clean request-
152
- or hint-driven branch (`--view rejected` or `selection.hint`) rather than an
153
- upfront classification decision.
154
- - **Empirical surface evaluation:** Added synthetic replay benchmark
155
- (`npm run benchmark:agent-surface`) and validation report documenting 100%
156
- first-attempt task correctness and payload savings across three workflow policies.
157
-
158
- ## 0.7.1
159
-
160
- ### Reliability and Discovery Refinements
161
-
162
- - Localized replacement matching now tries exact case-sensitive text first,
163
- then a narrow ASCII-space/non-breaking-space equivalent match. Ambiguous
164
- equivalent matches still fail closed, and unchanged boundary NBSPs are
165
- retained to avoid incidental whitespace redlines.
166
- - Added a narrow single-paragraph restore shortcut. A restore with a strong
167
- rejected-view target may omit `modified` for verbatim restoration or provide
168
- exact `replacements`; the CLI exposes the same path through `apply --restore
169
- --target-id ID`. Ranges, captures, weak targets, and inferred companion
170
- content remain excluded.
171
- - Fixed rejected-view restore receipts so `resolvedTarget` text, fingerprint,
172
- revision view, and match diagnostics describe the view actually used to
173
- resolve the target.
174
- - Fixed alternate-view search hints to honor `--revised`, `--table`, and
175
- `--body`, and retained compact revision authors plus `deleted`/`inserted` cues
176
- in `extract` output so empty revision-view paragraphs are self-explanatory.
177
-
178
- ## 0.7.0
179
-
180
- ### Agent Performance and Localized Editing
181
-
182
- - **Localized exact replacements:** `redline`/`replace` operations may provide
183
- `replacements: [{ find, replace, occurrence? }]` instead of a complete
184
- `modified` paragraph. The compiler resolves the accepted-view source once,
185
- applies non-overlapping exact replacements simultaneously, synthesizes the
186
- canonical desired paragraph, and delegates to the unchanged OOXML engine.
187
- - **One-turn speculative apply:** `docx-redline apply --find ... --replace ...`
188
- can resolve a globally unique source literal without preliminary extraction.
189
- `--search` plus signed `--context-range START:END` scopes visible headings and
190
- nearby content; `--around N` is symmetric shorthand. Longer, distinctive
191
- anchors are recommended because generic or repeated phrases widen the unioned
192
- scope and may cost an ambiguity-recovery turn.
193
- - **Fail-closed selection:** Missing anchors and patch sources, multiple eligible
194
- paragraphs, ambiguous occurrences, overlaps, conflicts, and unsupported
195
- localized shapes refuse mutation. A failed speculative command never writes
196
- or truncates its output path.
197
- - **Verified change evidence:** Localized operation results include a compact
198
- `change` object with target location, contextual anchor/range, exact
199
- replacement entries, bounded before/after excerpts, commit disposition, and
200
- post-mutation accepted-view verification. Atomic rollback marks the evidence
201
- non-committed and `rolled_back`.
202
- - **CLI contract version 8:** Adds `localized-replacements-v1`,
203
- `speculative-search-apply-v1`, and `localized-change-summary-v1`. Skills and
204
- harnesses should pin version 0.7.0 and negotiate only the capabilities used.
205
- - **Measured workflow reduction:** The checked benchmark records 50% fewer tool
206
- turns and a 64.77%–78.98% smaller deterministic request-token proxy across
207
- three literal-edit cases. Median native engine/I/O time remained effectively
208
- neutral; provider reasoning and network latency were not estimated.
209
-
210
- ### Compatibility
211
-
212
- - Existing `modified` operations, strong targets, operations files/stdin,
213
- tracked-change generation, package validation, rollback, comments,
214
- Accept/Reject, and revision policies remain supported.
215
- - Localized replacement fields and successful `change` evidence are additive.
216
- Wrappers that adopt them must require CLI contract 8; integrations using only
217
- earlier capabilities can continue using their existing operation shapes.
218
- - The implementation remains OOXML-only and adds no runtime dependency or Word,
219
- Office automation, COM, or proprietary document API requirement.
220
-
221
- ## 0.6.0–0.6.2
222
-
223
- ### Safety Fixes
224
-
225
- - **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.
226
- - **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.
227
- - **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.
228
- - **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.
229
- - **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.
230
- - **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.
231
-
232
- ### New Features
233
-
234
- - **Self-describing and bounded agent discovery (WP-00–WP-02)**: CLI contract version 6 adds command-specific JSON help, canonical redline/comment/restore examples, case-insensitive contextual search with `--around`/`--context`/`-C`, direct-hit pagination through `--limit` and `--after`, and deliberate `--all` opt-out. Unscoped inspection is capped at 20 direct records and a 48 KiB soft budget without truncating any returned `exactText`; extraction now distinguishes matches from context and prioritizes human-facing provision/heading references over machine paragraph ordinals.
235
- - **Composable agent safety and compact output (WP-03–WP-04)**: CLI contract version 7 replaces the atomic-coupled agent profile capability with `agent-safety-profile-v2`. `--profile agent` now enforces complete-success exits while preserving progressive execution and the ordinary revision policy; `--atomic` and `--existing-revisions` compose explicitly. Compact CLI JSON keeps receipts only in the authoritative top-level array, omits exact-match diagnostics, summarizes successful equivalent-whitespace matches, and supports one-line `--compact` output without changing Node or standalone-runner results.
236
- - **Packaged skill-authoring contract (WP-05)**: `docs/SKILL_AUTHORING.md` separates automation invariants from transaction/revision policy, file-versus-stdin transport, reviewer preferences, recovery-envelope behavior, and human-facing presentation. It requires contract/capability negotiation, focused contextual extraction, and library delegation while retaining the visible `AI Redliner` fallback without a new author guard.
237
- - **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.
238
- - **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.
239
- - **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.
240
- - **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.
241
- - **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.
242
- - **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.
243
- - **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.
244
-
245
- ## 0.5.1
246
-
247
- ### Highlights & New Features
248
-
249
- - **Cross-Author Revision Slicing (`existingRevisions: 'slice-cross-author'`)**: Adds native support for editing text inside another reviewer's pending tracked insertion without erasing their attribution or requiring prior acceptance.
250
- - **Word-Native Insertion Slicing**: When inserting text inside another author's pending `<w:ins>`, the engine splits the outer carrier into sibling `<w:ins>` elements at the paragraph level (`[ins(A), ins(B), ins(A)]`), ensuring strict ECMA-376 schema compliance without illegal `ins/ins` nesting.
251
- - **Word-Native Deletion Slicing**: When deleting text inside another author's pending `<w:ins>`, the engine nests `<w:del>` directly inside `<w:ins>` per ECMA-376 Part 1 `CT_RunTrackChange` Section 17.13.5.21 and Microsoft Word Desktop 365 native behavior.
252
- - **Boundary & Straddle Deletions**: Deletions straddling baseline text and pending insertions cleanly partition into separate top-level and nested `<w:del>` containers while sharing unified event/author attribution.
253
- - **Multi-Author Stacked Revisions**: Multiple reviewers can independently delete or insert content within the same carrier insertion without cross-author interference.
254
- - **Cascading & Coalescing Lifecycles**: Full round-trip lifecycle parity with Microsoft Word Desktop:
255
- - Rejecting Author A cleanly discards Author A's insertion and any dependent nested deletions by Author B.
256
- - Accepting Author A unwraps the insertion into baseline text while leaving Author B's deletions pending against the baseline.
257
- - Rejecting Author B restores Author B's deleted text within Author A's insertion, and coalesces adjacent split `<w:ins>` fragments back into a single continuous carrier.
258
- - **CLI & Facade Integration**: Fully exposed via the `docx-redline` CLI (`--existing-revisions slice-cross-author`), Node facade (`openDocx`), and batch operation runner.
259
-
260
- ### Non-breaking Changes & Improvements
261
-
262
- - **Validation Update (`validateRedlineOoxml`)**: Refined `NESTED_REVISION` checks to permit direct `<w:ins><w:del>...</w:del></w:ins>` nesting (standard ECMA-376 and Word Desktop behavior), while continuing to strictly reject `ins/ins`, `del/del`, and `del/ins` nesting.
263
- - **Slicing Round-Trip Guard**: Cross-author surgical edits now preserve whitespace-only insertions (including ordinary-space replacements for NBSP characters beside hyperlinks) and verify the exact accepted-view text before reporting success. A mismatch fails closed with `PATCH_ROUNDTRIP_MISMATCH` and returns the original OOXML unchanged.
264
- - **Hyperlink-Adjacent Replacement Anchoring**: Paired replacements immediately before or after a hyperlink now retain a stable insertion point after the deletion run is split, preventing qualifiers from being relocated past the hyperlink or following formatted runs.
265
- - **Multiple Same-Run Insertions**: Insertion-only slicing operations with multiple edit points now apply from right to left against a refreshed live span index, preventing an earlier run split from relocating later insertions.
266
- - **Insertion Stress Hardening**: Slicing now detects edge whitespace changes exactly, uses a character-local insertion-only diff when the original text is an exact subsequence of the modified text, and coalesces new text into an existing same-author carrier when foreign revisions are also present. This prevents repeated phrases from relocating insertions and prevents invalid `w:ins/w:ins` nesting in mixed-author paragraphs.
267
- - **Preflight Inspection**: `preflightOperations` now inspects and validates `slice-cross-author` batches, reporting pending foreign-author carrier targets as `ready` instead of `EXISTING_REVISIONS`.
268
- - **Test Suite Expansion**: Added 7 new test suites covering 36 Word Desktop COM golden fixtures, carrier splitting invariants, the SYN-01..12d synthetic test matrix, PKG-01..06 strict package differential replay, the repeated-text/hyperlink whitespace regression, 76 deterministic insertion stress scenarios, and 12 replacement-anchor lifecycle scenarios (expanding the suite from 88 to 95 passing suites).
269
-
270
- ## 0.5.0
271
-
272
- ### ⚠️ Breaking changes
273
-
274
- The following changes can require caller or contributor updates. No public
275
- export or valid existing function signature was removed.
276
-
277
- #### Runtime and result-contract changes
278
-
279
- - **Canonical paragraph text:** `getParagraphText(...)` now returns the
280
- accepted/current view instead of the earlier simplified `w:t`/`w:tab`
281
- concatenation. It excludes deleted and `w:moveFrom` content and includes
282
- structural breaks, soft hyphens, and non-breaking hyphens.
283
- **Migration:** regenerate compared/cached text with the upgraded library. Use
284
- `extractCanonicalParagraphText(...)` when accepted/rejected-view semantics
285
- are intended; integrations that require the former raw traversal must retain
286
- their own legacy extractor before upgrading.
287
- - **Paragraph fingerprints:** fingerprints now incorporate canonical paragraph
288
- text. Stored fingerprints can become stale for paragraphs containing
289
- revisions, moves, breaks, soft hyphens, or non-breaking hyphens.
290
- **Migration:** do not carry fingerprints across this upgrade; re-extract the
291
- document and regenerate target descriptors.
292
- - **Invalid operations:** malformed or field-incompatible document-operation
293
- objects now return a structured `INVALID_OPERATION` error at the runner
294
- boundary instead of reaching later failure/no-op paths.
295
- **Migration:** validate operation files against the published schema and
296
- handle `status === 'error'` plus `error.code === 'INVALID_OPERATION'`.
297
- - **Commented paragraph deletion:** deleting a complete paragraph that contains
298
- an existing comment now fails with `COMMENTED_CONTENT_DELETE` instead of
299
- producing OOXML with dangling comment references.
300
- **Migration:** preserve or deliberately remove/relocate the comment before
301
- deleting the paragraph, and handle the structured error. Atomic callers keep
302
- the original document unchanged.
303
- - **Comment anchor failures:** missing or ambiguous comment anchors now return
304
- `ANCHOR_NOT_FOUND` or `AMBIGUOUS_ANCHOR` and fail/roll back an atomic batch;
305
- they are no longer reported as successful `no_change` operations.
306
- **Migration:** treat these codes as failed operations and use a unique exact
307
- anchor or stricter target descriptor before retrying.
308
- - **Stricter package validation:** DOCX package validation now rejects
309
- unbalanced comment ranges, dangling references/usages, orphan definitions,
310
- and duplicate comment-definition IDs that earlier validation could allow.
311
- **Migration:** repair the comment anchors and `word/comments.xml` definitions
312
- before applying or validating further changes.
313
-
314
- #### Development workflow change
315
-
316
- - **Parallel tests by default:** `npm test` now runs up to four isolated child
317
- processes concurrently. This does not change the published runtime API, but
318
- contributor automation that depends on test execution order or shared files
319
- must be updated. **Migration:** make tests independent or set
320
- `DOCX_TEST_CONCURRENCY=1` for the previous serial scheduling behavior.
321
-
322
- Strict targeting remains opt-in on existing low-level mutation APIs; it is the
323
- default only on the newly added Node facade and CLI.
324
-
325
- ### Deprecations and future breaking changes
326
-
327
- - Replacing an existing numbering part without a merge callback now emits a
328
- deprecation warning. The Node facade and CLI already merge safely by default.
329
- **This remains supported in this release but will become an error in the next
330
- major version.** Migrate low-level callers to
331
- `mergeNumberingXmlBySchemaOrder` before that release.
332
- - **Permissive target resolution deprecation (`AMBIGUOUS_TARGET_HEURISTIC_USED`):**
333
- Lower-level document operation runners currently default to permissive targeting.
334
- In v1.0.0, application will default to `strictTargets: true`. In this release,
335
- when permissive resolution encounters multiple identical candidate paragraphs and
336
- heuristically chooses the first candidate, it emits an `AMBIGUOUS_TARGET_HEURISTIC_USED`
337
- warning with candidate count and migration guidance.
338
- **Migration:** Disambiguate operations using `paragraphId`, `index`, `occurrence`,
339
- or `fingerprint` and pass `{ strictTargets: true }`.
340
-
341
- ### ℹ️ Noteworthy Default & Usability Changes (Non-breaking)
342
-
343
- The following improvements update default behaviors to maximize agent speed, reduce friction, and eliminate unnecessary errors during iterative editing. These changes are **not technically breaking changes** (no public APIs or exports were removed, and all existing options remain configurable), but represent important behavioral refinements that callers should note:
344
-
345
- - **Fallback Author Default (`'AI Redliner'`):** `setDefaultAuthor`, `node/cli.js`, and `openDocx` now default to `'AI Redliner'` (configurable via the `DOCX_REDLINE_AUTHOR` environment variable or `--author`). The CLI `apply` command and core APIs no longer error with `AUTHOR_REQUIRED` when operations omit an author.
346
- - **Output Overwrite by Default:** Destination files provided via `--output` now overwrite by default instead of failing with `OUTPUT_EXISTS`. Callers wanting protection against accidental overwrites can pass `--no-overwrite` or `--no-clobber`. The source document remains protected and is never overwritten unless `--in-place` is explicitly passed.
347
- - **Replacement Event Pairing (`pairReplacements: true` by default):** Adjacent `<w:del>` and `<w:ins>` revisions now default to sharing linked revision metadata and identical timestamps so Microsoft Word groups them as a single replacement in the Reviewing Pane. Pass `pairReplacements: false` for independent revision timestamps.
348
- - **Structured Content Auto-Detection (`structuredContent: true` by default):** Markdown tables, headings (`#`), and lists in replacement text automatically render as native Word elements (`w:tbl`, `w:pStyle`, `w:numPr`). Single outline-numbered legal clauses (e.g. `13.2.1.1`) continue to be treated as ordinary paragraphs without spurious list conversion. Pass `structuredContent: false` to treat replacement text strictly as plain text.
349
- - **Progressive Batch Mode (`atomic: false` default, `atomic: true` opt-in):** Batch operations and CLI `apply` now apply edits progressively by default (`atomic: false`): valid edits commit directly, while failing edits report structured errors in `results` for faster debugging. Pass `{ atomic: true }` (or `--atomic` on the CLI) for all-or-nothing rollback when any operation fails.
350
- - **Clean Direct Edits (`generateRedlines: false` / `--no-redlines`):** Prominently documented that tracked redlines are not always the preferred method. Callers can pass `generateRedlines: false` (or `--no-redlines` on the CLI) for clean execution drafts, document restructuring, or minor typo fixes without tracked changes markup.
351
- - **Existing Revisions Policy (`existingRevisions: 'merge-same-author'` by default):** When editing a paragraph that already contains tracked changes from the same author, prior revisions by that author are merged against the pre-revision baseline (prior changes by that author are reverted and re-diffed to the new modified text), avoiding revision accumulation and nested markup. If a paragraph contains tracked changes from another reviewer, the operation fails closed with `EXISTING_REVISIONS` to protect third-party review marks. Commented revision content fails closed with `COMMENTED_CONTENT_MERGE` so comment anchors cannot be removed or orphaned. Callers can explicitly pass `'accept-all-first'` to normalize prior revisions or `'reject-input'` to refuse editing any revised paragraph.
352
- - **Inline One-Liner CLI Edits:** Added `--target <text>` with `--modified <text>` or `--comment <text>` on `docx-redline apply` for fast 1–2 edit workflows without creating a JSON operations file.
353
-
354
- ### Added
355
-
356
- - Added **Mutation Receipts** (`receipt` on `DocumentOperationResult` and `receipts`
357
- on `BatchOperationResult`). Every operation execution returns structured,
358
- commit-aware telemetry capturing durable revision IDs (with kind and target part),
359
- comment IDs, numbering IDs, relationship IDs, affected targets, and warnings.
360
- Supports `not_attempted`, `applied`, `refused`, `no_change`, and `rolled_back`
361
- dispositions.
362
- - Added **Commit-Aware Output Reconciliation Oracle** (`reconcileReceiptsAgainstOutput`).
363
- Before completing any transaction, reported durable IDs are checked against a
364
- fresh parse of the generated output OOXML parts. Any reconciliation discrepancy
365
- immediately fails the transaction and triggers atomic rollback.
366
- - Exported receipt primitives: `ReceiptCollector`, `createEmptyReceipt`, and
367
- `reconcileReceiptsAgainstOutput` along with `MutationReceipt` and
368
- `MutationReceiptRevisionItem` TypeScript declarations.
369
- - Added **Paragraph Mark Revisions** (`w:pPrChange`, `markParagraphMarkInserted`,
370
- `markParagraphMarkDeleted`) ensuring whole-paragraph deletions and insertions
371
- cleanly track paragraph mark lifecycles for full Accept All / Reject All symmetry.
372
- - Added **Paragraph Boundary Mutation Validation** (`validateParagraphBoundaryMutation`)
373
- guarding against invalid cross-paragraph boundary merges.
374
-
375
- - Added `analyzeStructuredContent(...)` and `planStructuredReplacement(...)`
376
- for agent-authored attachments and schedules containing mixed headings,
377
- paragraphs, lists, and tables. The planner returns typed blocks and produces
378
- one atomic `structuredContent` operation only when the Markdown is valid.
379
-
380
- - Added session-scoped paragraph metadata indexing for canonical text,
381
- normalized text, paragraph IDs, fingerprints, table context, and document
382
- indexes, plus deterministic targeting and route-profiling benchmarks.
383
- - Added a shared list-marker grammar and parsed list-item vocabulary used by
384
- pipeline analysis, orchestration, normalization, and list targeting.
385
- - Added an internal reconciliation capability matrix and opt-in route
386
- instrumentation. Public reconciliation result shapes and route selection
387
- policy remain unchanged.
388
-
389
- - Added `scripts/extract_text.mjs` and `scripts/apply_changes.mjs` as thin
390
- compatibility entrypoints for legacy skill installations. They delegate to
391
- the supported CLI, retain strict atomic validated writes, and accept legacy
392
- operation files with a top-level `changes` array.
393
- - Added typed operations, per-operation authors, strict target descriptors,
394
- deterministic preflight diagnostics, and auditable resolution metadata.
395
- - Added `inspectDocumentParts(...)`, a structured document inventory using the
396
- same canonical text extractor as targeting and ingestion.
397
- - Added `@ansonlai/docx-redline-js/node`. `openDocx(...)` applies batches to
398
- complete DOCX buffers transactionally and validates before commit.
399
- - Added the cross-platform `docx-redline` CLI with JSON `inspect`, `extract`,
400
- `preflight`, `apply`, `accept`, `reject`, `delete-comments`, and `validate`
401
- commands, plus a published operation-file JSON Schema.
402
-
403
- ### Behavior and reliability
404
-
405
- - Explicit structured replacements now reject missing Markdown table separator
406
- - Generated Markdown tables repeat their header row and prevent logical data rows from splitting across pages.
407
- rows, missing data rows, and inconsistent column counts with
408
- `STRUCTURED_CONTENT_INVALID` instead of inserting literal pipe-delimited text.
409
- - Tables created inside mixed replacements are tracked once at block scope,
410
- avoiding nested revisions while keeping Accept All and Reject All symmetric.
411
-
412
- - Target preflight and live mutation reuse one document-scoped paragraph index;
413
- strict duplicate detection and resolution metadata retain their prior
414
- semantics while avoiding repeated whole-document text traversal.
415
- - Revision-ID seeding now walks the DOM with child/sibling pointers instead of
416
- allocating an array containing every element.
417
- - Single-source-paragraph list expansion uses the focused list generator
418
- directly. Multi-paragraph marked-list edits deliberately retain the legacy
419
- run-aware reconciliation pipeline for structural parity.
420
- - Fixed paragraph-to-list expansion when a manually numbered heading carries
421
- `w:numId w:val="0"`. Numbering suppression is no longer reused as a generated
422
- list ID, so inserted bullet and numbered items receive valid positive IDs.
423
- - Tracked heading-to-list replacements now keep the deleted source heading in
424
- its own paragraph and track the paragraph marks of every inserted list item.
425
- This prevents deleted heading text from joining the first item and preserves
426
- exact Accept/Reject structure without adding a full-document sentinel
427
- paragraph.
428
- - Inserted list items now inherit source font family, size, language, and
429
- related script typography without inheriting heading-only bold, underline,
430
- italic, or strike formatting unless requested by the replacement markdown.
431
- - Canonical-only table-cell targeting now consumes the shared paragraph-text
432
- extractor, while offset- and sentinel-producing walkers remain specialized.
433
- Their accepted visible projections now agree on soft hyphens as well as tabs,
434
- breaks, hyperlinks, and non-breaking hyphens.
435
-
436
- - Comment preflight and application now share one anchor resolver. Exact
437
- matches take priority, unique ordinary-space/NBSP differences preserve raw
438
- offsets, and missing or repeated anchors return structured
439
- `ANCHOR_NOT_FOUND` or `AMBIGUOUS_ANCHOR` errors.
440
- - Full-document operation batches now share one live DOM and revision-ID
441
- allocator, reducing complete-document parsing and serialization from once per
442
- operation to once per changed batch. Scoped reconciliation engines and their
443
- targeting decisions are unchanged.
444
- - Each live-session operation uses a DOM and allocator savepoint. Errors and
445
- reported no-ops restore the savepoint; atomic failures and all-no-op batches
446
- still return the exact original XML without serializing it.
447
- - Decomposed the standalone document-operation runner into a stable 13-line
448
- compatibility facade plus focused session, applier, batch-orchestrator,
449
- heuristic, and OOXML-mutation modules. Runtime exports and declarations are
450
- unchanged, and operation internals now use leaf imports instead of importing
451
- the root entry point.
452
- - Package comment IDs are seeded from existing anchors and definitions, and
453
- the Node facade merges numbering without discarding prior definitions.
454
- - Canonical paragraph text consistently handles revisions, moves, tabs,
455
- breaks, soft hyphens, and non-breaking hyphens; see the breaking-change note
456
- above for `getParagraphText(...)` and fingerprint compatibility.
457
- - Structured inspection now explicitly supports the `current` view as the
458
- accepted/current document view and resolves comment ranges spanning multiple
459
- paragraphs as one exact anchor string.
460
- - Transactional no-op results preserve the original DOCX bytes exactly,
461
- including when the same facade instance has already committed an edit.
462
-
463
- ### Testing and development
464
-
465
- - Added focused and semantic visual-failure regressions for suppressed
466
- numbering, paragraph separation, paragraph-mark revisions, selective
467
- typography inheritance, and exact accepted/rejected paragraph sequences,
468
- plus seeded fuzz variants across list length, marker, font, and size.
469
- Added the independently specified
470
- `legal-suppressed-heading-to-bullet-list` and
471
- `legal-structured-attachment-mixed-blocks` Microsoft Word differential
472
- fixtures; the synthetic catalogue now contains 49 cases and 49 task types.
473
-
474
- - The JavaScript test runner now uses bounded asynchronous subprocess workers,
475
- capped at four by default, while retaining one fresh Node process per file,
476
- sorted reporting, timeouts, captured diagnostics, and failure-marker checks.
477
- Set `DOCX_TEST_CONCURRENCY=1` for serial reproduction.
478
- - Added `npm run benchmark:tests` and a Phase 6 runner regression. The checked
479
- benchmark reduced median suite time from 18.02 seconds to 7.96 seconds
480
- (55.80%); 20 consecutive parallel runs and a separate serial run passed all
481
- 63 files. Parallel c8 collection remains valid at 89.90% statements/lines,
482
- 77.40% branches, and 93.10% functions.
483
-
484
- - Added Phase 3 targeting/traversal, Phase 4 list/text parity, and Phase 5 route
485
- compatibility regressions. Together with the Phase 6 runner regression, the
486
- suite now contains 63 passing test files.
487
- - Added `npm run benchmark:targeting` and `npm run profile:routes`. The checked
488
- 10,000-paragraph benchmark showed no single-operation regression and a 47.16x
489
- Node median improvement for 100 cached resolutions. The equivalent native
490
- Chrome benchmark showed no single-operation regression and a 36.45x batch
491
- improvement. All 47 Microsoft Word differential fixtures and all 60 real-
492
- document Word corpus scenarios passed after the routing changes.
493
-
494
- - Added subprocess coverage for legacy wrapper selection, operation-file
495
- compatibility, successful output, atomic anchor failure, source-byte
496
- preservation, and protection of pre-existing output files.
497
- - Added a live-session accuracy and instrumentation suite covering accepted and
498
- rejected text, structural validity, sequential equivalence, comments, lists,
499
- tables, highlights, one-parse/one-serialize execution, exact no-ops, and
500
- atomic rollback. Added `npm run benchmark:session`; the checked accuracy-first
501
- benchmark reduced parse/serialize counts from 10/10 to 1/1 and measured a
502
- 1.58x median speedup with per-operation savepoints retained.
503
- - Added a Phase 2 architecture regression covering facade export identity,
504
- direct leaf imports, exact session rollback, isolated context commit, and
505
- stable comment-first scheduling.
506
- - Added five edge-case suites for canonical text views, cross-paragraph comment
507
- inspection, table/list/reference context, sequential package transactions,
508
- selective multi-author cleanup, ZIP rejection behavior, CLI filters, JSON
509
- exit contracts, and destructive-output safeguards. Together with the Phase 2
510
- Phase 1 and Phase 2 regressions, these established the earlier 57-file
511
- baseline expanded by the subsequent performance phases.
512
-
513
- ## 0.3.0
514
-
515
- ### Breaking changes
516
-
517
- - Malformed OOXML no longer escapes public transform APIs as a raw parser
518
- exception. In particular, revision accept/reject and comment-deletion callers
519
- that previously used `try`/`catch` must now inspect `status === 'error'` and
520
- `error.code === 'PARSE_ERROR'` on the returned result.
521
- - Caller content is no longer sanitized by default. Pass `sanitizeInput: true`
522
- to remove a standalone leading assistant-preface line. Dollar-delimited text
523
- and literal `\\n` / `\\r\\n` sequences are never removed implicitly.
524
- - The exported `sanitizeAiResponse` helper no longer removes dollar-delimited
525
- spans or converts literal `\\n` / `\\r\\n` sequences. Direct callers that
526
- relied on those transformations must perform them explicitly.
527
- - `applyOperationsToDocumentXml` is atomic by default. Failed batches return
528
- the original document with `rolledBack: true`; pass `atomic: false` to retain
529
- partial-result behavior.
530
- - Missing multi-line targets now return `TARGET_NOT_FOUND` instead of being
531
- indistinguishable from a no-op. Stale batch anchors also return
532
- `TARGET_NOT_FOUND` instead of falling back to a visibly different paragraph.
533
-
534
- ### Behavior changes and fixes
535
-
536
- - Revision IDs are now allocated per document. Near-limit prior IDs restart in
537
- a safe low range, and unrelated bookmark/relationship IDs no longer seed the
538
- revision counter.
539
- - Concurrent calls with explicit authors keep their revision attribution
540
- isolated from process-global defaults.
541
- - `existingRevisions: 'accept-all-first'` now preserves the original OOXML when
542
- the requested edit is a no-op.
543
- - Added `existingRevisions: 'accept-all-first-keep-normalized'` for callers that
544
- explicitly want accepted existing revisions returned even when no redline is
545
- added.
546
- - Diff output is deterministic by default. Inputs exceeding 262,144 unique
547
- tokens return `DIFF_TOKEN_LIMIT` with the original OOXML unchanged instead of
548
- risking truncated text; leading whitespace is preserved exactly.
549
- - Reconstruction preserves structural line breaks and body section-property
550
- placement. A target that names only part of a reconstruction range now
551
- returns `PARTIAL_TARGET` instead of risking deletion of untargeted content.
552
- - Paragraph targeting and reconstruction now preserve leading, middle, and
553
- trailing `w:tab` elements as literal tab characters instead of omitting or
554
- trimming them.
555
- - Adjacent redline edits now preserve complex-field instructions, begin/
556
- separate/end markers, and unchanged cached display results in their original
557
- field boundary. Field instructions remain inert and are not evaluated.
558
- - Added structured XML parse results and result-returning ingestion helpers.
559
- - Hardened deterministic diff token handling and leading-whitespace fidelity.
560
-
561
- ### Testing and development
562
-
563
- - Expanded the desktop Word differential from 20 to 25 cases, adding structural
564
- checks for bookmarks, internal hyperlinks, mixed formatting, content
565
- controls, and tables.
566
- - Expanded the desktop Word differential from 25 to 28 cases with structural
567
- tabs and a locked PAGE field, and added tab/field shapes to focused and fuzz
568
- regression testing.
569
- - Expanded the desktop Word differential from 28 to 33 cases with comments,
570
- footnotes, endnotes, headers/footers, and an external hyperlink.
571
- - Extended the deterministic script-only DOCX packager with opt-in related
572
- parts, relationship/content-type validation, reusable fixture constructors,
573
- and SHA-256 verification that supplied parts remain byte-identical. Runtime
574
- library code and dependencies are unchanged.
575
- - Added validated task, structure, oracle, and manual-review metadata across the
576
- 33 synthetic and 20 SuperDoc Word cases. `npm run report:word:coverage`
577
- produces a deterministic coverage matrix with explicit high-priority gap
578
- dispositions, while `npm run review:word:prepare` creates a pending rotating
579
- human-review manifest without self-certifying visual results.
580
- - Added detailed production-function coverage reporting and a checked per-file
581
- regression baseline. Five behavior-focused Phase 3 suites exercise numbering
582
- collisions/remapping, every reconciliation route, list construction and
583
- fallback, patch/format boundaries, table decisions, pipeline modes, and
584
- standalone rollback/highlight paths. Production function coverage moved from
585
- 437/540 to 496/542 and all reachable P0 functions are covered; no runtime
586
- behavior or compatibility contract changed.
1
+ # Changelog
2
+
3
+ ## 0.9.0
4
+
5
+ ### Changed
6
+
7
+ - **Unknown `paragraph-format.properties` keys are rejected:** `INVALID_OPERATION` with `field: properties.<key>`, `properties[]`
8
+ and `recovery.action: change_request`; nothing is applied. The 0.8.x `UNKNOWN_PARAGRAPH_FORMAT_PROPERTY` warning no longer fires.
9
+ - **CLI contract version 9** (`list-format` operation type, capability `list-format-v1`). Integrations that do not send
10
+ `list-format` can keep requiring version 8.
11
+ - **`UNSUPPORTED_LIST_CONVERSION` / `LIST_MARKER_NOT_BOUND` now point at `list-format`:** `recovery.action` is
12
+ `use_list_format_convert_plain` (was `apply_plain_text_or_convert_in_host`) and, when the original paragraph has no marker,
13
+ `recovery.suggestedOperation` carries the `list-format` request to send with the same target (for "Header" -> "C. Header":
14
+ `{ mode: 'convert-plain', format: 'upperLetter', strip: 'none', start: 3 }`). `listMarkers: 'literal'` is unchanged.
15
+ - **`UNSUPPORTED_LIST_FORMAT_CHANGE` / `LIST_FORMAT_NOT_APPLIED` now point at `list-format` `change-format`:** `recovery.action`
16
+ is `use_list_format_change_format` (was `change_list_format_in_host`) and `recovery.suggestedOperation` carries
17
+ `{ type: 'list-format', list: { mode: 'change-format', format, level, scope: 'list' } }`; add a target on one item of the list.
18
+ `list-change` still never applies a format change itself.
19
+ - **A `list-format` with `scope: 'list'` counts as a write to every item it rebinds:** another write to one of those items in the
20
+ same batch (`paragraph-format`, `highlight`, `format`, a second `list-format` of the same list) now fails before mutation with
21
+ `LIST_FORMAT_COMPOSITION_CONFLICT` (`reason: list-rebind`); in WP-5 it ran in batch order. Send it in a separate apply or use
22
+ `scope: 'range'`. A single-paragraph `redline` of an item still composes (see Added).
23
+ - **Text-equal letter, roman and bullet header conversions are made in place:** a `redline` whose `modified` equals the plain
24
+ paragraph's typed-marker text (`B. Header` -> `B. Header`) now strips the marker as a tracked deletion and binds the list with a
25
+ tracked `w:pPrChange` in the same paragraph, like the decimal path. The previous whole-paragraph delete plus insert lost
26
+ `w14:paraId`, bookmarks and direct formatting and reviewed as a deleted and an inserted line; it remains only for shapes the
27
+ in-place conversion refuses. (It was reject-exact for plain runs but not for markers inside hyperlinks or smart tags, where the
28
+ Reject All verifier refuses it.)
29
+ - **Operations that would break Reject All are refused by default:** the revision fidelity verifier now runs in
30
+ `enforce` mode. A refused operation leaves the document unchanged and returns `REVISION_FIDELITY_VIOLATION` with a
31
+ one-line message naming the invariant, the source paragraph (extraction index and excerpt), expected vs actual, an
32
+ explanation and a retry instruction; `violations[]`, `invariants` and `recovery.retryGuidance` carry the same detail for
33
+ agents, the message is also sent to `onWarn`, and compact CLI output keeps it. Opt out per call with
34
+ `verifyRevisionFidelity: 'warn'` (apply and warn) or `'off'` (diagnosis). CLI: `--revision-fidelity <enforce|warn|off>`,
35
+ shown in `effectiveOptions`; the agent profile rejects `off`. Capability `revision-fidelity-v1`. A single-target untracked
36
+ operation (`generateRedlines: false`) is also refused when it changes a paragraph outside its target in the Accept All or
37
+ Reject All view (invariant `ACCEPT_NEIGHBORS`).
38
+ - **`ACCEPT_INTENT` and numbering-part existence in the revision fidelity check:** a tracked plain-text `redline` with complete
39
+ `modified` (no Markdown, list-marker or table syntax; single-paragraph targets and `targetEnd` ranges) is refused when Accept All of
40
+ its target would not read as `modified`. `REJECT_NUMBERING_REFS` also refuses a numId that the output binds or Reject All restores
41
+ (including inside `w:pPrChange`) when the source did not already use it and no `w:num` -> `w:abstractNum` -> `w:lvl` defines it in the
42
+ numbering available to the operation. Accept-first range targets now use the exact accepted scope.
43
+ - **`ensureNumberingArtifactsInZip` always merges numbering, and refuses id conflicts:** the optional `mergeNumberingXml`
44
+ callback is ignored (the single merge is `mergeNumberingParts` in `services/numbering-allocation.js`), an existing
45
+ `numbering.xml` is no longer replaced when no callback is given, and an incoming `w:abstractNum` / `w:num` whose id exists with
46
+ different content throws `NUMBERING_ID_CONFLICT` instead of silently binding new paragraphs to another list. `openDocx` also
47
+ rejects output that introduces a dangling `numId`, `abstractNumId` or `ilvl` (`NUMBERING_*` issues).
48
+ - **Receipt kinds:** a whole paragraph deleted by an empty `modified` reports its deleted paragraph mark with receipt kind `del`
49
+ (was `structural`); the explicit `1. Header` list conversion now receipts its `w:pPrChange`.
50
+ - **`paragraph-format` keeps the earliest same-author `w:pPrChange` snapshot** and refuses to overwrite another author's pending
51
+ snapshot (`PARAGRAPH_FORMAT_REVISION_CONFLICT`); see Added.
52
+
53
+ ### Added
54
+
55
+ - **`redline` + `list-format` on one paragraph in one batch:** a changed-text header (`1. Old` -> `1. New`) is a `redline` that keeps
56
+ the typed marker exactly (`modified: '1. New'` or `replacements`) plus a `list-format` `convert-plain` of the same paragraph. The
57
+ batch compiler allows exactly this pair (both bind at batch start) and fixes the order whatever the batch order: the redline runs
58
+ first, then the list-format strips the unchanged marker, so Accept All shows label `1.` with `New` and Reject All the literal
59
+ `1. Old` (verified in Word with `ListFormat.ListString`, including a `3.` start value). A single-paragraph `redline` of a list item
60
+ also composes with a `change-format`/`join` that rebinds it. Incompatible pairs are refused before mutation with
61
+ `LIST_FORMAT_COMPOSITION_CONFLICT` (`reason`: `marker-changed` with `sourceMarker`/`modifiedMarker`, `redline-unchanged`,
62
+ `redline-shape`; `recovery.action: split_text_and_list_changes`); other kinds keep `OVERLAPPING_SOURCE_TARGETS` /
63
+ `REVISION_ORDER_CONFLICT`. No new capability: it is part of `list-format-v1` behavior.
64
+ - **`list-format` `change-format` and `join` modes (`list-format-change-v1`, contract version stays 9):** `change-format` changes
65
+ the kind or numbering format of an existing list (`format`, `lvlText`, `start`, `level`, default the target's level). It never
66
+ edits the shared definition: a NEW `w:abstractNum` + `w:num` is allocated with the source levels, indentation and fonts copied
67
+ and one level redefined (`w:isLgl`, picture bullets and a bullet's symbol font are dropped on a format change), and each item is
68
+ rebound with a tracked `w:pPrChange`, so Reject All restores the original labels and Accept All shows the new ones (verified in
69
+ Word with `ListFormat.ListString` after `Revisions.AcceptAll()`/`RejectAll()`: bullets to `1.`, `1.` to `A.`, a nested level to
70
+ `(i)`, and two range splits). `scope: 'list'` (default) rebinds every live item of the list (`items[].inTarget` marks those outside
71
+ the target); `scope: 'range'` rebinds a `target`..`targetEnd` range that runs to the end of the list and continues its count
72
+ (start values on the new definition, including a split that starts at a nested item). `join` binds the target list (or a range
73
+ running to its end) to the `numId` of `list.continueFrom`, an item of an earlier list, so it continues that count; `continueFrom` is
74
+ bound at batch start. Tracked-deleted paragraphs keep their `w:numPr`; a same-author earlier `w:pPrChange` snapshot is kept;
75
+ later operations in a batch see definitions earlier ones allocated. Refused before any write: `LIST_FORMAT_TARGET_NOT_LIST`,
76
+ `LIST_FORMAT_MIXED_RANGE` (plain paragraphs, several lists, or several levels without `list.level`), `LIST_FORMAT_STYLE_LINKED`
77
+ (heading/list-style numbering), `LIST_FORMAT_STYLE_NUMBERED`, `LIST_FORMAT_SPLIT_UNSUPPORTED` (`mid-sequence` range,
78
+ `nested-restart`, `shared-definition` abstract used by another list, `interleaved` join), `LIST_FORMAT_DEFINITION_UNSUPPORTED`
79
+ (missing definition, `w:lvlOverride/w:lvl`, `w:lvlRestart` in a split), `LIST_FORMAT_JOIN_INVALID` and
80
+ `PARAGRAPH_FORMAT_REVISION_CONFLICT`; an untracked rebinding outside the target is refused; a no-op is `ok` with a
81
+ `LIST_FORMAT_UNCHANGED` warning. Results carry `list` (`sourceNumId`, `numId`, `items[]`, `levelStarts` or `continueFrom`).
82
+ An older CLI refuses the new modes with `INVALID_OPERATION` (`field: list.mode`), so negotiate the capability.
83
+ - **`list-format` operation, `convert-plain` mode (`list-format-v1`):** binds plain paragraphs (a target, or `target`..`targetEnd`
84
+ in one container) whose text starts with a typed marker (`1.`/`1)`, `A.`/`a)`, `IV.`/`iv.`, `-`/`•`) to a NEW list definition in
85
+ place. The marker is removed with a tracked `w:del` of exactly the marker text (`strip: 'marker'`, default) and the `w:numPr`
86
+ binding is a tracked `w:pPrChange`, so Reject All restores the literal marker and the unnumbered paragraph and Accept All shows
87
+ the list label (verified in Word: `ListFormat.ListString` after `Revisions.AcceptAll()`/`RejectAll()` for `A.`, `1.`, `I.` and a
88
+ `C.` start value). Each marker's value is the item's value (`startOverride` on the new `w:num`; the label keeps the typed `.`/`)`);
89
+ consecutive values share one list, a gap starts a new list at the typed value, body paragraphs in the range stay plain, and a
90
+ later `list-format` in the same batch whose first value continues the previous list joins it. `strip: 'none'` with `format`
91
+ (and optional `start`) binds without touching the text. Ambiguous or unsupported shapes are refused with recovery envelopes:
92
+ `LIST_MARKER_NOT_FOUND`, `LIST_MARKER_AMBIGUOUS` (a lone `I.`/`C.` reads as letter or roman; set `list.format`),
93
+ `LIST_MARKER_UNSUPPORTED` (mixed markers, `2.1.`, `(a)`, a marker inside a hyperlink/field/revision, format mismatch),
94
+ `LIST_FORMAT_TARGET_IS_LIST`, `LIST_FORMAT_STYLE_NUMBERED` (style-linked numbering; needs `stylesXml` on the bare runner, read from
95
+ the package by `openDocx`) and `PARAGRAPH_FORMAT_REVISION_CONFLICT`. Body only. Results carry `list` (`format`, `items[]` with
96
+ `numId`/`value`, `skipped[]`). Schema `$defs.listFormat`, `ListFormatDocumentOperation` in `index.d.ts`.
97
+ - **`paragraph-format.numbering` binding (`paragraph-numbering-binding-v1`, no contract bump):** `properties.numbering` is
98
+ `{ numId, level? }` (bind the paragraph to an existing list) or `null` (remove its direct list binding), recorded as a tracked
99
+ `w:pPrChange` so Reject All restores the previous `w:numPr` exactly and Accept All shows the new one (verified in Word with
100
+ `Document.Revisions.RejectAll()/AcceptAll()` and `ListFormat.ListString`). `w:numPr` is inserted at its CT_PPr position
101
+ (after `widowControl`, before `suppressLineNumbers`/`spacing`/`ind`; `ilvl` before `numId`). A binding never allocates: an unknown
102
+ `numId` or undefined level is refused `PARAGRAPH_NUMBERING_NOT_FOUND`. `paragraph-format` now keeps the earliest `w:pPrChange`
103
+ snapshot for repeated same-author edits (it used to replace it, so Reject All restored the intermediate state) and refuses to
104
+ overwrite another author's pending snapshot (`PARAGRAPH_FORMAT_REVISION_CONFLICT`). Letter and roman literal-marker headers
105
+ (`C.`, `iv.`) now convert with their start value instead of restarting at the first item.
106
+ - **List conversion diagnostics (`list-conversion-diagnostics-v1`, no contract bump):** text edits no longer report a silent `ok`
107
+ when they cannot create list structure. A plain paragraph whose new text starts a list marker returns the warning
108
+ `LIST_MARKER_NOT_BOUND` (`diagnostics[]` on the operation result, `CODE: message` in `warnings`), or `UNSUPPORTED_LIST_CONVERSION`
109
+ under the strict policy (`--profile agent`, `--atomic`, or `strictListConversion: true`). A list item whose retained marker asks for a
110
+ different kind or format than its numbering definition returns `LIST_FORMAT_NOT_APPLIED` / `UNSUPPORTED_LIST_FORMAT_CHANGE`.
111
+ `listMarkers: 'literal'` on an operation opts out. `paragraph-format.properties` is now a closed schema; unknown keys (such as
112
+ `spacingAfter`) first produced an `UNKNOWN_PARAGRAPH_FORMAT_PROPERTY` warning (superseded in 0.9.0, see Changed).
113
+ Independent of `REVISION_FIDELITY_VIOLATION`; compact CLI output keeps `diagnostics` and the error fields.
114
+
115
+ - **Numbering allocator:** new `services/numbering-allocation.js` (exported from the package root): `allocateNumberingDefinition`
116
+ (new abstractNum + num with fresh ids, copied level geometry, per-level `startOverride`), `allocateNumberingForReplacement`
117
+ (same-kind source-list continuation or fresh allocation; tracked-deleted source paragraphs never change `numPr`),
118
+ `mergeNumberingParts` / `mergeNumberingXmlBySchemaOrder` (now with an `onConflict` option), `canonicalNumberingDefinition` and
119
+ `validateNumberingIntegrity`. The standalone runner's consumers and the `openDocx` facade merge through the same function;
120
+ `reuseSourceListNumbering` is replaced by allocator policy.
121
+ - **Revision fidelity verifier:** every tracked operation now checks, on the live DOM and without extra
122
+ serialization, that Word's Reject All of its output equals Word's Reject All of the source (paragraph count and order,
123
+ canonical text including breaks and tabs, paragraph properties, `numPr`, hyperlink/field/content-control membership).
124
+ A mismatch refuses the operation (see Changed); in `warn` mode it is applied with the message in `warnings` and
125
+ `revisionFidelityViolations`. The Word-semantics simulator is `core/word-revision-semantics.js`, validated against
126
+ recorded Word 16 Reject All / Accept All results (`tests/fixtures/word-revision-semantics/expectations.json`).
127
+
128
+ - **`UNTRACKED_CREATED_REVISION` invariant:** an operation with `generateRedlines: false` is refused (default `enforce`) when its
129
+ output contains a revision id (`w:ins`, `w:del`, moves, `*PrChange`, paragraph-mark and row revisions) that the source did not
130
+ have. A foreign revision sliced around the edit keeps its element, author and date and is not counted. It reports as
131
+ `REVISION_FIDELITY_VIOLATION` with `violations[].invariant: 'UNTRACKED_CREATED_REVISION'`, also for range targets.
132
+
133
+ ### Internal
134
+
135
+ - **Revision fidelity verifier cost cut from about +39% to about +8% per batch:** each verified operation's projections (accepted and rejected views, revision-id signatures, numbering references) are reused as the next operation's baseline, numbering definitions are parsed once per part, and the revision-view projection no longer builds live node lists. No check was weakened.
136
+ - **One paragraph-mark revision helper:** `engine/paragraph-mark-revisions.js` is now the only module that constructs a tracked paragraph mark (`w:ins`/`w:del` under `w:pPr/w:rPr`): `markParagraphInserted`, `markParagraphDeleted`, `setParagraphPropertiesMarkRevision`, `restoreParagraphMarkInserted`, string builders `paragraphMarkRevisionXml` / `paragraphMarkPPrXml`, and `planParagraphMarks` (asserts the Reject All paragraph-count invariants). Document-level shortcuts, list generation, reconstruction, text-to-table and generated tables use it, and `tests/paragraph_mark_single_site_tests.mjs` fails on any other construction site. Output is unchanged except that a new mark `rPr` is placed before `sectPr`/`pPrChange`, an existing mark is replaced rather than duplicated, and the empty-modified shortcut's deleted-mark receipt kind is `del` (was `structural`).
137
+
138
+ ### Fixed
139
+
140
+ - **A `targetEnd` given as a compact handle (`paragraphId` + `fingerprint`, no numeric `index`) was silently ignored** for a
141
+ plain `redline`, so only the first paragraph of the range was replaced and the rest stayed in the accepted text. The range is now
142
+ resolved from the end handle; a range that crosses a table or other container boundary is refused `TARGET_RANGE_INVALID`.
143
+ - **Range replace over empty paragraphs no longer fails receipt reconciliation:** a replacement whose diff deletes only paragraph
144
+ breaks recorded a paired deletion revision id in the receipt that was never written, so the operation was refused with
145
+ `RECEIPT_RECONCILIATION_FAILED`. Paired replacement ids are now recorded only when written. The reject-roundtrip matrix
146
+ treats that code as a failure, not a skipped refusal.
147
+ - **Plain `redline` keeps `w14:paraId` / `w14:textId`:** reconstruction used to rebuild the paragraph without its identity
148
+ attributes; the rebuilt paragraph that survives for a source paragraph (not tracked-deleted) now keeps them, once (an inserted
149
+ split gives them to the paragraph whose mark Reject All keeps). A composed `redline` + `list-format` pair keeps identity.
150
+ - **Text-changed header conversion with direct `keepNext` / spacing is no longer refused `REJECT_PARAGRAPH_PROPS`:** the list
151
+ fallback's tracked-deleted source paragraph keeps its original `pPr`; the new list paragraph carries the direct properties and
152
+ the source `w14:paraId`.
153
+ - **`ReconciliationPipeline.executeTableGeneration` inserts a tracked table block:** the public pipeline method generated
154
+ tracked cell text in untracked rows (the defect `engine/table-mode.js` had); rows now carry `w:trPr/w:ins` so Reject All
155
+ removes the table.
156
+ - **Separate list conversions in one batch continue one list:** two `redline` operations that turned non-adjacent headers into continuing markers (`A. Header`, `B. Header`) each allocated their own numbering instance, so Word showed `A.`, `B.` (start values) but as two lists, and a later item inserted between them would not renumber. The structural list fallback now uses the batch sequence state `list-format convert-plain` uses: same kind, format and level with a continuing value reuses the previous operation's live `numId` (a `redline` and a `convert-plain` of the same format join one list too); a gap (`A.` then `C.`) or a different format allocates a new instance. Ids still come from `services/numbering-allocation.js`. (Reported against 0.8.4.)
157
+ - **Weak-target localized replacements no longer delete soft-break paragraphs:** a `replacements` edit targeted only by `index` + `exactText` (no `paragraphId`/`fingerprint`) in a paragraph containing `<w:br/>` returned `ok` but tracked the whole paragraph as deleted and inserted nothing, because the batch compiler treated the line break in `exactText` as a multi-paragraph target and skipped replacement compilation. Localized replacements now always bind at batch start, so weak and strong targets behave identically. Every localized replacement also has its accepted result checked against the compiled text before commit (`PATCH_ROUNDTRIP_MISMATCH`, nothing applied), and uncompiled replacements are refused rather than applied.
158
+ - **Reconstruction keeps wrappers and deleted placeholders:** a text edit in a paragraph with a `w:fldSimple` no longer drops the field (Reject All lost its result text); a deleted manual break, tab or inline object is a tracked deletion instead of disappearing; and converting a paragraph to a list keeps hyperlink and content-control containers around the deleted runs, so Reject All keeps the text inside its link. Found by the reject-roundtrip matrix.
159
+ - **Punctuation edits stay surgical:** leading and trailing punctuation are separate diff tokens and unchanged substantive words anchor the re-diff, so "agents and attorneys" -> "agents, attorneys, accountants and other professional advisors" marks only " and" -> "," and the appended items.
160
+ - **Table reconciliation keeps untouched cell paragraphs:** editing one cell of a Markdown table reconciliation collapsed a multi-paragraph cell (for example `LS` plus a trailing empty paragraph) to one paragraph, untracked, so Reject All lost a paragraph. Cells whose only difference is trailing empty paragraphs are now unchanged, and a modified multi-paragraph cell is reconciled paragraph by paragraph (inserted paragraphs carry tracked marks).
161
+ - **Text-to-table redlines insert tracked table rows:** a paragraph range converted to a Markdown table carried tracked cell text but untracked rows, so Reject All left an empty table behind. Rows now carry `w:trPr/w:ins` and tracked cell paragraph marks like other inserted tables.
162
+ - **Explicit-start list conversion is reversible with Reject All:** converting a `1. Header` paragraph to a list bound `numPr` without recording a `w:pPrChange`, so rejecting the tracked deletion of the marker left a numbered item. The binding now records the original paragraph properties (empty `<w:pPr/>` when none) in a receipted `w:pPrChange`.
163
+ - **Reject/Accept All match Word for table cells and `pPrChange`:** rejecting an inserted paragraph mark keeps the cell's last paragraph (empty) instead of dropping it, accepting a fully deleted sole-paragraph table removes the table, and rejecting a `pPrChange` keeps the paragraph-mark `rPr` and `sectPr` instead of discarding them.
164
+ - **Property readers ignore `pPrChange`/`rPrChange` snapshots:** a new `core/current-properties.js` accessor reads only direct `w:pPr`/`w:rPr` children, so a tracked property change no longer makes its historical `numPr`, `pStyle`, `outlineLvl` or `b`/`i` look current. Markdown export, numbering-context detection, list-numbering reuse/overwrite, list-shape checks and captured-entity kind now use it; a usage test fails on new descendant property lookups.
165
+ - **Explicit `1. Header` list conversion works on a bare `document.xml`:** with no numbering part the batch had no numbering id state, so the conversion aborted after recording tracked deletions in the receipt and failed with `RECEIPT_RECONCILIATION_FAILED`. The bare runner now always seeds numbering ids as `openDocx` does, and an aborted conversion no longer records receipt revisions.
166
+ - **`generateRedlines: false` stays untracked under `slice-cross-author`:** the surgical insert computed its nested-revision flag as
167
+ `!(generateRedlines && ...)`, which is true whenever `generateRedlines` is false, so an inserted word was wrapped in a new tracked
168
+ `w:ins` (`applyRedlineToOxml` and `applyOperationsToDocumentXml`; `redline`/`replace`, `modified`/`replacements`). It now writes
169
+ plain runs, like `merge-same-author`, and leaves other authors' revisions intact (a foreign insertion is sliced around the edit).
170
+ - **Table appended after the final paragraph of a pretty-printed document keeps its tracked trailing paragraph:** a whitespace
171
+ text node between the last paragraph and `w:sectPr` made the retained-paragraph table append believe the table did not end
172
+ the body, so the body ended with a table and Word added an untracked empty paragraph that Reject All left behind (plugin
173
+ report R5; Word differential 5 vs 4 paragraphs). The append now skips non-element nodes when deciding. The Word-semantics
174
+ simulator (and therefore the post-apply verifier) counts the untracked paragraph Word supplies after a body-final table, so
175
+ the old output is refused with `REVISION_FIDELITY_VIOLATION`. Documents without whitespace between blocks (the plugin's
176
+ own) were already correct on `Revisions.RejectAll()` and the ribbon; `Document.RejectAllRevisions()` still leaves the
177
+ tracked-inserted last mark behind (a Word API quirk, see the fidelity plan).
178
+
179
+ ## 0.8.4
180
+
181
+ ### Added
182
+
183
+ - **`textOccurrence` on `format` / `character-format`:** a 1-based match of `textToFormat` inside the resolved paragraph,
184
+ independent of `target.occurrence` (which selects the paragraph). Out of range fails with `PATCH_SOURCE_NOT_FOUND`;
185
+ calls without it still read `target.occurrence`. CLI capability `format-text-occurrence-v1`; contract stays version 8.
186
+ - **`applyRedlineToOxml` numbering options:** `numberingXml` and `numberingIdState` remap generated list definitions to
187
+ IDs that do not collide with the source numbering.
188
+
189
+ ### Fixed
190
+
191
+ - **Soft-break text can be converted to a list:** a paragraph whose `A. …`, `B. …` items are separated by `<w:br/>` was
192
+ refused with `TARGET_NOT_FOUND`, and an explicit list request identical to typed markers was a no-op. In-run `\n`/`\t`
193
+ now serialize back to `<w:br/>`/`<w:tab/>`, including inside `w:delText`, so Reject All restores the breaks.
194
+ - **Localized `replacements` work in paragraphs with soft line breaks:** they were refused with `INVALID_OPERATION`.
195
+ A `find` within one line now applies and keeps the breaks; a `find` or `replace` that spans or adds a break is refused.
196
+ - **Generated bullets no longer reuse existing numbering IDs:** bullets generated into a document that already defined
197
+ `numId 1` continued an existing decimal list in Word. The bare runner and `applyRedlineToOxml` now allocate new IDs
198
+ from the supplied `numberingXml`, as `openDocx` already did.
199
+ - **Appending a table after a paragraph keeps its underline:** the paragraph was tracked as deleted and re-inserted as
200
+ plain text, so a prior same-author underline, or a `++underline++` sent with the table, was lost on Accept All. The
201
+ source paragraph is now kept in place.
202
+ - **Word's Reject All removes generated tables:** tracked tables were wrapped in one `w:ins` around `w:tbl`, which Word
203
+ does not recognize. Reject All left an empty table, and a table at the end of a document could hang Word. Tables now
204
+ use row-level `w:trPr/w:ins` with inserted cell content and marks, and `rejectTrackedChangesInOoxml` removes a table
205
+ once all of its rows are rejected. Confirmed in Word.
206
+
207
+ ### Tests
208
+
209
+ - New `tests/soft_break_list_conversion_tests.mjs`, `tests/soft_break_localized_replacements_tests.mjs`,
210
+ `tests/generated_bullet_numbering_collision_tests.mjs`, `tests/format_text_occurrence_tests.mjs` and
211
+ `tests/table_append_fidelity_tests.mjs`.
212
+
213
+ ### Known limitations
214
+
215
+ - Localized restore in the rejected view still refuses paragraphs with line breaks.
216
+ - The table-append path covers one paragraph followed by one table.
217
+ - Under `merge-same-author`, editing the text of a paragraph that carries your own pending formatting change still drops
218
+ that formatting on Accept All.
219
+
220
+ ## 0.8.3
221
+
222
+ ### Fixed
223
+
224
+ - **Reject All no longer leaves an empty paragraph after an inserted paragraph:** `modified: "<original>\n<new>"` on a plain
225
+ paragraph did not track the new paragraph's mark as inserted. It now does.
226
+ - **Reject All no longer merges source paragraphs after a list range replacement:** replacing a multi-paragraph list range
227
+ (`target` + `targetEnd`) collapsed the sources into one deleted paragraph, so Reject All gave results such as
228
+ `Bullet Root ABullet Insertion Anchor`, `AlphaGamma` for an empty middle paragraph, and lost both paragraphs of an
229
+ all-empty range. Each source paragraph, empty ones included, now keeps its own tracked-deleted paragraph. Confirmed in Word.
230
+ - **Inspection no longer reports historical paragraph properties as current:** `list`, `styleId` and `headingLevel` (and list
231
+ targeting) could read `numPr`, `pStyle` or `outlineLvl` stored under `w:pPrChange`. Only the paragraph's direct `w:pPr`
232
+ is read now.
233
+ - **`openDocx` list range replacement no longer renumbers or re-points lists:** deleted source paragraphs were remapped to a
234
+ new generic `numId` (Reject All restored them with the wrong list type), new items started a separate list (untouched
235
+ following items restarted at 1), and unused numbering definitions were added. Deleted source paragraphs now keep their
236
+ `numPr`, and a same-kind replacement reuses the source `numId`, leaving `numbering.xml` unchanged. Reuse applies when the
237
+ range uses one `numId` and the generated level formats match; format-changing edits still allocate new definitions, and
238
+ definitions created earlier in the same batch are not reused. Confirmed in Word.
239
+ - **Explicit list start values are applied:** generated list fragments without `xmlns:w` failed to parse in
240
+ `orchestration/list-structural-fallback.js`, so `7. Header` lost its `startOverride` and xmldom logged parse warnings.
241
+
242
+ ### Tests
243
+
244
+ - New `tests/list_reject_fidelity_tests.mjs` and `tests/facade_list_numbering_tests.mjs`, with the Word-authored fixture
245
+ `tests/fixtures/agentic-lists/nested-lists-source.docx`; extended inspection and list structural fallback suites.
246
+
247
+ ### Known limitations
248
+
249
+ - Plain paragraph to list conversion and list format changes are not yet supported canonically; see
250
+ `docs/plans/2026-09-30-canonical-list-operations.md`.
251
+ - `1. Header` to `1. Header` on a bare `document.xml` (not a packaged `.docx`) can fail with `RECEIPT_RECONCILIATION_FAILED`.
252
+
253
+ ## 0.8.2
254
+
255
+ ### Fixed
256
+
257
+ - **Replacement next to a hyperlink no longer pulls adjacent plain text into the link** ([#3](https://github.com/AnsonLai/docx-redline-js/issues/3)):
258
+ replacing `example.org` with `example.net` (localized `replacements` or full `modified`) in a hyperlink followed by a
259
+ plain `.` run produced a link showing `example.net.` after Accept All. The reconstruction-mode word diff treated
260
+ `example.org.` as one token spanning the hyperlink edge. Where a replacement's shared prefix or suffix reaches a wrapper
261
+ boundary inside the deleted text, it is now kept unchanged.
262
+ - **Reject All no longer reorders text around a manual line break** ([#4](https://github.com/AnsonLai/docx-redline-js/issues/4)):
263
+ replacing `tabbed` with `aligned` in `<tab>tabbed<w:br/>Line with ` rejected to `<tab>tabbedLine<break> with `, moving
264
+ deleted text across the break. Break and note-reference placeholders are now standalone word-diff tokens (new internal
265
+ `atomicChars` option), and change groups are split at references present on both sides. Covered by `tests/reconstruction_boundary_fidelity_tests.mjs`.
266
+
267
+ ## 0.8.1
268
+
269
+ ### Added
270
+
271
+ - **Header and footer editing:** add `part` to an operation (a selector `{ kind, type?, section? }` or a part path) to edit a
272
+ header or footer; `inspect().headersFooters` lists the parts with their text, the sections they apply to (including
273
+ inherited ones) and whether they contain fields. Body edits are unchanged and headers/footers are opt-in. Fields such as
274
+ PAGE are atomic (`FIELD_EDIT_REFUSED`), comments are refused (`COMMENT_IN_HEADER_FOOTER`), bad selectors fail closed
275
+ (`PART_NOT_FOUND`, `PART_AMBIGUOUS`), atomic batches roll back across body and parts, revision ids stay unique across the
276
+ package, receipts name the part, `accept`/`reject` also resolve revisions inside headers and footers, and
277
+ `validateDocxPackage` checks header/footer references, relationships and content types.
278
+ - **Resolve and reopen comment threads:** `doc.resolveComment(id, { resolved })` and the batch operation
279
+ `{ "type": "comment_resolve", "commentId": "0", "resolved": true }`, so the CLI can resolve through `apply --operations`.
280
+ Word resolves whole threads, so the root and every reply change together, whichever id is given. Only
281
+ `word/commentsExtended.xml` changes; `document.xml` stays byte-identical. Unknown ids fail with `COMMENT_NOT_FOUND`
282
+ (also in `preflight`).
283
+ - **Delete comments by id:** `doc.deleteComments({ ids })` and `docx-redline delete-comments --comment-id 3,4`. Deleting a
284
+ thread root also deletes its replies; unknown ids fail closed with `COMMENT_NOT_FOUND`.
285
+ - CLI capabilities `comment-resolve-operation-v1`, `delete-comments-by-id-v1` and `header-footer-parts-v1` (additive; the
286
+ contract version is unchanged).
287
+ - `services/package-parts.js`, a registry of package part paths, content types and relationship types checked against
288
+ Word-saved fixtures (`tests/fixtures/word-authored/`), and `npm run test:word:package`, which opens generated outputs in
289
+ real Word with repair disabled and checks comment counts, thread parents, resolved state and header/footer revisions
290
+ through Word's object model.
291
+
292
+ ### Fixed
293
+
294
+ - **Word "unreadable content" prompt on commented documents:** `word/commentsExtended.xml` was relabeled
295
+ `application/vnd.ms-word.commentsExtended+xml` on every save of any document that already had comments.
296
+ Word expects `application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml` and
297
+ offered to repair the file. The constant is corrected, an edit that does not change comment threading no
298
+ longer rewrites the extended part or its content type/relationship, and files already damaged by earlier
299
+ versions are repaired on the next save (`repairKnownContentTypes`, also exported). `validateDocxPackage`
300
+ now rejects the wrong type instead of accepting it. `tests/package_content_type_contract_tests.mjs` checks this
301
+ against a Word-authored content-type table on every OS.
302
+ - **Comment threads keyed on the last paragraph:** Word joins `comments.xml` to `commentsExtended.xml` and
303
+ `commentsIds.xml` through the `w14:paraId` of a comment's LAST paragraph. Inspection, package validation,
304
+ replies and delete-cascade used the first, so Word's own multi-paragraph threaded documents failed
305
+ validation and replies to them attached to the wrong key.
306
+ - **Edits no longer drop comment anchors:** a mid-paragraph edit of a commented paragraph dropped the trailing
307
+ `commentRangeEnd` and `commentReference` (markers at the very end of the paragraph text were only emitted when the
308
+ last diff part was an insert), leaving the comment unanchored and failing package validation. Found by
309
+ opening our output in real Word; covered by `tests/comment_marker_preservation_tests.mjs`.
310
+ - **Replies are now visible in Word:** `comment_reply` only wrote `comments.xml`/`commentsExtended.xml`, and Word
311
+ does not show a reply that has no body markers. Replies now get their own `commentRangeStart`/`commentRangeEnd`/
312
+ `commentReference` next to the thread's, laid out like Word writes them. A reply to a reply joins the root thread.
313
+ A parent with no body anchor fails with `PARENT_ANCHOR_NOT_FOUND`.
314
+ - **Replies to a resolved thread are resolved too**, as Word writes them (`reply-to-resolved.docx` fixture). They were
315
+ written open, leaving a thread that was half resolved.
316
+ - `commentsIds.xml` and `commentsExtensible.xml` are kept consistent with `comments.xml` on add/delete when present.
317
+ - `deleteComments` and `resolveRevisions` report `artifactsChanged` from a real diff of the package (they listed a fixed
318
+ set of parts and missed `commentsIds.xml`, `commentsExtensible.xml` and `[Content_Types].xml`).
319
+
320
+ ## 0.8.0
321
+
322
+ ### Universal Document Facade & Zero-Node Architecture
323
+
324
+ - **Universal `DocxDocument` & `openDocx`:** Implemented complete-DOCX lifecycle operations
325
+ (`openDocx`, `doc.inspect()`, `doc.applyOperations()`, `doc.resolveRevisions()`, `doc.deleteComments()`,
326
+ `doc.toUint8Array()`, `doc.getRevisionToken()`) on standard `Uint8Array` in `document/docx-document.js`,
327
+ re-exported directly from `index.js`. Enables complete-document redline workflows in any JavaScript
328
+ runtime without Node.js dependencies.
329
+ - **Pure-JS ZIP container (`fflate`):** Replaced `node:zlib` with lightweight, zero-dependency `fflate`
330
+ in `document/zip-archive.js`, providing cross-runtime raw DEFLATE / INFLATE compression and decompression
331
+ with full OOXML archive compatibility.
332
+ - **Pure-JS FIPS 180-4 SHA-256:** Created zero-dependency, synchronous SHA-256 implementation in
333
+ `core/sha256.js`, removing `node:crypto` from package revision token calculations while retaining
334
+ exact deterministic hash parity with standard NIST vectors.
335
+ - **Transparent XML DOM fallback:** Added `resolveDomParserConstructor()` and
336
+ `resolveXmlSerializerConstructor()` in `adapters/xml-adapter.js`, automatically utilizing native
337
+ `globalThis.DOMParser`/`globalThis.XMLSerializer` when present and falling back transparently to
338
+ `@xmldom/xmldom` in headless, server, edge, and sandbox environments without requiring manual provider setup.
339
+ - **Zero-dependency sandbox bundles:** Configured `scripts/build.mjs` with `esbuild` (`platform: 'neutral'`)
340
+ to produce single-file, zero-dependency standalone bundles in both ESM (`dist/docx-redline.bundle.js`)
341
+ and CommonJS (`dist/docx-redline.bundle.cjs`), exported as `@ansonlai/docx-redline-js/bundle` for direct
342
+ use in workflow engines (e.g., n8n Code nodes), Cloudflare Workers, AWS Lambda, Deno, and QuickJS sandboxes.
343
+ - **Strict runtime boundary enforcement:** Removed `node:zlib` and `node:crypto` from allowed external
344
+ imports in `tests/no_word_api_index_check.mjs`. Automated sandbox isolation tests
345
+ (`tests/sandbox_bundle_isolation_tests.mjs`) verify zero Node built-in imports and full document
346
+ reconciliation in restricted VM contexts without `process`, `Buffer`, or `require`.
347
+ - **Backward compatibility:** Preserved full API and `Buffer` compatibility for `@ansonlai/docx-redline-js/node`
348
+ consumers and `doc.toBuffer()`.
349
+
350
+ ## 0.7.2
351
+
352
+ ### Agent Surface Simplification
353
+
354
+ - **Extraction-first golden path:** Re-established the single deterministic two-turn
355
+ "extract-then-apply" sequence as the default agent workflow across `AGENTS.md`,
356
+ `docs/AGENT_FAST_START.md`, and `docs/SKILL_AUTHORING.md`. Eliminates the
357
+ upfront "choice tax" and model deliberation overhead.
358
+ - **Localized replacements after inspection:** Decoupled localized patching from
359
+ speculation. Agents inspect the target paragraph first, then supply compact
360
+ `replacements` instructions, achieving 60.6%–90.8% payload reductions without
361
+ speculative ambiguity risk.
362
+ - **Computed success gate:** Consolidated standard success verification onto
363
+ `completion: true` and non-null `outputPath`, removing multi-field verification
364
+ checklists from primary prompts while retaining full `change` audit evidence.
365
+ - **Advanced features relocated:** Speculative 1-turn apply, directional context
366
+ windows (`--context-range`), and anchor diagnostics are relocated to
367
+ `docs/AGENT_KNOWLEDGE_BASE.md` and CLI help as opt-in harness policies.
368
+ - **Observed-state restoration:** Restructured restoration as a clean request-
369
+ or hint-driven branch (`--view rejected` or `selection.hint`) rather than an
370
+ upfront classification decision.
371
+ - **Empirical surface evaluation:** Added synthetic replay benchmark
372
+ (`npm run benchmark:agent-surface`) and validation report documenting 100%
373
+ first-attempt task correctness and payload savings across three workflow policies.
374
+
375
+ ## 0.7.1
376
+
377
+ ### Reliability and Discovery Refinements
378
+
379
+ - Localized replacement matching now tries exact case-sensitive text first,
380
+ then a narrow ASCII-space/non-breaking-space equivalent match. Ambiguous
381
+ equivalent matches still fail closed, and unchanged boundary NBSPs are
382
+ retained to avoid incidental whitespace redlines.
383
+ - Added a narrow single-paragraph restore shortcut. A restore with a strong
384
+ rejected-view target may omit `modified` for verbatim restoration or provide
385
+ exact `replacements`; the CLI exposes the same path through `apply --restore
386
+ --target-id ID`. Ranges, captures, weak targets, and inferred companion
387
+ content remain excluded.
388
+ - Fixed rejected-view restore receipts so `resolvedTarget` text, fingerprint,
389
+ revision view, and match diagnostics describe the view actually used to
390
+ resolve the target.
391
+ - Fixed alternate-view search hints to honor `--revised`, `--table`, and
392
+ `--body`, and retained compact revision authors plus `deleted`/`inserted` cues
393
+ in `extract` output so empty revision-view paragraphs are self-explanatory.
394
+
395
+ ## 0.7.0
396
+
397
+ ### Agent Performance and Localized Editing
398
+
399
+ - **Localized exact replacements:** `redline`/`replace` operations may provide
400
+ `replacements: [{ find, replace, occurrence? }]` instead of a complete
401
+ `modified` paragraph. The compiler resolves the accepted-view source once,
402
+ applies non-overlapping exact replacements simultaneously, synthesizes the
403
+ canonical desired paragraph, and delegates to the unchanged OOXML engine.
404
+ - **One-turn speculative apply:** `docx-redline apply --find ... --replace ...`
405
+ can resolve a globally unique source literal without preliminary extraction.
406
+ `--search` plus signed `--context-range START:END` scopes visible headings and
407
+ nearby content; `--around N` is symmetric shorthand. Longer, distinctive
408
+ anchors are recommended because generic or repeated phrases widen the unioned
409
+ scope and may cost an ambiguity-recovery turn.
410
+ - **Fail-closed selection:** Missing anchors and patch sources, multiple eligible
411
+ paragraphs, ambiguous occurrences, overlaps, conflicts, and unsupported
412
+ localized shapes refuse mutation. A failed speculative command never writes
413
+ or truncates its output path.
414
+ - **Verified change evidence:** Localized operation results include a compact
415
+ `change` object with target location, contextual anchor/range, exact
416
+ replacement entries, bounded before/after excerpts, commit disposition, and
417
+ post-mutation accepted-view verification. Atomic rollback marks the evidence
418
+ non-committed and `rolled_back`.
419
+ - **CLI contract version 8:** Adds `localized-replacements-v1`,
420
+ `speculative-search-apply-v1`, and `localized-change-summary-v1`. Skills and
421
+ harnesses should pin version 0.7.0 and negotiate only the capabilities used.
422
+ - **Measured workflow reduction:** The checked benchmark records 50% fewer tool
423
+ turns and a 64.77%–78.98% smaller deterministic request-token proxy across
424
+ three literal-edit cases. Median native engine/I/O time remained effectively
425
+ neutral; provider reasoning and network latency were not estimated.
426
+
427
+ ### Compatibility
428
+
429
+ - Existing `modified` operations, strong targets, operations files/stdin,
430
+ tracked-change generation, package validation, rollback, comments,
431
+ Accept/Reject, and revision policies remain supported.
432
+ - Localized replacement fields and successful `change` evidence are additive.
433
+ Wrappers that adopt them must require CLI contract 8; integrations using only
434
+ earlier capabilities can continue using their existing operation shapes.
435
+ - The implementation remains OOXML-only and adds no runtime dependency or Word,
436
+ Office automation, COM, or proprietary document API requirement.
437
+
438
+ ## 0.6.0–0.6.2
439
+
440
+ ### Safety Fixes
441
+
442
+ - **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.
443
+ - **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.
444
+ - **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.
445
+ - **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.
446
+ - **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.
447
+ - **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.
448
+
449
+ ### New Features
450
+
451
+ - **Self-describing and bounded agent discovery (WP-00–WP-02)**: CLI contract version 6 adds command-specific JSON help, canonical redline/comment/restore examples, case-insensitive contextual search with `--around`/`--context`/`-C`, direct-hit pagination through `--limit` and `--after`, and deliberate `--all` opt-out. Unscoped inspection is capped at 20 direct records and a 48 KiB soft budget without truncating any returned `exactText`; extraction now distinguishes matches from context and prioritizes human-facing provision/heading references over machine paragraph ordinals.
452
+ - **Composable agent safety and compact output (WP-03–WP-04)**: CLI contract version 7 replaces the atomic-coupled agent profile capability with `agent-safety-profile-v2`. `--profile agent` now enforces complete-success exits while preserving progressive execution and the ordinary revision policy; `--atomic` and `--existing-revisions` compose explicitly. Compact CLI JSON keeps receipts only in the authoritative top-level array, omits exact-match diagnostics, summarizes successful equivalent-whitespace matches, and supports one-line `--compact` output without changing Node or standalone-runner results.
453
+ - **Packaged skill-authoring contract (WP-05)**: `docs/SKILL_AUTHORING.md` separates automation invariants from transaction/revision policy, file-versus-stdin transport, reviewer preferences, recovery-envelope behavior, and human-facing presentation. It requires contract/capability negotiation, focused contextual extraction, and library delegation while retaining the visible `AI Redliner` fallback without a new author guard.
454
+ - **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.
455
+ - **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.
456
+ - **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.
457
+ - **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.
458
+ - **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.
459
+ - **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.
460
+ - **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.
461
+
462
+ ## 0.5.1
463
+
464
+ ### Highlights & New Features
465
+
466
+ - **Cross-Author Revision Slicing (`existingRevisions: 'slice-cross-author'`)**: Adds native support for editing text inside another reviewer's pending tracked insertion without erasing their attribution or requiring prior acceptance.
467
+ - **Word-Native Insertion Slicing**: When inserting text inside another author's pending `<w:ins>`, the engine splits the outer carrier into sibling `<w:ins>` elements at the paragraph level (`[ins(A), ins(B), ins(A)]`), ensuring strict ECMA-376 schema compliance without illegal `ins/ins` nesting.
468
+ - **Word-Native Deletion Slicing**: When deleting text inside another author's pending `<w:ins>`, the engine nests `<w:del>` directly inside `<w:ins>` per ECMA-376 Part 1 `CT_RunTrackChange` Section 17.13.5.21 and Microsoft Word Desktop 365 native behavior.
469
+ - **Boundary & Straddle Deletions**: Deletions straddling baseline text and pending insertions cleanly partition into separate top-level and nested `<w:del>` containers while sharing unified event/author attribution.
470
+ - **Multi-Author Stacked Revisions**: Multiple reviewers can independently delete or insert content within the same carrier insertion without cross-author interference.
471
+ - **Cascading & Coalescing Lifecycles**: Full round-trip lifecycle parity with Microsoft Word Desktop:
472
+ - Rejecting Author A cleanly discards Author A's insertion and any dependent nested deletions by Author B.
473
+ - Accepting Author A unwraps the insertion into baseline text while leaving Author B's deletions pending against the baseline.
474
+ - Rejecting Author B restores Author B's deleted text within Author A's insertion, and coalesces adjacent split `<w:ins>` fragments back into a single continuous carrier.
475
+ - **CLI & Facade Integration**: Fully exposed via the `docx-redline` CLI (`--existing-revisions slice-cross-author`), Node facade (`openDocx`), and batch operation runner.
476
+
477
+ ### Non-breaking Changes & Improvements
478
+
479
+ - **Validation Update (`validateRedlineOoxml`)**: Refined `NESTED_REVISION` checks to permit direct `<w:ins><w:del>...</w:del></w:ins>` nesting (standard ECMA-376 and Word Desktop behavior), while continuing to strictly reject `ins/ins`, `del/del`, and `del/ins` nesting.
480
+ - **Slicing Round-Trip Guard**: Cross-author surgical edits now preserve whitespace-only insertions (including ordinary-space replacements for NBSP characters beside hyperlinks) and verify the exact accepted-view text before reporting success. A mismatch fails closed with `PATCH_ROUNDTRIP_MISMATCH` and returns the original OOXML unchanged.
481
+ - **Hyperlink-Adjacent Replacement Anchoring**: Paired replacements immediately before or after a hyperlink now retain a stable insertion point after the deletion run is split, preventing qualifiers from being relocated past the hyperlink or following formatted runs.
482
+ - **Multiple Same-Run Insertions**: Insertion-only slicing operations with multiple edit points now apply from right to left against a refreshed live span index, preventing an earlier run split from relocating later insertions.
483
+ - **Insertion Stress Hardening**: Slicing now detects edge whitespace changes exactly, uses a character-local insertion-only diff when the original text is an exact subsequence of the modified text, and coalesces new text into an existing same-author carrier when foreign revisions are also present. This prevents repeated phrases from relocating insertions and prevents invalid `w:ins/w:ins` nesting in mixed-author paragraphs.
484
+ - **Preflight Inspection**: `preflightOperations` now inspects and validates `slice-cross-author` batches, reporting pending foreign-author carrier targets as `ready` instead of `EXISTING_REVISIONS`.
485
+ - **Test Suite Expansion**: Added 7 new test suites covering 36 Word Desktop COM golden fixtures, carrier splitting invariants, the SYN-01..12d synthetic test matrix, PKG-01..06 strict package differential replay, the repeated-text/hyperlink whitespace regression, 76 deterministic insertion stress scenarios, and 12 replacement-anchor lifecycle scenarios (expanding the suite from 88 to 95 passing suites).
486
+
487
+ ## 0.5.0
488
+
489
+ ### ⚠️ Breaking changes
490
+
491
+ The following changes can require caller or contributor updates. No public
492
+ export or valid existing function signature was removed.
493
+
494
+ #### Runtime and result-contract changes
495
+
496
+ - **Canonical paragraph text:** `getParagraphText(...)` now returns the
497
+ accepted/current view instead of the earlier simplified `w:t`/`w:tab`
498
+ concatenation. It excludes deleted and `w:moveFrom` content and includes
499
+ structural breaks, soft hyphens, and non-breaking hyphens.
500
+ **Migration:** regenerate compared/cached text with the upgraded library. Use
501
+ `extractCanonicalParagraphText(...)` when accepted/rejected-view semantics
502
+ are intended; integrations that require the former raw traversal must retain
503
+ their own legacy extractor before upgrading.
504
+ - **Paragraph fingerprints:** fingerprints now incorporate canonical paragraph
505
+ text. Stored fingerprints can become stale for paragraphs containing
506
+ revisions, moves, breaks, soft hyphens, or non-breaking hyphens.
507
+ **Migration:** do not carry fingerprints across this upgrade; re-extract the
508
+ document and regenerate target descriptors.
509
+ - **Invalid operations:** malformed or field-incompatible document-operation
510
+ objects now return a structured `INVALID_OPERATION` error at the runner
511
+ boundary instead of reaching later failure/no-op paths.
512
+ **Migration:** validate operation files against the published schema and
513
+ handle `status === 'error'` plus `error.code === 'INVALID_OPERATION'`.
514
+ - **Commented paragraph deletion:** deleting a complete paragraph that contains
515
+ an existing comment now fails with `COMMENTED_CONTENT_DELETE` instead of
516
+ producing OOXML with dangling comment references.
517
+ **Migration:** preserve or deliberately remove/relocate the comment before
518
+ deleting the paragraph, and handle the structured error. Atomic callers keep
519
+ the original document unchanged.
520
+ - **Comment anchor failures:** missing or ambiguous comment anchors now return
521
+ `ANCHOR_NOT_FOUND` or `AMBIGUOUS_ANCHOR` and fail/roll back an atomic batch;
522
+ they are no longer reported as successful `no_change` operations.
523
+ **Migration:** treat these codes as failed operations and use a unique exact
524
+ anchor or stricter target descriptor before retrying.
525
+ - **Stricter package validation:** DOCX package validation now rejects
526
+ unbalanced comment ranges, dangling references/usages, orphan definitions,
527
+ and duplicate comment-definition IDs that earlier validation could allow.
528
+ **Migration:** repair the comment anchors and `word/comments.xml` definitions
529
+ before applying or validating further changes.
530
+
531
+ #### Development workflow change
532
+
533
+ - **Parallel tests by default:** `npm test` now runs up to four isolated child
534
+ processes concurrently. This does not change the published runtime API, but
535
+ contributor automation that depends on test execution order or shared files
536
+ must be updated. **Migration:** make tests independent or set
537
+ `DOCX_TEST_CONCURRENCY=1` for the previous serial scheduling behavior.
538
+
539
+ Strict targeting remains opt-in on existing low-level mutation APIs; it is the
540
+ default only on the newly added Node facade and CLI.
541
+
542
+ ### Deprecations and future breaking changes
543
+
544
+ - Replacing an existing numbering part without a merge callback now emits a
545
+ deprecation warning. The Node facade and CLI already merge safely by default.
546
+ **This remains supported in this release but will become an error in the next
547
+ major version.** Migrate low-level callers to
548
+ `mergeNumberingXmlBySchemaOrder` before that release.
549
+ - **Permissive target resolution deprecation (`AMBIGUOUS_TARGET_HEURISTIC_USED`):**
550
+ Lower-level document operation runners currently default to permissive targeting.
551
+ In v1.0.0, application will default to `strictTargets: true`. In this release,
552
+ when permissive resolution encounters multiple identical candidate paragraphs and
553
+ heuristically chooses the first candidate, it emits an `AMBIGUOUS_TARGET_HEURISTIC_USED`
554
+ warning with candidate count and migration guidance.
555
+ **Migration:** Disambiguate operations using `paragraphId`, `index`, `occurrence`,
556
+ or `fingerprint` and pass `{ strictTargets: true }`.
557
+
558
+ ### ℹ️ Noteworthy Default & Usability Changes (Non-breaking)
559
+
560
+ The following improvements update default behaviors to maximize agent speed, reduce friction, and eliminate unnecessary errors during iterative editing. These changes are **not technically breaking changes** (no public APIs or exports were removed, and all existing options remain configurable), but represent important behavioral refinements that callers should note:
561
+
562
+ - **Fallback Author Default (`'AI Redliner'`):** `setDefaultAuthor`, `node/cli.js`, and `openDocx` now default to `'AI Redliner'` (configurable via the `DOCX_REDLINE_AUTHOR` environment variable or `--author`). The CLI `apply` command and core APIs no longer error with `AUTHOR_REQUIRED` when operations omit an author.
563
+ - **Output Overwrite by Default:** Destination files provided via `--output` now overwrite by default instead of failing with `OUTPUT_EXISTS`. Callers wanting protection against accidental overwrites can pass `--no-overwrite` or `--no-clobber`. The source document remains protected and is never overwritten unless `--in-place` is explicitly passed.
564
+ - **Replacement Event Pairing (`pairReplacements: true` by default):** Adjacent `<w:del>` and `<w:ins>` revisions now default to sharing linked revision metadata and identical timestamps so Microsoft Word groups them as a single replacement in the Reviewing Pane. Pass `pairReplacements: false` for independent revision timestamps.
565
+ - **Structured Content Auto-Detection (`structuredContent: true` by default):** Markdown tables, headings (`#`), and lists in replacement text automatically render as native Word elements (`w:tbl`, `w:pStyle`, `w:numPr`). Single outline-numbered legal clauses (e.g. `13.2.1.1`) continue to be treated as ordinary paragraphs without spurious list conversion. Pass `structuredContent: false` to treat replacement text strictly as plain text.
566
+ - **Progressive Batch Mode (`atomic: false` default, `atomic: true` opt-in):** Batch operations and CLI `apply` now apply edits progressively by default (`atomic: false`): valid edits commit directly, while failing edits report structured errors in `results` for faster debugging. Pass `{ atomic: true }` (or `--atomic` on the CLI) for all-or-nothing rollback when any operation fails.
567
+ - **Clean Direct Edits (`generateRedlines: false` / `--no-redlines`):** Prominently documented that tracked redlines are not always the preferred method. Callers can pass `generateRedlines: false` (or `--no-redlines` on the CLI) for clean execution drafts, document restructuring, or minor typo fixes without tracked changes markup.
568
+ - **Existing Revisions Policy (`existingRevisions: 'merge-same-author'` by default):** When editing a paragraph that already contains tracked changes from the same author, prior revisions by that author are merged against the pre-revision baseline (prior changes by that author are reverted and re-diffed to the new modified text), avoiding revision accumulation and nested markup. If a paragraph contains tracked changes from another reviewer, the operation fails closed with `EXISTING_REVISIONS` to protect third-party review marks. Commented revision content fails closed with `COMMENTED_CONTENT_MERGE` so comment anchors cannot be removed or orphaned. Callers can explicitly pass `'accept-all-first'` to normalize prior revisions or `'reject-input'` to refuse editing any revised paragraph.
569
+ - **Inline One-Liner CLI Edits:** Added `--target <text>` with `--modified <text>` or `--comment <text>` on `docx-redline apply` for fast 1–2 edit workflows without creating a JSON operations file.
570
+
571
+ ### Added
572
+
573
+ - Added **Mutation Receipts** (`receipt` on `DocumentOperationResult` and `receipts`
574
+ on `BatchOperationResult`). Every operation execution returns structured,
575
+ commit-aware telemetry capturing durable revision IDs (with kind and target part),
576
+ comment IDs, numbering IDs, relationship IDs, affected targets, and warnings.
577
+ Supports `not_attempted`, `applied`, `refused`, `no_change`, and `rolled_back`
578
+ dispositions.
579
+ - Added **Commit-Aware Output Reconciliation Oracle** (`reconcileReceiptsAgainstOutput`).
580
+ Before completing any transaction, reported durable IDs are checked against a
581
+ fresh parse of the generated output OOXML parts. Any reconciliation discrepancy
582
+ immediately fails the transaction and triggers atomic rollback.
583
+ - Exported receipt primitives: `ReceiptCollector`, `createEmptyReceipt`, and
584
+ `reconcileReceiptsAgainstOutput` along with `MutationReceipt` and
585
+ `MutationReceiptRevisionItem` TypeScript declarations.
586
+ - Added **Paragraph Mark Revisions** (`w:pPrChange`, `markParagraphMarkInserted`,
587
+ `markParagraphMarkDeleted`) ensuring whole-paragraph deletions and insertions
588
+ cleanly track paragraph mark lifecycles for full Accept All / Reject All symmetry.
589
+ - Added **Paragraph Boundary Mutation Validation** (`validateParagraphBoundaryMutation`)
590
+ guarding against invalid cross-paragraph boundary merges.
591
+
592
+ - Added `analyzeStructuredContent(...)` and `planStructuredReplacement(...)`
593
+ for agent-authored attachments and schedules containing mixed headings,
594
+ paragraphs, lists, and tables. The planner returns typed blocks and produces
595
+ one atomic `structuredContent` operation only when the Markdown is valid.
596
+
597
+ - Added session-scoped paragraph metadata indexing for canonical text,
598
+ normalized text, paragraph IDs, fingerprints, table context, and document
599
+ indexes, plus deterministic targeting and route-profiling benchmarks.
600
+ - Added a shared list-marker grammar and parsed list-item vocabulary used by
601
+ pipeline analysis, orchestration, normalization, and list targeting.
602
+ - Added an internal reconciliation capability matrix and opt-in route
603
+ instrumentation. Public reconciliation result shapes and route selection
604
+ policy remain unchanged.
605
+
606
+ - Added `scripts/extract_text.mjs` and `scripts/apply_changes.mjs` as thin
607
+ compatibility entrypoints for legacy skill installations. They delegate to
608
+ the supported CLI, retain strict atomic validated writes, and accept legacy
609
+ operation files with a top-level `changes` array.
610
+ - Added typed operations, per-operation authors, strict target descriptors,
611
+ deterministic preflight diagnostics, and auditable resolution metadata.
612
+ - Added `inspectDocumentParts(...)`, a structured document inventory using the
613
+ same canonical text extractor as targeting and ingestion.
614
+ - Added `@ansonlai/docx-redline-js/node`. `openDocx(...)` applies batches to
615
+ complete DOCX buffers transactionally and validates before commit.
616
+ - Added the cross-platform `docx-redline` CLI with JSON `inspect`, `extract`,
617
+ `preflight`, `apply`, `accept`, `reject`, `delete-comments`, and `validate`
618
+ commands, plus a published operation-file JSON Schema.
619
+
620
+ ### Behavior and reliability
621
+
622
+ - Explicit structured replacements now reject missing Markdown table separator
623
+ - Generated Markdown tables repeat their header row and prevent logical data rows from splitting across pages.
624
+ rows, missing data rows, and inconsistent column counts with
625
+ `STRUCTURED_CONTENT_INVALID` instead of inserting literal pipe-delimited text.
626
+ - Tables created inside mixed replacements are tracked once at block scope,
627
+ avoiding nested revisions while keeping Accept All and Reject All symmetric.
628
+
629
+ - Target preflight and live mutation reuse one document-scoped paragraph index;
630
+ strict duplicate detection and resolution metadata retain their prior
631
+ semantics while avoiding repeated whole-document text traversal.
632
+ - Revision-ID seeding now walks the DOM with child/sibling pointers instead of
633
+ allocating an array containing every element.
634
+ - Single-source-paragraph list expansion uses the focused list generator
635
+ directly. Multi-paragraph marked-list edits deliberately retain the legacy
636
+ run-aware reconciliation pipeline for structural parity.
637
+ - Fixed paragraph-to-list expansion when a manually numbered heading carries
638
+ `w:numId w:val="0"`. Numbering suppression is no longer reused as a generated
639
+ list ID, so inserted bullet and numbered items receive valid positive IDs.
640
+ - Tracked heading-to-list replacements now keep the deleted source heading in
641
+ its own paragraph and track the paragraph marks of every inserted list item.
642
+ This prevents deleted heading text from joining the first item and preserves
643
+ exact Accept/Reject structure without adding a full-document sentinel
644
+ paragraph.
645
+ - Inserted list items now inherit source font family, size, language, and
646
+ related script typography without inheriting heading-only bold, underline,
647
+ italic, or strike formatting unless requested by the replacement markdown.
648
+ - Canonical-only table-cell targeting now consumes the shared paragraph-text
649
+ extractor, while offset- and sentinel-producing walkers remain specialized.
650
+ Their accepted visible projections now agree on soft hyphens as well as tabs,
651
+ breaks, hyperlinks, and non-breaking hyphens.
652
+
653
+ - Comment preflight and application now share one anchor resolver. Exact
654
+ matches take priority, unique ordinary-space/NBSP differences preserve raw
655
+ offsets, and missing or repeated anchors return structured
656
+ `ANCHOR_NOT_FOUND` or `AMBIGUOUS_ANCHOR` errors.
657
+ - Full-document operation batches now share one live DOM and revision-ID
658
+ allocator, reducing complete-document parsing and serialization from once per
659
+ operation to once per changed batch. Scoped reconciliation engines and their
660
+ targeting decisions are unchanged.
661
+ - Each live-session operation uses a DOM and allocator savepoint. Errors and
662
+ reported no-ops restore the savepoint; atomic failures and all-no-op batches
663
+ still return the exact original XML without serializing it.
664
+ - Decomposed the standalone document-operation runner into a stable 13-line
665
+ compatibility facade plus focused session, applier, batch-orchestrator,
666
+ heuristic, and OOXML-mutation modules. Runtime exports and declarations are
667
+ unchanged, and operation internals now use leaf imports instead of importing
668
+ the root entry point.
669
+ - Package comment IDs are seeded from existing anchors and definitions, and
670
+ the Node facade merges numbering without discarding prior definitions.
671
+ - Canonical paragraph text consistently handles revisions, moves, tabs,
672
+ breaks, soft hyphens, and non-breaking hyphens; see the breaking-change note
673
+ above for `getParagraphText(...)` and fingerprint compatibility.
674
+ - Structured inspection now explicitly supports the `current` view as the
675
+ accepted/current document view and resolves comment ranges spanning multiple
676
+ paragraphs as one exact anchor string.
677
+ - Transactional no-op results preserve the original DOCX bytes exactly,
678
+ including when the same facade instance has already committed an edit.
679
+
680
+ ### Testing and development
681
+
682
+ - Added focused and semantic visual-failure regressions for suppressed
683
+ numbering, paragraph separation, paragraph-mark revisions, selective
684
+ typography inheritance, and exact accepted/rejected paragraph sequences,
685
+ plus seeded fuzz variants across list length, marker, font, and size.
686
+ Added the independently specified
687
+ `legal-suppressed-heading-to-bullet-list` and
688
+ `legal-structured-attachment-mixed-blocks` Microsoft Word differential
689
+ fixtures; the synthetic catalogue now contains 49 cases and 49 task types.
690
+
691
+ - The JavaScript test runner now uses bounded asynchronous subprocess workers,
692
+ capped at four by default, while retaining one fresh Node process per file,
693
+ sorted reporting, timeouts, captured diagnostics, and failure-marker checks.
694
+ Set `DOCX_TEST_CONCURRENCY=1` for serial reproduction.
695
+ - Added `npm run benchmark:tests` and a Phase 6 runner regression. The checked
696
+ benchmark reduced median suite time from 18.02 seconds to 7.96 seconds
697
+ (55.80%); 20 consecutive parallel runs and a separate serial run passed all
698
+ 63 files. Parallel c8 collection remains valid at 89.90% statements/lines,
699
+ 77.40% branches, and 93.10% functions.
700
+
701
+ - Added Phase 3 targeting/traversal, Phase 4 list/text parity, and Phase 5 route
702
+ compatibility regressions. Together with the Phase 6 runner regression, the
703
+ suite now contains 63 passing test files.
704
+ - Added `npm run benchmark:targeting` and `npm run profile:routes`. The checked
705
+ 10,000-paragraph benchmark showed no single-operation regression and a 47.16x
706
+ Node median improvement for 100 cached resolutions. The equivalent native
707
+ Chrome benchmark showed no single-operation regression and a 36.45x batch
708
+ improvement. All 47 Microsoft Word differential fixtures and all 60 real-
709
+ document Word corpus scenarios passed after the routing changes.
710
+
711
+ - Added subprocess coverage for legacy wrapper selection, operation-file
712
+ compatibility, successful output, atomic anchor failure, source-byte
713
+ preservation, and protection of pre-existing output files.
714
+ - Added a live-session accuracy and instrumentation suite covering accepted and
715
+ rejected text, structural validity, sequential equivalence, comments, lists,
716
+ tables, highlights, one-parse/one-serialize execution, exact no-ops, and
717
+ atomic rollback. Added `npm run benchmark:session`; the checked accuracy-first
718
+ benchmark reduced parse/serialize counts from 10/10 to 1/1 and measured a
719
+ 1.58x median speedup with per-operation savepoints retained.
720
+ - Added a Phase 2 architecture regression covering facade export identity,
721
+ direct leaf imports, exact session rollback, isolated context commit, and
722
+ stable comment-first scheduling.
723
+ - Added five edge-case suites for canonical text views, cross-paragraph comment
724
+ inspection, table/list/reference context, sequential package transactions,
725
+ selective multi-author cleanup, ZIP rejection behavior, CLI filters, JSON
726
+ exit contracts, and destructive-output safeguards. Together with the Phase 2
727
+ Phase 1 and Phase 2 regressions, these established the earlier 57-file
728
+ baseline expanded by the subsequent performance phases.
729
+
730
+ ## 0.3.0
731
+
732
+ ### Breaking changes
733
+
734
+ - Malformed OOXML no longer escapes public transform APIs as a raw parser
735
+ exception. In particular, revision accept/reject and comment-deletion callers
736
+ that previously used `try`/`catch` must now inspect `status === 'error'` and
737
+ `error.code === 'PARSE_ERROR'` on the returned result.
738
+ - Caller content is no longer sanitized by default. Pass `sanitizeInput: true`
739
+ to remove a standalone leading assistant-preface line. Dollar-delimited text
740
+ and literal `\\n` / `\\r\\n` sequences are never removed implicitly.
741
+ - The exported `sanitizeAiResponse` helper no longer removes dollar-delimited
742
+ spans or converts literal `\\n` / `\\r\\n` sequences. Direct callers that
743
+ relied on those transformations must perform them explicitly.
744
+ - `applyOperationsToDocumentXml` is atomic by default. Failed batches return
745
+ the original document with `rolledBack: true`; pass `atomic: false` to retain
746
+ partial-result behavior.
747
+ - Missing multi-line targets now return `TARGET_NOT_FOUND` instead of being
748
+ indistinguishable from a no-op. Stale batch anchors also return
749
+ `TARGET_NOT_FOUND` instead of falling back to a visibly different paragraph.
750
+
751
+ ### Behavior changes and fixes
752
+
753
+ - Revision IDs are now allocated per document. Near-limit prior IDs restart in
754
+ a safe low range, and unrelated bookmark/relationship IDs no longer seed the
755
+ revision counter.
756
+ - Concurrent calls with explicit authors keep their revision attribution
757
+ isolated from process-global defaults.
758
+ - `existingRevisions: 'accept-all-first'` now preserves the original OOXML when
759
+ the requested edit is a no-op.
760
+ - Added `existingRevisions: 'accept-all-first-keep-normalized'` for callers that
761
+ explicitly want accepted existing revisions returned even when no redline is
762
+ added.
763
+ - Diff output is deterministic by default. Inputs exceeding 262,144 unique
764
+ tokens return `DIFF_TOKEN_LIMIT` with the original OOXML unchanged instead of
765
+ risking truncated text; leading whitespace is preserved exactly.
766
+ - Reconstruction preserves structural line breaks and body section-property
767
+ placement. A target that names only part of a reconstruction range now
768
+ returns `PARTIAL_TARGET` instead of risking deletion of untargeted content.
769
+ - Paragraph targeting and reconstruction now preserve leading, middle, and
770
+ trailing `w:tab` elements as literal tab characters instead of omitting or
771
+ trimming them.
772
+ - Adjacent redline edits now preserve complex-field instructions, begin/
773
+ separate/end markers, and unchanged cached display results in their original
774
+ field boundary. Field instructions remain inert and are not evaluated.
775
+ - Added structured XML parse results and result-returning ingestion helpers.
776
+ - Hardened deterministic diff token handling and leading-whitespace fidelity.
777
+
778
+ ### Testing and development
779
+
780
+ - Expanded the desktop Word differential from 20 to 25 cases, adding structural
781
+ checks for bookmarks, internal hyperlinks, mixed formatting, content
782
+ controls, and tables.
783
+ - Expanded the desktop Word differential from 25 to 28 cases with structural
784
+ tabs and a locked PAGE field, and added tab/field shapes to focused and fuzz
785
+ regression testing.
786
+ - Expanded the desktop Word differential from 28 to 33 cases with comments,
787
+ footnotes, endnotes, headers/footers, and an external hyperlink.
788
+ - Extended the deterministic script-only DOCX packager with opt-in related
789
+ parts, relationship/content-type validation, reusable fixture constructors,
790
+ and SHA-256 verification that supplied parts remain byte-identical. Runtime
791
+ library code and dependencies are unchanged.
792
+ - Added validated task, structure, oracle, and manual-review metadata across the
793
+ 33 synthetic and 20 SuperDoc Word cases. `npm run report:word:coverage`
794
+ produces a deterministic coverage matrix with explicit high-priority gap
795
+ dispositions, while `npm run review:word:prepare` creates a pending rotating
796
+ human-review manifest without self-certifying visual results.
797
+ - Added detailed production-function coverage reporting and a checked per-file
798
+ regression baseline. Five behavior-focused Phase 3 suites exercise numbering
799
+ collisions/remapping, every reconciliation route, list construction and
800
+ fallback, patch/format boundaries, table decisions, pipeline modes, and
801
+ standalone rollback/highlight paths. Production function coverage moved from
802
+ 437/540 to 496/542 and all reachable P0 functions are covered; no runtime
803
+ behavior or compatibility contract changed.