@ansonlai/docx-redline-js 0.5.0 → 0.5.1
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/AGENTS.md +69 -13
- package/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/core/redline-validation.js +11 -5
- package/dist/docx-redline-js.esm.js +305 -48
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +73 -72
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/plans/2026-09-08-cross-author-revision-slicing.md +505 -0
- package/docs/schemas/document-operations.schema.json +1 -1
- package/engine/oxml-engine.js +27 -10
- package/engine/surgical-diff-application.js +189 -17
- package/engine/surgical-mode.js +13 -5
- package/engine/surgical-run-splitting.js +103 -0
- package/index.d.ts +1 -1
- package/node/cli.js +12 -7
- package/package.json +123 -123
- package/scripts/generate-cross-author-slicing-fixtures.ps1 +256 -0
- package/services/batch-operation-orchestrator.js +24 -5
- package/services/document-operation-contract.js +22 -0
- package/services/document-operation-mutations.js +19 -3
- package/services/operation-preflight.js +32 -8
- package/services/revision-comment-management.js +40 -0
package/AGENTS.md
CHANGED
|
@@ -57,13 +57,16 @@ reverted to the pre-revision baseline and re-diffed to the new text, cleanly
|
|
|
57
57
|
merging the edits without accumulating intermediate revisions or nesting markup.
|
|
58
58
|
If the paragraph contains revisions from a different reviewer, the edit fails
|
|
59
59
|
with `EXISTING_REVISIONS` to safeguard third-party marks. Pass
|
|
60
|
-
`existingRevisions: '
|
|
60
|
+
`existingRevisions: 'slice-cross-author'` (or `--existing-revisions slice-cross-author`)
|
|
61
|
+
to preserve the other reviewer's attribution while applying Word-native
|
|
62
|
+
insertions and deletions inside their pending insertion. Pass
|
|
63
|
+
`existingRevisions: 'accept-all-first'` (or `--existing-revisions accept-all-first`
|
|
61
64
|
via CLI) to normalize all prior revisions first, or `'reject-input'` to refuse any
|
|
62
65
|
paragraph with open revisions. Use `'accept-all-first-keep-normalized'` only when
|
|
63
|
-
accepted revisions should be returned as a real change even on a no-op edit.
|
|
64
|
-
Same-author merging also fails with `COMMENTED_CONTENT_MERGE` when the revised
|
|
65
|
-
paragraph contains comment anchors, because reverting the prior revision could
|
|
66
|
-
remove or orphan those comments. Resolve the comments before re-editing.
|
|
66
|
+
accepted revisions should be returned as a real change even on a no-op edit.
|
|
67
|
+
Same-author merging also fails with `COMMENTED_CONTENT_MERGE` when the revised
|
|
68
|
+
paragraph contains comment anchors, because reverting the prior revision could
|
|
69
|
+
remove or orphan those comments. Resolve the comments before re-editing.
|
|
67
70
|
|
|
68
71
|
### Apply a text edit without tracked changes (Direct Edits)
|
|
69
72
|
|
|
@@ -251,7 +254,7 @@ explicitly supplied.
|
|
|
251
254
|
|
|
252
255
|
#### Standard Workflow (Fast & Direct)
|
|
253
256
|
|
|
254
|
-
`apply` is fast, progressive, and self-validating by default. It supports inline one-liners as well as batch operations files:
|
|
257
|
+
Use this for everything by default. `apply` is fast, progressive, and self-validating by default—it validates the resulting package and revision markup internally before writing. **Do not insert a `preflight` or baseline `validate` step on top of it "to be safe"**; `apply` already covers that internally. It supports inline one-liners as well as batch operations files:
|
|
255
258
|
|
|
256
259
|
```bash
|
|
257
260
|
# 1. Inline one-liner edit (fastest for 1–2 edits; no JSON file needed)
|
|
@@ -275,9 +278,11 @@ For multi-clause or multi-page reviews, apply edits **section-by-section** or cl
|
|
|
275
278
|
|
|
276
279
|
#### High-Assurance / Staged Verification Workflow (Optional)
|
|
277
280
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
report
|
|
281
|
+
This is an opt-in, higher-latency path for cases like large automated batch
|
|
282
|
+
migrations or workflows where the user specifically requests a non-mutating dry run
|
|
283
|
+
and an independent baseline audit report. **Never switch into it on your own initiative**
|
|
284
|
+
(not even for "high-stakes" contracts); unless the user explicitly requests it, stick with the
|
|
285
|
+
Standard workflow above. Use the extended verification cycle:
|
|
281
286
|
|
|
282
287
|
```bash
|
|
283
288
|
docx-redline inspect contract.docx --non-empty
|
|
@@ -351,6 +356,57 @@ compatibility fallback is `DOCX_REDLINE_AUTHOR` and then `Agent`. Consumers
|
|
|
351
356
|
must use the JSON status and process exit code; failed atomic work has
|
|
352
357
|
`written: false`, `outputPath: null`, and does not modify the output path.
|
|
353
358
|
|
|
359
|
+
#### Safe Operations File Creation (JSON vs. Shell Heredocs)
|
|
360
|
+
|
|
361
|
+
When composing batch operations files (`operations.json`):
|
|
362
|
+
|
|
363
|
+
- **Use structured file-writing tools or JSON serializers**: Write operations files via your environment's file-creation tools or a language JSON serializer (`JSON.stringify`).
|
|
364
|
+
- **Never compose operations in raw shell heredocs** (e.g., `cat << 'EOF'` in bash or PowerShell `@" ... "@`): Legal clauses routinely contain curly quotes (`“ ”`), smart apostrophes (`’`), em-dashes (`—`), section symbols (`§`), non-breaking spaces, and backslashes. Shell heredocs frequently mangle Unicode character encodings, quote escaping, and whitespace formatting, causing immediate `TARGET_NOT_FOUND` failures.
|
|
365
|
+
|
|
366
|
+
#### Walking Progressive Batch Results (Status & Partial Execution)
|
|
367
|
+
|
|
368
|
+
In default progressive mode (`atomic: false`), operations execute independently: valid operations commit to the document while failing operations report errors without aborting the batch:
|
|
369
|
+
|
|
370
|
+
- **Do not rely solely on top-level `written: true` or `status !== "error"`**: A progressive batch can return `status: "partial"` with `written: true` when some operations succeed and others fail.
|
|
371
|
+
- **Walk every entry in `results`**: Check `results[i].status` and `results[i].error`. Any `status: "error"` entry in `results` represents an unapplied change that must be investigated and resolved.
|
|
372
|
+
- **`written: false`**: Indicates that zero operations were committed (or an atomic rollback occurred). Never treat or present an unwritten or partial output file as complete.
|
|
373
|
+
|
|
374
|
+
#### Human-Readable References vs. Internal Machine Handles
|
|
375
|
+
|
|
376
|
+
Target handles such as `ref` (`P<index>`), `targetRef`, and bare paragraph `index` numbers are **strictly internal machine handles** for the CLI and engine. They do not correspond to any visual or followable marker in Microsoft Word:
|
|
377
|
+
|
|
378
|
+
- **Never surface `P11`, `P42`, or bare paragraph numbers** in user-facing prose, comments, redline summaries, or negotiation notes.
|
|
379
|
+
- Instead, cite locations using the human-readable fields provided by `inspect` / `extract`:
|
|
380
|
+
- **`provision`**: Lead with section/clause numbers when present (e.g., `§14.1 Entire Agreement`).
|
|
381
|
+
- **`nearestHeading` + ordinal offset**: When `provision` is absent, describe position relative to the nearest heading (e.g., `under "Limitation of Liability", 2nd paragraph`).
|
|
382
|
+
- **Structural context**: For unnumbered clauses prior to the first heading, use plain language (e.g., `opening recital, before Section 1`).
|
|
383
|
+
- **`humanReference`**: Use the pre-joined citation string provided directly on inspected paragraph objects.
|
|
384
|
+
|
|
385
|
+
#### Actionable Error Recovery Matrix
|
|
386
|
+
|
|
387
|
+
When the CLI or runner returns an error code, follow these specific recovery actions:
|
|
388
|
+
|
|
389
|
+
| Error Code | Meaning | Actionable Recovery |
|
|
390
|
+
|---|---|---|
|
|
391
|
+
| `TARGET_NOT_FOUND` | Target text did not match any paragraph. | **Do NOT retry with paraphrased text.** Re-run `extract`/`inspect`, copy `exactText` verbatim (including exact whitespace/punctuation), and add a discriminator (`paragraphId`, `fingerprint`, or `occurrence`). |
|
|
392
|
+
| `AMBIGUOUS_TARGET` | Multiple paragraphs match identical text. | Disambiguate by supplying `paragraphId`, `fingerprint`, `occurrence`, or `index` in the target descriptor. |
|
|
393
|
+
| `ANCHOR_NOT_FOUND` / `AMBIGUOUS_ANCHOR` | Comment anchor text was not uniquely matched in paragraph. | Narrow `textToComment` to a unique exact substring, or omit `textToComment` to anchor the comment to the entire paragraph. |
|
|
394
|
+
| `OVERLAPPING_TEXT_EDITS` | Multiple operations target the same paragraph concurrently. | Consolidate all changes to the same paragraph into a single `redline` or `replace` operation. |
|
|
395
|
+
| `EXISTING_REVISIONS` | Target paragraph contains tracked changes from another author. | Fails closed to protect third-party review marks. Report the other reviewer's name to the user. Do not pass `accept-all-first` without explicit authorization. |
|
|
396
|
+
| `COMMENTED_CONTENT_MERGE` / `COMMENTED_CONTENT_DELETE` | Operation would overwrite, revert, or delete content with comments. | Fails closed to prevent orphaned comment threads. Report the comment author and text to the user; resolve the comment before re-editing. |
|
|
397
|
+
| `INVALID_OPERATION` | Operation object violates schema or has incompatible fields. | Validate the JSON structure against [`document-operations.schema.json`](file:///c:/Users/Phara/Desktop/Projects/Docx%20Redline%20JS/docs/schemas/document-operations.schema.json) before targeting is attempted. |
|
|
398
|
+
| `STRUCTURED_CONTENT_INVALID` | Malformed Markdown table or structure in replacement text. | Ensure tables include a separator row (`\| --- \| --- \|`) and consistent column counts; do not downgrade to raw text. |
|
|
399
|
+
|
|
400
|
+
**Important Rule:** Never repeat the exact same failing command without correcting the reported cause. If an error persists after one correction attempt, stop and report the diagnostic code to the user.
|
|
401
|
+
|
|
402
|
+
#### Document Scope & Boundary Invariants
|
|
403
|
+
|
|
404
|
+
The `docx-redline` engine and CLI operate specifically on the **main document body**:
|
|
405
|
+
|
|
406
|
+
- **Supported Content**: Body paragraphs, numbered/bulleted lists, tables and table cells, comments, and comment replies.
|
|
407
|
+
- **Unsupported Content**: Headers, footers, footnotes, endnotes, floating text boxes, shape drawings, watermarks, and embedded macros.
|
|
408
|
+
- Do not attempt to target, edit, or comment on header/footer text or footnote citations using `docx-redline`. Use specialized document manipulation tools or manual editing for layout frames outside the body text.
|
|
409
|
+
|
|
354
410
|
### Convert paragraph text into a Word list
|
|
355
411
|
|
|
356
412
|
```js
|
|
@@ -475,7 +531,7 @@ orchestration/
|
|
|
475
531
|
| `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. |
|
|
476
532
|
| `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. |
|
|
477
533
|
| `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. |
|
|
478
|
-
| `existingRevisions` | `string` | `'merge-same-author'` | How to handle paragraphs with existing tracked changes. `'merge-same-author'`
|
|
534
|
+
| `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. |
|
|
479
535
|
| `removeFormatting` | `boolean` | `false` | When `true` and the text is unchanged with no Markdown hints, strips existing bold/italic/underline/strikethrough formatting. |
|
|
480
536
|
| `sanitizeInput` | `boolean` | `false` | Opt-in removal of standalone leading assistant-preface lines. Literal dollar signs and `\n` sequences are always preserved. |
|
|
481
537
|
|
|
@@ -508,8 +564,8 @@ orchestration/
|
|
|
508
564
|
}
|
|
509
565
|
```
|
|
510
566
|
|
|
511
|
-
Known error codes include `PARSE_ERROR`, `TARGET_NOT_FOUND`, `PARTIAL_TARGET`,
|
|
512
|
-
`EXISTING_REVISIONS`, `COMMENTED_CONTENT_MERGE`, `UNSAFE_REVISION_NESTING`, `UNSUPPORTED_REVISION_VIEW_MUTATION`,
|
|
567
|
+
Known error codes include `PARSE_ERROR`, `TARGET_NOT_FOUND`, `PARTIAL_TARGET`,
|
|
568
|
+
`EXISTING_REVISIONS`, `COMMENTED_CONTENT_MERGE`, `UNSAFE_REVISION_NESTING`, `UNSUPPORTED_REVISION_VIEW_MUTATION`,
|
|
513
569
|
`UNSAFE_PARAGRAPH_BOUNDARY`, `DIFF_TOKEN_LIMIT`, and `BATCH_OPERATION_FAILED`.
|
|
514
570
|
|
|
515
571
|
For ingestion that must distinguish an empty document from malformed OOXML,
|
|
@@ -561,7 +617,7 @@ directly into `word/document.xml`.
|
|
|
561
617
|
5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
|
|
562
618
|
6. `deleteCommentsByAuthorInOoxml` removes definitions and linked anchors only when they are present in the same OOXML payload. In a real `.docx`, `word/comments.xml` and `word/document.xml` are separate parts and must both be updated by the package integration layer.
|
|
563
619
|
7. If output begins with `<pkg:package`, treat it as package-level OOXML and normalize it before writing anything back to `word/document.xml`.
|
|
564
|
-
8. Existing revisions from the same author are merged by default against the pre-revision baseline (`merge-same-author`), while third-party revisions fail closed with `EXISTING_REVISIONS`. Pass `existingRevisions: 'accept-all-first'` to normalize all prior revisions first, or `'reject-input'` to refuse any revised paragraph.
|
|
620
|
+
8. Existing revisions from the same author are merged by default against the pre-revision baseline (`merge-same-author`), while third-party revisions fail closed with `EXISTING_REVISIONS`. Pass `existingRevisions: 'slice-cross-author'` to preserve third-party attribution while editing inside pending insertions, `'accept-all-first'` to normalize all prior revisions first, or `'reject-input'` to refuse any revised paragraph.
|
|
565
621
|
9. Caller content is not sanitized by default. Pass `sanitizeInput: true` only for raw assistant output; literal dollar delimiters and `\\n` sequences are never rewritten.
|
|
566
622
|
10. Hyperlinks, bookmarks, comment markers, tabs/breaks, and footnote/endnote references are structural OOXML and should survive adjacent redline edits.
|
|
567
623
|
11. Internally, create Word elements through `createWordElement` and tracked-change metadata through `createRevisionMetadata`.
|
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -183,7 +183,7 @@ See [the agent workflow in AGENTS.md](./AGENTS.md#agent-document-workflow-cli) a
|
|
|
183
183
|
| `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. |
|
|
184
184
|
| `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. |
|
|
185
185
|
| `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. |
|
|
186
|
-
| `existingRevisions` | `string` | `'merge-same-author'` | How to handle paragraphs with existing tracked changes. `'merge-same-author'`
|
|
186
|
+
| `existingRevisions` | `string` | `'merge-same-author'` | How to handle paragraphs with existing tracked changes. `'merge-same-author'` merges revisions from the same author and protects other authors with `EXISTING_REVISIONS`. `'slice-cross-author'` keeps that same-author merge behavior 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. |
|
|
187
187
|
| `removeFormatting` | `boolean` | `false` | When `true` and the text is unchanged with no Markdown hints, strips existing bold/italic/underline/strikethrough formatting. |
|
|
188
188
|
| `sanitizeInput` | `boolean` | `false` | Opt-in removal of standalone leading assistant-preface lines. Literal dollar signs and `\n` sequences are always preserved. |
|
|
189
189
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Mirrors the invariants enforced by the test-suite round-trip harness so
|
|
5
5
|
* downstream consumers can verify output before writing it into a package:
|
|
6
|
-
*
|
|
7
|
-
* complete, revision ids are unique, and boundary
|
|
6
|
+
* only schema-permitted nested revisions, deleted text uses w:delText,
|
|
7
|
+
* revision metadata is complete, revision ids are unique, and boundary
|
|
8
|
+
* whitespace is preserved.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import { parseXml } from '../adapters/xml-adapter.js';
|
|
@@ -107,13 +108,18 @@ export function validateRedlineOoxml(oxml) {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
//
|
|
111
|
+
// A w:del may be a direct revision child of w:ins. All other insertion /
|
|
112
|
+
// deletion nesting is rejected, including deeper revisions inside that del.
|
|
111
113
|
for (const revision of revisions) {
|
|
112
114
|
const nested = Array.from(revision.getElementsByTagName('*'))
|
|
113
115
|
.filter(el => el !== revision && ['ins', 'del'].includes(localNameOf(el)));
|
|
114
|
-
|
|
116
|
+
const invalidNested = nested.find(candidate => {
|
|
117
|
+
if (localNameOf(revision) !== 'ins' || localNameOf(candidate) !== 'del') return true;
|
|
118
|
+
return candidate.parentNode !== revision;
|
|
119
|
+
});
|
|
120
|
+
if (invalidNested) {
|
|
115
121
|
addIssue('NESTED_REVISION', 'error',
|
|
116
|
-
`<${revision.nodeName}> (w:id="${wordAttribute(revision, 'id')}") contains nested <${
|
|
122
|
+
`<${revision.nodeName}> (w:id="${wordAttribute(revision, 'id')}") contains invalid nested <${invalidNested.nodeName}>.`);
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|