@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
@@ -41,6 +41,8 @@ If the user requests deleted text, search with `--view rejected` first. Otherwis
41
41
 
42
42
  - Require `completion: true` and a non-null `outputPath`; the CLI computes this from write, operation, commit, and localized-verification results.
43
43
  - On failure, follow `error.recovery.action`; never retry unchanged arguments.
44
+ - `REVISION_FIDELITY_VIOLATION` (Reject All would not restore the source): retry
45
+ narrower per `recovery.retryGuidance`; never pass `--revision-fidelity off`.
44
46
  - Use `retryPlan.base: "original"` to replay a batch and `"output"` to retain
45
47
  committed progressive work and retry the reported indexes.
46
48
  - The source is not overwritten unless `--in-place` is explicit.
@@ -462,10 +462,14 @@ fragment to insert. Do not choose a type from its English name alone.
462
462
  |---|---|
463
463
  | Change text within one paragraph or replace its content | `{ type: 'redline', target, modified }` (`replace` is a compatibility alias) |
464
464
  | Delete a whole paragraph | `{ type: 'delete', target }` (normalized to a redline with `modified: ''`) |
465
- | Change native list structure | `{ type: 'list-change', target, modified: '<complete Markdown list>' }` |
465
+ | Change native list structure | `{ type: 'list-change', target, modified: '<complete Markdown list>' }`. It inserts, removes and nests items; it never changes the list's kind or numbering format (retyped markers warn `LIST_FORMAT_NOT_APPLIED` or fail `UNSUPPORTED_LIST_FORMAT_CHANGE` with a `change-format` `suggestedOperation`): use `list-format` `change-format` for that |
466
466
  | Reconcile a Word table | `{ type: 'table-reconciliation', target, modified: '<complete Markdown table>' }` |
467
467
  | Comment or highlight existing text | `comment` with `commentContent`, or `highlight` with `textToHighlight` |
468
- | Change character or paragraph formatting | `character-format`/`format` with `textToFormat` and `properties`, or `paragraph-format` with `properties` |
468
+ | Change character or paragraph formatting | `character-format`/`format` with `textToFormat` and `properties`, or `paragraph-format` with `properties`; add `textOccurrence` (1-based, default 1) to format a later match of `textToFormat` inside the targeted paragraph (out of range fails `PATCH_SOURCE_NOT_FOUND`; independent of `target.occurrence`, which selects the paragraph; legacy calls without `textOccurrence` still read `target.occurrence`; capability `format-text-occurrence-v1`) |
469
+ | Put an existing paragraph in (or out of) an existing list | `{ type: 'paragraph-format', target, properties: { numbering: { numId, level } } }` with `numId`/`level` copied from an inspected list paragraph of the target list, or `numbering: null` to remove the paragraph's direct list binding. A tracked `w:pPrChange` keeps the previous `w:numPr`, so Reject All restores the old membership and Accept All shows the new one. It binds only: it never creates a list, marker text is not stripped, and sibling numbering is not remapped. Refused `PARAGRAPH_NUMBERING_NOT_FOUND` (unknown `numId` or undefined level) and `PARAGRAPH_FORMAT_REVISION_CONFLICT` (another author's pending paragraph-property change). Capability `paragraph-numbering-binding-v1` |
470
+ | Turn typed headers (`A.`, `1.`, `I.`, `-` typed as text) into a real list | `{ type: 'list-format', target, targetEnd?, list: { mode: 'convert-plain' } }` on the plain paragraphs that carry the typed markers. Each marker becomes a tracked deletion and the paragraph a list item with that label (tracked `w:pPrChange`, same paragraph); the marker's value is the start value, consecutive markers share one list, body paragraphs in the range stay plain, and a later `list-format` in the batch continuing the count joins the list. A lone `I.`/`C.` is ambiguous (letter or roman): pass `list.format`. `strip: 'none'` with `format` (and `start`) binds a paragraph without a typed marker and leaves its text alone. Do not express this as a `redline` that adds or keeps marker text. A changed-text header (`1. Old` -> `1. New`) is a `redline` of that paragraph that keeps the typed marker exactly (`modified: '1. New'`, or `replacements`) plus this `list-format` in the same batch, in either order: the redline runs first and the list-format strips the unchanged marker, so Accept All shows label `1.` with `New` and Reject All the literal `1. Old`. A redline in that batch that adds, removes or changes the marker, spans paragraphs, or leaves the text unchanged is refused before mutation (`LIST_FORMAT_COMPOSITION_CONFLICT`). Capability `list-format-v1` (contract version 9) |
471
+ | Change a list's kind or numbering style (bullets to `1.`, `1.` to `A.`, a nested level to `(i)`) | `{ type: 'list-format', target, list: { mode: 'change-format', format, lvlText?, level?, start?, scope? } }` with `target` on any item of the list. A NEW definition is allocated (levels, indentation and fonts copied from the list; only `level`, default the target's level, is redefined) and every item is rebound with a tracked `w:pPrChange`: Reject All restores the old labels, Accept All shows the new ones; the shared definition is never edited. `scope: 'list'` (default) rebinds every item, including items outside `target` (`result.list.items[].inTarget`); `scope: 'range'` with `targetEnd` changes only a range that runs to the end of the list and continues its count. `lvlText` keeps the source text unless given (`"%1."`, `"(%2)"`; a bullet's character). Do not express this as a `list-change`/`redline` that retypes the markers. `scope: 'list'` counts as a write to every item it rebinds: in the same batch each of those items may also carry one single-paragraph `redline` that adds no marker (it runs first); any other write to them fails `LIST_FORMAT_COMPOSITION_CONFLICT` (`reason: list-rebind`), so send it in a separate apply or use `scope: 'range'`. Capability `list-format-change-v1` (contract version 9) |
472
+ | Make a list continue an earlier list (`1. 2.` / `1. 2.` -> `1. 2.` / `3. 4.`) | `{ type: 'list-format', target, list: { mode: 'join', continueFrom: { paragraphId, fingerprint } } }` with `target` on the later list and `continueFrom` on an item of the earlier one; items keep their levels and take the earlier list's format. `scope: 'range'` joins only a range that runs to the end of the later list. Capability `list-format-change-v1` |
469
473
  | Counterpropose a paragraph wholly deleted by another author | `restore` with a rejected-view target |
470
474
  | Insert text inside another author's rejected-view content | `insert` with `target.revisionView: 'rejected'`, an exact `anchor`, and `existingRevisions: 'slice-cross-author'` |
471
475
 
@@ -514,6 +518,9 @@ revision markup internally before writing, so do not add `preflight` or baseline
514
518
  2. Build one final operation per target. Use complete `modified` text for broad
515
519
  semantic revisions or localized `replacements` for small literal changes in
516
520
  an inspected target. Consolidate multiple changes to one paragraph.
521
+ Paragraphs with soft line breaks (`\n` in `exactText`) accept `replacements`
522
+ whose `find` lies within one line; the breaks are preserved. A `find` or
523
+ `replace` that spans or adds a line break is refused.
517
524
  3. Run `apply` once per stable batch. Independent strong targets bind against
518
525
  the batch-start document and do not need bottom-up sorting. Use captures only
519
526
  for intentional created-content dependencies.
@@ -767,13 +774,25 @@ When the CLI or runner returns an error code, follow these specific recovery act
767
774
  | `ANCHOR_NOT_FOUND` / `AMBIGUOUS_ANCHOR` | A comment or rejected-view insertion anchor was not uniquely matched. | For comments, narrow `textToComment` or omit it to anchor the whole paragraph. For rejected-view insertion, copy exact rejected text and provide `anchor.occurrence`. |
768
775
  | `OVERLAPPING_SOURCE_TARGETS` / `OVERLAPPING_TEXT_EDITS` | Multiple complete text operations target the same batch-start source. | Consolidate all changes to that paragraph into one `redline` or `replace`; the library cannot choose between incompatible complete desired states. |
769
776
  | `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
+ | `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'`. |
770
778
  | `CAPTURE_FANOUT_CONFLICT` | Multiple mutating consumers share one capture without distinct selectors. | Give consumers distinct selectors, chain them explicitly, or split the batch. |
771
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. |
772
- | `PATCH_ROUNDTRIP_MISMATCH` | A cross-author surgical edit did not reconstruct the requested modified text exactly. | 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. |
780
+ | `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. |
773
781
  | `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. |
774
782
  | `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. |
775
783
  | `REJECTED_INSERTION_STATE_REQUIRED` / `UNSAFE_REVISION_BOUNDARY` | An explicit rejected-view insertion did not resolve to supported plain run text inside a wholly foreign-deleted paragraph. | Do not fall back to a generic edit. Narrow the exact anchor/offset, or handle comments, bookmarks, fields, hyperlinks, moves, or other structural boundaries manually. |
776
784
  | `GENERATED_OOXML_INVALID` | The operation introduced a new validation error relative to its baseline. | Treat the operation as unapplied and inspect `generatedIssues`; correct the generating operation or builder rather than repairing or accepting the source document's unrelated baseline defects. |
785
+ | `LIST_MARKER_NOT_BOUND` (warning) / `UNSUPPORTED_LIST_CONVERSION` (error) | A plain paragraph was edited so its text starts with a list marker (`A.`, `1.`, `iv.`, `-`) its original did not carry, but text edits cannot bind a list: the marker would be literal text and `list` stays `null`. Warning (`diagnostics[]` plus a `CODE: message` string in `warnings`) by default; error under the strict policy (`--profile agent`, `--atomic`, or `strictListConversion: true`). Separate from `REVISION_FIDELITY_VIOLATION`. Fields: `listMarker`, `requestedList`, `targetExcerpt`. Capability `list-conversion-diagnostics-v1`. | The error applied nothing. To create the list item, send `recovery.suggestedOperation` (a `list-format` `convert-plain` request) with the same target and leave the text unmarked (`recovery.action: use_list_format_convert_plain`); if the original already carries another marker, retype it with a `redline` first and convert with `strip: "marker"` in a later batch. If literal marker text is intended, repeat with `listMarkers: "literal"`. Do not treat a warning-only `ok` as a list conversion. |
786
+ | `LIST_MARKER_NOT_FOUND` / `LIST_MARKER_AMBIGUOUS` / `LIST_MARKER_UNSUPPORTED` (error) | `list-format` `convert-plain` found no typed marker to strip; a lone roman-looking letter (`I.`, `C.`, `D.`) reads as letter or roman (`formats` lists both); or the marker shape is unsupported (`reason`: `mixed` markers in one range, `multilevel` `2.1.`, `parenthesized` `(a)`, `zero`, `format-mismatch`, `marker-shape` when the marker sits in a hyperlink, field, content control or pending revision). Nothing was written. | NOT_FOUND: target the marker paragraphs, or use `strip: "none"` with `list.format`. AMBIGUOUS: repeat with `list.format` from `recovery.allowedValues`. UNSUPPORTED: split mixed ranges into one operation per format; leave other shapes literal or convert them in the host. |
787
+ | `LIST_FORMAT_TARGET_IS_LIST` / `LIST_FORMAT_STYLE_NUMBERED` (error) | `convert-plain` converts plain paragraphs only: the paragraph already has a direct `w:numPr`, or its paragraph style (or a `basedOn` ancestor) carries numbering. Nothing was written. | Narrow the range to plain paragraphs. To move an existing list item, use `paragraph-format` `numbering`; style-linked numbering is changed in the host. |
788
+ | `LIST_FORMAT_NOT_APPLIED` (warning) / `UNSUPPORTED_LIST_FORMAT_CHANGE` (error) | A multi-line `list-change`/`redline` kept a list item's text with a marker whose kind or format (bullet vs numbered, decimal vs `A.`) differs from the numbering definition the item keeps (`sourceList.{numId,level,kind,format}`); the old definition is not replaced. Same warning/strict policy as above. Inserted items are not compared. | `recovery.action: use_list_format_change_format`: send `recovery.suggestedOperation` (a `list-format` `change-format` request, scope `list`) with a target on one item of the list; `list-change` never applies a format change itself. Text changes go in single-paragraph `redline`s without the new markers, which may share the batch with the `change-format` (they run first). Or pass `listMarkers: "literal"` to accept the result. |
789
+ | `LIST_FORMAT_TARGET_NOT_LIST` / `LIST_FORMAT_MIXED_RANGE` (error) | `change-format`/`join`: the target range has no list item; or it mixes list items with plain paragraphs (`reason: non-list`), spans several lists (`several-lists`, `numIds`), or, for `change-format` without `list.level`, several levels (`levels`, `recovery.allowedValues`). Tracked-deleted paragraphs are skipped, never rebound. Nothing was written. | NOT_LIST: use `convert-plain` (typed markers) or `paragraph-format` `numbering`. MIXED: target one item of the list (scope `list`), one operation per list, or pass `list.level`. |
790
+ | `LIST_FORMAT_STYLE_LINKED` / `LIST_FORMAT_DEFINITION_UNSUPPORTED` (error) | The list's definition is style-linked (`w:numStyleLink`/`w:styleLink`, a level `w:pStyle` such as heading numbering, or a paragraph style that binds it), so items can belong to it without a direct binding; or the definition cannot be rebound exactly (`reason`: `missing` numbering part or numId, `level-override` `w:lvlOverride/w:lvl`, `level-restart` `w:lvlRestart` in a `scope: 'range'` split, `undefined-level`). Nothing was written. | Change these lists in the host. `missing` on a bare call: pass the package's `word/numbering.xml` as `numberingXml`. `level-restart`: use `scope: 'list'`. |
791
+ | `LIST_FORMAT_SPLIT_UNSUPPORTED` / `LIST_FORMAT_JOIN_INVALID` (error) | The rebinding would renumber paragraphs it does not name, or Word could not show the same count: `mid-sequence` (a `scope: 'range'` that stops before the list's last item; `followingParagraphIndexes`, `lastListParagraphIndex`), `nested-restart` (a split that starts below a level that later restarts inside the range), `shared-definition` (another list shares the abstract definition, which Word counts as one list), `interleaved` (the continued list has items after the joined ones), `textbox-member`; join: `continueFrom` `after-target`, `not-a-list`, `deleted`, `inside-target`. Nothing was written. | Extend `targetEnd` to `lastListParagraphIndex` or use `scope: 'list'`; start a split at a top-level item; join a list to the list that immediately precedes it. A `LIST_FORMAT_UNCHANGED` warning (`ok`, no change) means the list already has the requested format or already continues `continueFrom`. |
792
+ | `INVALID_OPERATION` with `field: properties.<key>` (error) | `paragraph-format.properties` contained keys outside `alignment`, `keepNext`, `keepLines`, `pageBreakBefore`, `style`, `numbering` (0.9.0; it was the `UNKNOWN_PARAGRAPH_FORMAT_PROPERTY` warning in 0.8.x). Nothing was applied; `properties` lists the rejected keys. | Remove the unsupported key, or use the dedicated operation (`list-format`, `format`). |
793
+ | `PARAGRAPH_NUMBERING_NOT_FOUND` (error) | `paragraph-format` `numbering.numId` is not defined in the document's numbering part (and was not allocated earlier in the batch), its `abstractNum` is missing, or the level is not defined. `reason` is `unknown-num`, `missing-abstract` or `undefined-level`. Nothing was changed. | Inspect a paragraph already in the target list and copy its `list.numId` and `list.level`. A binding never creates a list. |
794
+ | `PARAGRAPH_FORMAT_REVISION_CONFLICT` (error) | The target paragraph already has a pending `w:pPrChange` by another author; overwriting its snapshot would change what Reject All restores. A pending change by the same author keeps its earliest snapshot. | Ask the user to accept or reject the existing formatting change first. |
795
+ | `REVISION_FIDELITY_VIOLATION` | Word's Reject All of the operation's output would not reproduce the source (`violations[].invariant`: `REJECT_PARAGRAPH_COUNT`, `REJECT_PARAGRAPH_TEXT`, `REJECT_PARAGRAPH_PROPS`, `REJECT_NUMBERING_REFS` (also: a bound or restored numId with no `w:num`/`w:abstractNum`/`w:lvl` in the output numbering), `REJECT_WRAPPERS`, `ACCEPT_INTENT` (a plain-text `redline`'s Accept All target does not equal its complete `modified` text); for `generateRedlines: false` operations also `ACCEPT_NEIGHBORS` and `UNTRACKED_CREATED_REVISION`, the latter meaning the untracked edit wrote a tracked revision the source lacked). Refused by default (`enforce`); `verifyRevisionFidelity: 'warn'` / `--revision-fidelity warn` applies and appends the same text to `warnings`. Each violation has `explanation`, `expected`/`actual` excerpts and `source.sourceParagraphIndex` (the 1-based extraction index) with a text excerpt. | Nothing was applied. Follow `recovery.retryGuidance` for the reported invariant: target one paragraph, use `replacements` for literal edits, keep breaks/tabs and hyperlink boundaries, and move formatting or list changes into their own operation. If a narrower retry is also refused, report the operation and `violations`; never disable verification to force it through. For `UNTRACKED_CREATED_REVISION`, retry once with the fresh extraction; if it repeats, report it (or make the edit tracked with `generateRedlines: true` if a reviewable change is acceptable). |
777
796
  | `UNSAFE_DELETED_TABLE_ROW` / `UNSUPPORTED_MOVE_REVISION` / `SECTION_BREAK_PARAGRAPH` / `UNSAFE_PARAGRAPH_PLACEMENT` | Paragraph restoration cannot preserve the source structural boundary safely. | Do not retry as an ordinary redline. Resolve the row/move/section/placement condition manually or narrow the restoration to a safe paragraph. |
778
797
  | `PART_NOT_FOUND` / `PART_AMBIGUOUS` | The `part` selector matched no header/footer, or several. | Re-run `inspect` and use `headersFooters[].path`, or add `section`. |
779
798
  | `FIELD_EDIT_REFUSED` | The edit would change a field (PAGE, NUMPAGES, DATE) in a header or footer. | Edit the text around the field. |
@@ -960,7 +979,7 @@ directly into `word/document.xml`.
960
979
  1. Call `configureXmlProvider` first in Node.js.
961
980
  2. `applyRedlineToOxml` is async.
962
981
  3. Paragraph APIs expect paragraph-level OOXML, not full `word/document.xml` in all cases.
963
- 4. List operations may return `numberingXml` that must be merged into package parts. When `word/numbering.xml` already exists, pass `mergeNumberingXmlBySchemaOrder` to `ensureNumberingArtifactsInZip`; without a merge callback the helper replaces the prior payload.
982
+ 4. List operations may return `numberingXml` that must be merged into package parts. `ensureNumberingArtifactsInZip` always merges them into an existing `word/numbering.xml` (the former `mergeNumberingXml` option is ignored); a conflicting id fails with `NUMBERING_ID_CONFLICT`, so pass `numberingXml` or a shared `numberingIdState` to every call.
964
983
  That replacement behavior is deprecated and will become an error in the next major version.
965
984
  5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
966
985
  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.
@@ -1,150 +1,165 @@
1
- # Skill and Harness Authoring Contract
2
-
3
- Use this page to create or update an agent skill, MCP server, or custom harness
4
- for `@ansonlai/docx-redline-js`. Ordinary editing agents should load
5
- [Agent Fast Start](AGENT_FAST_START.md), not this design contract.
6
-
7
- > [!WARNING]
8
- > Before 1.0, every integration must identify and pin its exact tested library
9
- > release and required CLI contract/capabilities. Treat any different release or
10
- > contract as unverified until compatibility tests pass.
11
-
12
- Example declaration for a contract-8 shell integration that uses localized
13
- operations over stdin:
14
-
15
- ```json
16
- {
17
- "testedLibraryRelease": "0.7.1",
18
- "requiredCliContractVersion": 8,
19
- "requiredCapabilities": [
20
- "operations-stdin",
21
- "agent-safety-profile-v2",
22
- "recovery-envelope-v1",
23
- "localized-replacements-v1"
24
- ]
25
- }
26
- ```
27
-
28
- ## Runtime negotiation
29
-
30
- Run `docx-redline version` before the first document operation. Require CLI
31
- contract version 8 and only the capabilities the integration actually uses. Check once per stable
32
- runtime, fail closed when a requirement is absent, and never inspect bundles or
33
- ZIP parts as a fallback.
34
-
35
- Do not expose negotiated but unused capabilities as choices in the editing
36
- prompt. `command-help-v1`, restore shortcuts, speculative apply, and localized
37
- change summaries are optional requirements only for integrations that use them.
38
-
39
- ## Safety invariants
40
-
41
- Every generated integration must:
42
-
43
- - inspect narrowly, retain `exactText`, and target with `paragraphId` plus
44
- `fingerprint`;
45
- - use strict targeting, package validation, and a derived output path;
46
- - never overwrite the source unless `--in-place` is explicitly requested;
47
- - require `completion: true` and a non-null `outputPath` before reporting
48
- success;
49
- - follow `error.recovery.action` and never retry unchanged failed arguments; and
50
- - require explicit authorization before accepting/rejecting another reviewer's
51
- work or deleting comments.
52
-
53
- The CLI computes `completion` from write status, operation outcomes, commit
54
- disposition, and localized accepted-view verification. Preserve the detailed
55
- results for audit and recovery, but do not make the editing agent re-evaluate
56
- those fields during ordinary success handling.
57
-
58
- ## Workflow policy choices
59
-
60
- Keep these exceptions separate from the default workflow:
61
-
62
- | Choice | Default | Override only when |
63
- |---|---|---|
64
- | Transaction | progressive | Partial output is unacceptable (`--atomic`). |
65
- | Existing revisions | `merge-same-author` | Editing inside another reviewer's pending insertion is intended (`slice-cross-author`). |
66
- | Reviewer identity | `AI Redliner` is a valid visible fallback | The host supplies a preferred flag, operation author, or environment value. |
67
- | Output | derived sibling | The caller deliberately selects another destination. |
68
-
69
- Check `effectiveOptions` when an integration deliberately sets a policy.
70
-
71
- ## Transport choices
72
-
73
- A UTF-8 operations file and serializer-backed stdin are peer transports:
74
-
75
- ```bash
76
- docx-redline apply input.docx --operations operations.json --profile agent --output reviewed.docx
77
- node emit-operations.mjs | docx-redline apply input.docx --operations - --profile agent --compact --output reviewed.docx
78
- ```
79
-
80
- Use `JSON.stringify` or a structured tool API. Never demonstrate `echo`, a
81
- heredoc, or shell-interpolated document text. In JavaScript/TypeScript runtimes
82
- (Node.js, browsers, Cloudflare Workers, Deno, Bun, or sandboxed environments like
83
- n8n), an in-process wrapper should call `openDocx(uint8Array)` directly from
84
- `@ansonlai/docx-redline-js` (or `@ansonlai/docx-redline-js/bundle` for zero-dependency
85
- standalone sandboxes) and return the structured result instead of recreating CLI
86
- serialization, ZIP handling, targeting, validation, or rollback.
87
-
88
- ## Ordinary generated workflow
89
-
90
- The first executable editing example in a generated skill must be focused
91
- extraction:
92
-
93
- ```bash
94
- docx-redline extract input.docx --search "renewal notice" --around 3
95
- ```
96
-
97
- Then apply once using the inspected strong target. Use complete `modified` text
98
- for a broad semantic revision or `replacements` for a small literal change in
99
- that target. Keep requested relative wording relative unless the user asks for
100
- a concrete value.
101
-
102
- Follow `selection.nextAfter` when extraction is truncated. Output projections must retain
103
- `selection`, target descriptors, `humanReference`, and revision
104
- cues.
105
-
106
- ### Observed restore branch
107
-
108
- If the user requests restoration, search `--view rejected` first. Otherwise
109
- follow an alternate-view `selection.hint` when returned. Restore only the
110
- inspected strong target. Require `restore-shortcuts-v1` only if the integration
111
- uses inline `--restore`; canonical operations remain available.
112
-
113
- ### Optional speculative execution
114
-
115
- Do not make speculative apply part of an ordinary generated workflow. A harness
116
- may opt into `speculative-search-apply-v1` only when it owns a measured confidence
117
- policy and accepts the recovery cost of a miss. Keep anchor selection,
118
- directional windows, occurrence handling, and exact matching semantics out of
119
- the ordinary editing prompt; link to the knowledge base or CLI help instead.
120
-
121
- ## Recovery contract
122
-
123
- Lead failure handling with these runtime fields:
124
-
125
- 1. `error.recovery.action`
126
- 2. `error.recovery.requiresReinspection`
127
- 3. `error.recovery.requiresUserAuthorization`
128
- 4. `error.recovery.sameArgumentsSafe`
129
-
130
- Use `retryPlan.base` and its operation indexes to select the original or partial
131
- output package. Do not reproduce an error-code decision tree in the skill.
132
-
133
- ## Presentation rules
134
-
135
- Use `humanReference`, `provision`, or `nearestHeading` in completion reports.
136
- Paragraph indexes and `P55`-style references are machine coordinates, not
137
- locations a document user can follow. Keep the exact machine target internally.
138
-
139
- ## Generation checklist
140
-
141
- - Exact tested release, contract 8, and the minimum capability set are pinned.
142
- - Runtime compatibility is checked once.
143
- - The first ordinary command is focused extraction.
144
- - Apply uses the inspected target with `modified` or localized `replacements`.
145
- - Restore appears only as a user-requested or observed-view branch.
146
- - Speculative execution is absent unless the harness owns a measured policy.
147
- - Ordinary success requires `completion: true` and a non-null `outputPath`.
148
- - Recovery, authorization, source immutability, and structured serialization
149
- invariants are preserved.
150
- - Wrappers delegate mutation and OOXML packaging to the library.
1
+ # Skill and Harness Authoring Contract
2
+
3
+ Use this page to create or update an agent skill, MCP server, or custom harness
4
+ for `@ansonlai/docx-redline-js`. Ordinary editing agents should load
5
+ [Agent Fast Start](AGENT_FAST_START.md), not this design contract.
6
+
7
+ > [!WARNING]
8
+ > Before 1.0, every integration must identify and pin its exact tested library
9
+ > release and required CLI contract/capabilities. Treat any different release or
10
+ > contract as unverified until compatibility tests pass.
11
+
12
+ Example declaration for a contract-8 shell integration that uses localized
13
+ operations over stdin:
14
+
15
+ ```json
16
+ {
17
+ "testedLibraryRelease": "0.7.1",
18
+ "requiredCliContractVersion": 8,
19
+ "requiredCapabilities": [
20
+ "operations-stdin",
21
+ "agent-safety-profile-v2",
22
+ "recovery-envelope-v1",
23
+ "localized-replacements-v1"
24
+ ]
25
+ }
26
+ ```
27
+
28
+ ## Runtime negotiation
29
+
30
+ Run `docx-redline version` before the first document operation. Require CLI
31
+ contract version 9 only when the integration sends `list-format` operations (see
32
+ List structure); otherwise contract version 8 suffices. Require
33
+ only the capabilities the integration actually uses. Check once per stable
34
+ runtime, fail closed when a requirement is absent, and never inspect bundles or
35
+ ZIP parts as a fallback.
36
+
37
+ ### List structure (optional)
38
+
39
+ Keep list structure out of an ordinary editing prompt. A wrapper that converts typed headers, changes a list's
40
+ format, or joins lists:
41
+
42
+ - negotiates `list-format-v1` (`convert-plain`) and, only if used, `list-format-change-v1` (`change-format`,
43
+ `join`);
44
+ - without the capability keeps its host path, never a `redline` or `list-change` that retypes markers (that
45
+ warns, or fails under the agent profile, and binds nothing);
46
+ - follows `use_list_format_*` recovery by sending `recovery.suggestedOperation` with the inspected target;
47
+ - sends a changed-text header (`1. Old` -> `1. New`) as a marker-preserving `redline` plus a `list-format` of that
48
+ paragraph in one batch, and splits them on `LIST_FORMAT_COMPOSITION_CONFLICT`.
49
+
50
+ Do not expose negotiated but unused capabilities as choices in the editing
51
+ prompt. `command-help-v1`, restore shortcuts, speculative apply, and localized
52
+ change summaries are optional requirements only for integrations that use them.
53
+
54
+ ## Safety invariants
55
+
56
+ Every generated integration must:
57
+
58
+ - inspect narrowly, retain `exactText`, and target with `paragraphId` plus
59
+ `fingerprint`;
60
+ - use strict targeting, package validation, and a derived output path;
61
+ - never overwrite the source unless `--in-place` is explicitly requested;
62
+ - require `completion: true` and a non-null `outputPath` before reporting
63
+ success;
64
+ - follow `error.recovery.action` and never retry unchanged failed arguments; and
65
+ - require explicit authorization before accepting/rejecting another reviewer's
66
+ work or deleting comments.
67
+
68
+ The CLI computes `completion` from write status, operation outcomes, commit
69
+ disposition, and localized accepted-view verification. Preserve the detailed
70
+ results for audit and recovery, but do not make the editing agent re-evaluate
71
+ those fields during ordinary success handling.
72
+
73
+ ## Workflow policy choices
74
+
75
+ Keep these exceptions separate from the default workflow:
76
+
77
+ | Choice | Default | Override only when |
78
+ |---|---|---|
79
+ | 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`). |
81
+ | Reviewer identity | `AI Redliner` is a valid visible fallback | The host supplies a preferred flag, operation author, or environment value. |
82
+ | Output | derived sibling | The caller deliberately selects another destination. |
83
+
84
+ Check `effectiveOptions` when an integration deliberately sets a policy.
85
+
86
+ ## Transport choices
87
+
88
+ A UTF-8 operations file and serializer-backed stdin are peer transports:
89
+
90
+ ```bash
91
+ docx-redline apply input.docx --operations operations.json --profile agent --output reviewed.docx
92
+ node emit-operations.mjs | docx-redline apply input.docx --operations - --profile agent --compact --output reviewed.docx
93
+ ```
94
+
95
+ Use `JSON.stringify` or a structured tool API. Never demonstrate `echo`, a
96
+ heredoc, or shell-interpolated document text. In JavaScript/TypeScript runtimes
97
+ (Node.js, browsers, Cloudflare Workers, Deno, Bun, or sandboxed environments like
98
+ n8n), an in-process wrapper should call `openDocx(uint8Array)` directly from
99
+ `@ansonlai/docx-redline-js` (or `@ansonlai/docx-redline-js/bundle` for zero-dependency
100
+ standalone sandboxes) and return the structured result instead of recreating CLI
101
+ serialization, ZIP handling, targeting, validation, or rollback.
102
+
103
+ ## Ordinary generated workflow
104
+
105
+ The first executable editing example in a generated skill must be focused
106
+ extraction:
107
+
108
+ ```bash
109
+ docx-redline extract input.docx --search "renewal notice" --around 3
110
+ ```
111
+
112
+ Then apply once using the inspected strong target. Use complete `modified` text
113
+ for a broad semantic revision or `replacements` for a small literal change in
114
+ that target. Keep requested relative wording relative unless the user asks for
115
+ a concrete value.
116
+
117
+ Follow `selection.nextAfter` when extraction is truncated. Output projections must retain
118
+ `selection`, target descriptors, `humanReference`, and revision
119
+ cues.
120
+
121
+ ### Observed restore branch
122
+
123
+ If the user requests restoration, search `--view rejected` first. Otherwise
124
+ follow an alternate-view `selection.hint` when returned. Restore only the
125
+ inspected strong target. Require `restore-shortcuts-v1` only if the integration
126
+ uses inline `--restore`; canonical operations remain available.
127
+
128
+ ### Optional speculative execution
129
+
130
+ Do not make speculative apply part of an ordinary generated workflow. A harness
131
+ may opt into `speculative-search-apply-v1` only when it owns a measured confidence
132
+ policy and accepts the recovery cost of a miss. Keep anchor selection,
133
+ directional windows, occurrence handling, and exact matching semantics out of
134
+ the ordinary editing prompt; link to the knowledge base or CLI help instead.
135
+
136
+ ## Recovery contract
137
+
138
+ Lead failure handling with these runtime fields:
139
+
140
+ 1. `error.recovery.action`
141
+ 2. `error.recovery.requiresReinspection`
142
+ 3. `error.recovery.requiresUserAuthorization`
143
+ 4. `error.recovery.sameArgumentsSafe`
144
+
145
+ Use `retryPlan.base` and its operation indexes to select the original or partial
146
+ output package. Do not reproduce an error-code decision tree in the skill.
147
+
148
+ ## Presentation rules
149
+
150
+ Use `humanReference`, `provision`, or `nearestHeading` in completion reports.
151
+ Paragraph indexes and `P55`-style references are machine coordinates, not
152
+ locations a document user can follow. Keep the exact machine target internally.
153
+
154
+ ## Generation checklist
155
+
156
+ - Exact tested release, contract 8 (9 only with `list-format`), and the minimum capability set are pinned.
157
+ - Runtime compatibility is checked once.
158
+ - The first ordinary command is focused extraction.
159
+ - Apply uses the inspected target with `modified` or localized `replacements`.
160
+ - Restore appears only as a user-requested or observed-view branch.
161
+ - Speculative execution is absent unless the harness owns a measured policy.
162
+ - Ordinary success requires `completion: true` and a non-null `outputPath`.
163
+ - Recovery, authorization, source immutability, and structured serialization
164
+ invariants are preserved.
165
+ - Wrappers delegate mutation and OOXML packaging to the library.