@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.
- package/ARCHITECTURE.md +679 -669
- package/CHANGELOG.md +32 -1
- package/README.md +29 -6
- package/core/word-xml.js +160 -118
- package/dist/docx-redline-js.esm.js +171 -51
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +93 -93
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +171 -51
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +171 -51
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +30 -4
- package/docs/SKILL_AUTHORING.md +5 -1
- package/docs/TESTING.md +4 -0
- package/docs/schemas/document-operations.schema.json +1 -1
- package/engine/oxml-engine.js +748 -736
- package/index.d.ts +6 -1
- package/index.js +2 -0
- package/node/cli.js +993 -992
- package/package.json +1 -1
- package/pipeline/markdown-processor.js +5 -2
- package/services/comment-replies.js +1 -1
- package/services/document-operation-applier.js +1 -0
- package/services/document-operation-contract.js +1 -0
- package/services/document-operation-mutations.js +3358 -3346
- package/services/inspection-comparison.js +82 -0
- package/services/operation-preflight.js +42 -33
- package/services/revision-fidelity-verifier.js +5 -19
|
@@ -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
|
|
package/docs/SKILL_AUTHORING.md
CHANGED
|
@@ -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": {
|