@ansonlai/docx-redline-js 0.9.0 → 0.10.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.
@@ -71,6 +71,8 @@ instructions that will exist in another clone or in CI.
71
71
  | Full-document operation scheduling or rollback | `services/document-operation-applier.js`, `services/batch-operation-orchestrator.js`, `services/document-operation-session.js` | `tests/standalone_operation_runner_tests.mjs`, `tests/performance_phase1_session_tests.mjs` |
72
72
  | DOCX ZIP wiring or CLI behavior | `node/docx-document.js`, `node/zip-archive.js`, `node/cli.js` | `tests/docx_package_facade_tests.mjs`, `tests/node_zip_archive_tests.mjs`, `tests/agent_cli_tests.mjs` |
73
73
  | Validation, receipts, and lifecycle oracles | `core/redline-validation.js`, `services/receipt-collector.js`, `services/revision-comment-management.js` | `tests/redline_validation_tests.mjs`, `tests/mutation_receipt_tests.mjs`, `tests/roundtrip_oracle_tests.mjs` |
74
+ | Existing-revision policy resolution (`auto`) | `core/word-xml.js` (`resolveExistingRevisionsPolicy`), `services/document-operation-mutations.js`, `services/operation-preflight.js` | `tests/existing_revisions_auto_tests.mjs`, `tests/existing_revisions_auto_paths_tests.mjs` |
75
+ | Comparing a committed document with a prepared one | `services/inspection-comparison.js` | `tests/inspection_comparison_tests.mjs` |
74
76
 
75
77
  Run one focused suite with `node tests/<name>.mjs`. Run `npm test` only when the
76
78
  change crosses several subsystems or before release-level handoff. The Word COM,
@@ -163,6 +165,14 @@ insertions and deletions inside their pending insertion. Pass
163
165
  via CLI) to normalize all prior revisions first, or `'reject-input'` to refuse any
164
166
  paragraph with open revisions. Use `'accept-all-first-keep-normalized'` only when
165
167
  accepted revisions should be returned as a real change even on a no-op edit.
168
+ `existingRevisions: 'auto'` (or `--existing-revisions auto`, capability
169
+ `existing-revisions-auto-v1`) chooses per target: `'merge-same-author'` when the
170
+ target has no tracked changes or only the operation author's, otherwise
171
+ `'slice-cross-author'` (unattributed changes count as another author's).
172
+ Preflight reports the resolved policy. `auto` opts into cross-author slicing
173
+ wherever it is needed, so choose it only when editing inside other reviewers'
174
+ pending insertions is intended for the whole batch; it never accepts or rejects
175
+ anyone's revisions.
166
176
  Same-author merging also fails with `COMMENTED_CONTENT_MERGE` when the revised
167
177
  paragraph contains comment anchors, because reverting the prior revision could
168
178
  remove or orphan those comments. Resolve the comments before re-editing.
@@ -375,6 +385,22 @@ const inspection = inspectDocumentParts({ documentXml, commentsXml, numberingXml
375
385
  Reuse `exactText` plus `paragraphId` or `fingerprint` in an operation. Computed
376
386
  list labels and excerpts are for display, not replacements for exact targets.
377
387
 
388
+ ### Verify a committed document against the prepared one
389
+
390
+ ```js
391
+ import { inspectDocumentParts, compareDocumentInspections } from '@ansonlai/docx-redline-js';
392
+ const check = compareDocumentInspections(inspectDocumentParts(preparedParts), inspectDocumentParts(committedParts));
393
+ // { matches, differences: [{ path, expected, actual }] }, at most the first 10
394
+ ```
395
+
396
+ Use it when a host writes a prepared package through its own API (for example a
397
+ Word add-in's `insertOoxml`) and then reads the result back. It compares paragraph
398
+ text, table position, list labels, review segments (kind, author, position;
399
+ adjacent same-author segments merged) and comments (text, author, anchor,
400
+ resolved state, thread parent). It ignores revision ids, dates, rsids, paragraph
401
+ ids, comment ids and order, and run boundaries. A mismatch is a signal to
402
+ re-inspect, not something to retry blindly.
403
+
378
404
  ### Safely edit a complete DOCX in Node
379
405
 
380
406
  ```js
@@ -547,7 +573,7 @@ docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --n
547
573
 
548
574
  Key CLI defaults and behaviors:
549
575
  - **Author**: Automatically defaults to `'AI Redliner'` (overridable via `--author` or `DOCX_REDLINE_AUTHOR` environment variable).
550
- - **Existing revisions**: Defaults to `'merge-same-author'`. Pass `--existing-revisions slice-cross-author` to edit inside another reviewer's pending insertions with native carrier slicing.
576
+ - **Existing revisions**: Defaults to `'merge-same-author'`. Pass `--existing-revisions slice-cross-author` to edit inside another reviewer's pending insertions with native carrier slicing, or `--existing-revisions auto` to slice only the targets that carry another reviewer's changes.
551
577
  - **Overwrite behavior**: Destination files provided via `--output` overwrite by default. To protect existing destination files, pass `--no-overwrite` or `--no-clobber`. The source document is never overwritten unless `--in-place` is specified.
552
578
  - **Tracked changes**: Defaults to `generateRedlines: true`. When clean direct text is needed, pass `--no-redlines`.
553
579
  - **Atomic rollback (optional)**: Operations apply progressively by default (`atomic: false`). For all-or-nothing transactional rollback where any error halts and reverts all changes, pass `--atomic`.
@@ -776,7 +802,7 @@ When the CLI or runner returns an error code, follow these specific recovery act
776
802
  | `REVISION_ORDER_CONFLICT` | A text rewrite and formatting/highlight operation overlap the same source. | Consolidate or split the work at an intentional created-content dependency; do not try a different arbitrary order. |
777
803
  | `LIST_FORMAT_COMPOSITION_CONFLICT` | A `redline` and a `list-format` on the same paragraph do not compose (`reason`: `marker-changed` with `sourceMarker`/`modifiedMarker`, `redline-unchanged`, `redline-shape` for a multi-paragraph, ranged, anchored, `list-change` or structured redline), or a `list-format` with `scope: 'list'` rebinds an item another operation also writes (`list-rebind`, `rebindingOperationIndexes`). Detected at batch compilation; nothing was written. | `recovery.action: split_text_and_list_changes` (`recovery.hint`). Keep the typed marker byte-identical in the redline (the list-format strips it) and express a new label through `list-format`; drop a no-op redline; send other writes to rebound items in a separate apply or use `scope: 'range'`. |
778
804
  | `CAPTURE_FANOUT_CONFLICT` | Multiple mutating consumers share one capture without distinct selectors. | Give consumers distinct selectors, chain them explicitly, or split the batch. |
779
- | `EXISTING_REVISIONS` | Target paragraph contains tracked changes from another author. | Fails closed to protect third-party review marks. If editing inside that reviewer's pending insertion is intended, pass `--existing-revisions slice-cross-author` (or `existingRevisions: 'slice-cross-author'`). Do not pass `accept-all-first` without explicit user authorization. |
805
+ | `EXISTING_REVISIONS` | Target paragraph contains tracked changes from another author. | Fails closed to protect third-party review marks. If editing inside that reviewer's pending insertion is intended, pass `--existing-revisions slice-cross-author` (or `existingRevisions: 'slice-cross-author'`; `'auto'` does this per target). Do not pass `accept-all-first` without explicit user authorization. |
780
806
  | `PATCH_ROUNDTRIP_MISMATCH` | A cross-author surgical edit did not reconstruct the requested modified text exactly, or a localized `replacements` operation would not leave the compiled accepted text in the target (checked before commit on every path). | Treat the operation as unapplied. Re-extract the exact paragraph text and split the edit into a narrower operation that does not cross the reported structural boundary. |
781
807
  | `FOREIGN_PARAGRAPH_MARK_DELETION` | A normal edit attempted to write into a paragraph wholly deleted by another reviewer. | Use an explicit `restore` operation if the user intends to counterpropose that paragraph; otherwise leave the deletion unresolved. |
782
808
  | `RESTORATION_STATE_REQUIRED` / `RESTORATION_COUNT_MISMATCH` | A `restore` target is not a wholly foreign-deleted paragraph, or its replacement count does not match the paragraph range. | Re-inspect the document and target the deleted paragraph by stable descriptor; provide exactly one replacement string per source paragraph. |
@@ -885,10 +911,10 @@ reconstruct the architecture by repeatedly listing the repository.
885
911
  | `generateRedlines` | `boolean` | `true` | When `true`, emits Word-native tracked changes (`w:ins`/`w:del`). When `false`, applies direct text edits without revision markup. **Note: Redlines are not always the preferred method** — pass `generateRedlines: false` (or `--no-redlines` via CLI) when producing clean execution drafts, restructuring documents, or when revision clutter is unwanted. |
886
912
  | `author` | `string` | `'AI Redliner'` | Reviewer/author name stamped on generated tracked changes and comments. Overridable via `DOCX_REDLINE_AUTHOR` environment variable. |
887
913
  | `atomic` | `boolean` | `false` | Batch transaction mode. By default (`false`), valid edits are applied directly and failing operations report errors. When `true`, any operation failure rolls back the entire batch to the original document state (`rolledBack: true`, `hasChanges: false`). Use `atomic: true` (or `--atomic` in CLI) for high-assurance workflows. |
888
- | `structuredContent` | `boolean` | `true` | Auto-detects Markdown tables, headings (`#`), and lists in replacement text and renders them as native Word elements (`w:tbl`, `w:pStyle`, `w:numPr`). Pass `false` to treat replacement text strictly as plain text. |
914
+ | `structuredContent` | `boolean` | `true` | Auto-detects Markdown tables, headings (`#`), and lists in replacement text and renders them as native Word elements (`w:tbl`, `w:pStyle`, `w:numPr`). Pass `false` to treat replacement text strictly as plain text: inline Markdown markers (`**`, `__`, `_`, `~~`, `++`) are kept literally. With `true`, underscores follow CommonMark flanking rules, so signature lines (`_____`) and `snake_case` stay literal. |
889
915
  | `pairReplacements` | `boolean` | `true` | Links adjacent `<w:del>` and `<w:ins>` revisions with matching timestamps so Word groups them as a single replacement in the Reviewing Pane. |
890
916
  | `strictTargets` | `boolean` | `true` (CLI/facade) | Requires exact target descriptors (`exactText`, `paragraphId`, `index`, `occurrence`, `fingerprint`) and forbids ambiguous matching. Defaults to `false` in low-level runner for backwards compatibility. |
891
- | `existingRevisions` | `string` | `'merge-same-author'` | How to handle paragraphs with existing tracked changes. `'merge-same-author'` merges the same author's work and protects other authors with `EXISTING_REVISIONS`. `'slice-cross-author'` retains same-author merging while allowing Word-native edits inside another author's pending insertion. Pass `'accept-all-first'` to normalize prior revisions or `'reject-input'` to refuse editing revised paragraphs. |
917
+ | `existingRevisions` | `string` | `'merge-same-author'` | How to handle paragraphs with existing tracked changes. `'merge-same-author'` merges the same author's work and protects other authors with `EXISTING_REVISIONS`. `'slice-cross-author'` retains same-author merging while allowing Word-native edits inside another author's pending insertion. Pass `'accept-all-first'` to normalize prior revisions or `'reject-input'` to refuse editing revised paragraphs. `'auto'` chooses per target: `'merge-same-author'` when the target has no tracked changes or only the operation author's, otherwise `'slice-cross-author'` (preflight reports the resolved policy). |
892
918
  | `removeFormatting` | `boolean` | `false` | When `true` and the text is unchanged with no Markdown hints, strips existing bold/italic/underline/strikethrough formatting. |
893
919
  | `sanitizeInput` | `boolean` | `false` | Opt-in removal of standalone leading assistant-preface lines. Literal dollar signs and `\n` sequences are always preserved. |
894
920
 
@@ -47,6 +47,10 @@ format, or joins lists:
47
47
  - sends a changed-text header (`1. Old` -> `1. New`) as a marker-preserving `redline` plus a `list-format` of that
48
48
  paragraph in one batch, and splits them on `LIST_FORMAT_COMPOSITION_CONFLICT`.
49
49
 
50
+ ### Existing-revision policy (optional)
51
+
52
+ Only with `existing-revisions-auto-v1`: `existingRevisions: 'auto'` consents to slicing around other or unattributed authors' changes for the whole batch. Report the resolved policy.
53
+
50
54
  Do not expose negotiated but unused capabilities as choices in the editing
51
55
  prompt. `command-help-v1`, restore shortcuts, speculative apply, and localized
52
56
  change summaries are optional requirements only for integrations that use them.
@@ -77,7 +81,7 @@ Keep these exceptions separate from the default workflow:
77
81
  | Choice | Default | Override only when |
78
82
  |---|---|---|
79
83
  | Transaction | progressive | Partial output is unacceptable (`--atomic`). |
80
- | Existing revisions | `merge-same-author` | Editing inside another reviewer's pending insertion is intended (`slice-cross-author`). |
84
+ | Existing revisions | `merge-same-author` | Editing inside another reviewer's pending insertion is intended (`slice-cross-author`, or `auto` to slice only targets that carry another reviewer's changes; capability `existing-revisions-auto-v1`). |
81
85
  | Reviewer identity | `AI Redliner` is a valid visible fallback | The host supplies a preferred flag, operation author, or environment value. |
82
86
  | Output | derived sibling | The caller deliberately selects another destination. |
83
87
 
package/docs/TESTING.md CHANGED
@@ -37,6 +37,10 @@ fixtures rarely contain.
37
37
  | List and text-walker parity | `node tests/performance_phase4_list_and_text_parity_tests.mjs` | One shared marker vocabulary and canonical/specialized visible-text agreement across structural characters and revision views | That all specialized mappings are interchangeable |
38
38
  | Universal document facade & ZIP | `node tests/universal_docx_document_tests.mjs`, `node tests/universal_zip_archive_tests.mjs`, `node tests/universal_facade_edge_cases_tests.mjs` | Universal `Uint8Array` binary handling, `fflate` compression/decompression, optimistic concurrency, and complete-DOCX operations without Node built-ins | Desktop Word rendering |
39
39
  | Sandbox bundle isolation | `node tests/sandbox_bundle_isolation_tests.mjs` | Zero-dependency standalone ESM/CJS bundles execute in restricted VM sandboxes without `process`, `Buffer`, or `require`, with zero Node built-in imports | Web Worker latency |
40
+ | Existing-revision policy `auto` | `node tests/existing_revisions_auto_tests.mjs`, `node tests/existing_revisions_auto_paths_tests.mjs` | Per-target resolution to `merge-same-author` or `slice-cross-author` (mixed, unattributed and deletion-only history, whole `targetEnd` ranges, table cells), equivalence with the explicit policy through `applyRedlineToOxml` and the runner, preflight reporting, untracked edits, `openDocx`, `--existing-revisions auto`, schema and the CLI capability | Desktop Word rendering |
41
+ | Comment reply paraIds | `node tests/comment_reply_para_id_tests.mjs` | A reply's `w14:paraId` avoids body paragraph ids (case-insensitive) and stays deterministic without a collision | Word's own id allocation |
42
+ | Inspection comparison | `node tests/inspection_comparison_tests.mjs` | `compareDocumentInspections` ignores ids, dates, comment order and run splits and reports text, review-segment (author, kind, position), table, list-label and comment (text, author, anchor, resolved, thread) differences, capped at 10; works on `openDocx` inspections | A host's own write path |
43
+ | Literal underscores and plain text | `node tests/literal_underscore_text_tests.mjs` | Signature lines and `snake_case` survive table reconciliation and rewrites; CommonMark underscore flanking; a caller's `structuredContent: false` keeps inline Markdown literal | Desktop Word rendering |
40
44
  | Cross-author revision slicing | `node tests/cross_author_slicing_fixtures_tests.mjs`, `node tests/cross_author_carrier_splitting_tests.mjs`, `node tests/cross_author_slicing_synthetic_tests.mjs`, `node tests/cross_author_slicing_real_tests.mjs`, `node tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs`, `node tests/cross_author_slicing_insertion_stress_tests.mjs` | Word-native `<w:ins>` splitting, `<w:del>` nesting, multi-author stacking, straddle boundaries, synthetic matrix (SYN-01..12d), strict package differential replay (PKG-01..06), exact hyperlink/NBSP round trips, and 76 insertion stress scenarios | Non-Word consumers beyond OpenXML XSD validation |
41
45
 
42
46
  The package-facade regression opens a real ZIP buffer, adds a comment beside an
@@ -145,7 +145,7 @@
145
145
  "target": { "$ref": "#/$defs/target" }, "targetRef": { "type": ["integer", "string", "null"] },
146
146
  "part": { "$ref": "#/$defs/headerFooterPart" },
147
147
  "author": { "type": "string", "minLength": 1 }, "generateRedlines": { "type": "boolean" },
148
- "existingRevisions": { "enum": ["merge-same-author", "slice-cross-author", "reject-input", "accept-all-first", "accept-all-first-keep-normalized"] },
148
+ "existingRevisions": { "enum": ["auto", "merge-same-author", "slice-cross-author", "reject-input", "accept-all-first", "accept-all-first-keep-normalized"] },
149
149
  "pairReplacements": { "type": "boolean" },
150
150
  "listMarkers": { "const": "literal", "description": "Accepts list-marker text as literal text. Suppresses LIST_MARKER_NOT_BOUND / UNSUPPORTED_LIST_CONVERSION and LIST_FORMAT_NOT_APPLIED / UNSUPPORTED_LIST_FORMAT_CHANGE for this operation." },
151
151
  "insertionAffinity": {