@ansonlai/docx-redline-js 0.4.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 +646 -288
- package/ARCHITECTURE.md +215 -9
- package/CHANGELOG.md +319 -0
- package/README.md +604 -360
- package/adapters/config.js +45 -43
- package/bin/docx-redline.js +3 -0
- package/core/list-targeting.js +101 -110
- package/core/paragraph-targeting.js +501 -61
- package/core/paragraph-text.js +209 -0
- package/core/redline-validation.js +11 -5
- package/core/revision-cloning.js +38 -0
- package/core/types.js +64 -10
- package/core/word-xml.js +43 -15
- package/dist/docx-redline-js.esm.js +3145 -505
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +88 -76
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/TESTING.md +342 -23
- package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +1669 -0
- package/docs/plans/2026-09-08-cross-author-revision-slicing.md +505 -0
- package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +669 -0
- package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +427 -0
- package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +519 -0
- package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +69 -0
- package/docs/plans/completed/structural-revision-capability-matrix.md +115 -0
- package/docs/schemas/document-operations.schema.json +109 -0
- package/docs/test-comparison-dashboard.html +4250 -7
- package/engine/formatting-removal.js +11 -2
- package/engine/oxml-engine.js +508 -336
- package/engine/reconstruction-mode.js +15 -14
- package/engine/reconstruction-writer.js +247 -142
- package/engine/route-selection.js +35 -0
- package/engine/rpr-helpers.js +334 -35
- package/engine/run-builders.js +239 -196
- package/engine/surgical-diff-application.js +407 -50
- package/engine/surgical-mode.js +142 -6
- package/engine/surgical-run-splitting.js +103 -0
- package/engine/surgical-spans.js +52 -1
- package/engine/table-cell-context.js +3 -6
- package/engine/table-mode.js +1 -1
- package/index.d.ts +234 -6
- package/index.js +24 -1
- package/node/cli.js +322 -0
- package/node/docx-document.js +302 -0
- package/node/index.d.ts +31 -0
- package/node/index.js +2 -0
- package/node/zip-archive.js +52 -0
- package/orchestration/list-markdown.js +10 -16
- package/orchestration/list-parsing.js +7 -12
- package/orchestration/list-structural-fallback.js +21 -10
- package/package.json +123 -102
- package/pipeline/content-analysis.js +12 -17
- package/pipeline/ingestion-export.js +3 -31
- package/pipeline/ingestion-paragraph.js +10 -5
- package/pipeline/list-generation.js +150 -55
- package/pipeline/list-markers.js +70 -3
- package/pipeline/serialization.js +4 -2
- package/pipeline/structured-content.js +160 -0
- package/scripts/apply_changes.mjs +27 -0
- package/scripts/benchmark-operation-session.mjs +137 -0
- package/scripts/benchmark-targeting-browser.html +74 -0
- package/scripts/benchmark-targeting-hot-paths.mjs +67 -0
- package/scripts/benchmark-test-runner.mjs +59 -0
- package/scripts/build-test-dashboard.mjs +23 -0
- package/scripts/export-lane1-fixtures.mjs +380 -0
- package/scripts/export-reredline-stress-fixtures.mjs +317 -0
- package/scripts/export-validation-fixtures.mjs +1 -1
- package/scripts/extract_text.mjs +7 -0
- package/scripts/generate-cross-author-slicing-fixtures.ps1 +256 -0
- package/scripts/generate-paragraph-boundary-fixtures.ps1 +215 -0
- package/scripts/generate-test-dashboard.mjs +362 -11
- package/scripts/lib/word-coverage-catalogue.mjs +6 -2
- package/scripts/profile-route-selection.mjs +19 -0
- package/scripts/render-agenda-multilevel.mjs +0 -5
- package/scripts/render-multilevel-cases.mjs +0 -1
- package/scripts/run-tests.mjs +107 -35
- package/scripts/word-com-corpus-suite.ps1 +3 -0
- package/scripts/word-com-differential.ps1 +64 -4
- package/scripts/word-com-suite.ps1 +3 -0
- package/services/batch-operation-orchestrator.js +513 -0
- package/services/capture-engine.js +226 -0
- package/services/comment-builders.js +23 -6
- package/services/comment-engine.js +108 -47
- package/services/comment-locator.js +187 -82
- package/services/comment-replies.js +95 -0
- package/services/document-inspection.js +258 -0
- package/services/document-operation-applier.js +372 -0
- package/services/document-operation-contract.js +345 -0
- package/services/document-operation-mutations.js +1749 -0
- package/services/document-operation-session.js +258 -0
- package/services/numbering-service.js +14 -5
- package/services/operation-heuristics.js +173 -0
- package/services/operation-preflight.js +390 -0
- package/services/receipt-collector.js +288 -0
- package/services/revision-comment-management.js +77 -5
- package/services/revision-token.js +290 -0
- package/services/standalone-docx-plumbing.js +123 -8
- package/services/standalone-operation-runner.d.ts +296 -0
- package/services/standalone-operation-runner.js +10 -1455
- package/services/table-reconciliation.js +15 -6
- package/docs/VALIDATION.md +0 -183
- package/docs/WORD-MANUAL-REVIEW.md +0 -138
- package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +0 -210
- /package/docs/plans/{2026-08-30-reliability-testing-improvements.md → completed/2026-08-30-reliability-testing-improvements.md} +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# Comment Anchor and CLI Reliability Plan
|
|
2
|
+
|
|
3
|
+
**Status:** Completed
|
|
4
|
+
**Date:** 2026-09-04
|
|
5
|
+
**Completed:** 2026-09-04
|
|
6
|
+
|
|
7
|
+
## Completion summary
|
|
8
|
+
|
|
9
|
+
- **Phase 1 complete (2026-09-04):** The CLI now validates per-command
|
|
10
|
+
options, rejects malformed or conflicting selectors, supports `--index`,
|
|
11
|
+
accepts the documented and compatibility range spellings, and reports
|
|
12
|
+
`indexBase: 1`.
|
|
13
|
+
- **Phase 2 complete (2026-09-04):** Operation normalization no longer derives
|
|
14
|
+
`textToComment` from paragraph target text. An omitted anchor now uses exact
|
|
15
|
+
canonical text from the resolved paragraph.
|
|
16
|
+
- **Phase 3 complete (2026-09-04):** Preflight and application now share a
|
|
17
|
+
non-mutating resolver with exact-match priority, unique ASCII-space/NBSP
|
|
18
|
+
matching, ambiguity detection, raw run offsets, and resolution diagnostics.
|
|
19
|
+
- **Phase 4 complete (2026-09-04):** Missing and ambiguous anchors now produce
|
|
20
|
+
structured errors, failed resolution does not allocate comment IDs, and
|
|
21
|
+
atomic package/CLI operations roll back without writing partial output.
|
|
22
|
+
- **Phase 5 complete (2026-09-04):** The package now publishes thin
|
|
23
|
+
`extract_text.mjs` and `apply_changes.mjs` compatibility entrypoints that
|
|
24
|
+
delegate to the supported CLI, accept legacy `changes` arrays, preserve the
|
|
25
|
+
legacy positional invocation and author fallback, and have subprocess tests
|
|
26
|
+
for atomic failure and output safety. No local `docx-redline` plugin or
|
|
27
|
+
marketplace entry is installed in this environment, so rollout into the
|
|
28
|
+
reported `/mnt/skills/plugins/docx-redline` installation remains a release
|
|
29
|
+
deployment step rather than an additional implementation.
|
|
30
|
+
- **Previous-release deletion regression closed (2026-09-04):** A
|
|
31
|
+
whole-paragraph delete now stops with `COMMENTED_CONTENT_DELETE` when the
|
|
32
|
+
target carries an existing comment, returning comment IDs and package-level
|
|
33
|
+
author/text metadata for follow-up. The transaction remains byte-identical,
|
|
34
|
+
and package validation independently rejects partial ranges, dangling
|
|
35
|
+
usages, orphan definitions, and duplicate definitions. Regression coverage
|
|
36
|
+
includes preflight diagnostics, atomic rollback, metadata preservation,
|
|
37
|
+
point-comment validity, and each malformed comment-linkage shape.
|
|
38
|
+
|
|
39
|
+
## Summary
|
|
40
|
+
|
|
41
|
+
A reported agent workflow exposed three related reliability problems:
|
|
42
|
+
|
|
43
|
+
1. Unsupported or malformed extraction filters can be silently ignored, so an
|
|
44
|
+
agent may receive the wrong paragraph while believing that it selected a
|
|
45
|
+
specific index or range.
|
|
46
|
+
2. Paragraph targeting and comment-anchor placement use different whitespace
|
|
47
|
+
rules. A paragraph can resolve successfully through normalized matching but
|
|
48
|
+
its comment anchor can subsequently fail on an ordinary-space versus
|
|
49
|
+
non-breaking-space difference.
|
|
50
|
+
3. A missing comment anchor is returned as `no_change` plus a warning. Atomic
|
|
51
|
+
batches only roll back explicit errors, so a mixed batch can write a
|
|
52
|
+
partially modified document and still appear successful.
|
|
53
|
+
|
|
54
|
+
The report used the legacy `extract_text.mjs` and `apply_changes.mjs` plugin
|
|
55
|
+
wrappers, while this repository now provides the `docx-redline` CLI and Node
|
|
56
|
+
package facade. The library must be safe independently of those wrappers, and
|
|
57
|
+
the wrappers must be migrated so they cannot reintroduce the old behavior.
|
|
58
|
+
|
|
59
|
+
## Goals
|
|
60
|
+
|
|
61
|
+
- Make paragraph selection explicit, predictable, and consistently 1-based.
|
|
62
|
+
- Make preflight and application resolve comment anchors identically.
|
|
63
|
+
- Tolerate safe space-representation differences without changing document
|
|
64
|
+
text or selecting an ambiguous anchor.
|
|
65
|
+
- Treat unresolved and ambiguous anchors as operation failures.
|
|
66
|
+
- Guarantee byte-exact rollback and no output write for failed atomic batches.
|
|
67
|
+
- Preserve intentional no-ops as `no_change` rather than treating every
|
|
68
|
+
unchanged result as an error.
|
|
69
|
+
- Provide regression coverage at the unit, service, CLI, DOCX package, and
|
|
70
|
+
agent-workflow levels.
|
|
71
|
+
|
|
72
|
+
## Non-goals
|
|
73
|
+
|
|
74
|
+
- Do not split a single OOXML `w:p` into multiple paragraphs because formatting
|
|
75
|
+
makes part of it look like a heading. Inspection must reflect the source
|
|
76
|
+
document structure.
|
|
77
|
+
- Do not normalize or rewrite the contents of `w:t` nodes while locating an
|
|
78
|
+
anchor.
|
|
79
|
+
- Do not make approximate or fuzzy comment-anchor selection silently succeed.
|
|
80
|
+
- Do not change `atomic: false` into all-or-nothing behavior.
|
|
81
|
+
- Do not retain two independent batch engines for the package and plugin
|
|
82
|
+
workflows.
|
|
83
|
+
|
|
84
|
+
## Confirmed failure paths (historical)
|
|
85
|
+
|
|
86
|
+
The sections below record the pre-implementation behavior that motivated the
|
|
87
|
+
work; they are retained for regression context.
|
|
88
|
+
|
|
89
|
+
### Extraction filters
|
|
90
|
+
|
|
91
|
+
`node/cli.js` currently recognizes `--indexes 2,5,8` and `--range 2:8`.
|
|
92
|
+
Unrecognized flags and malformed values can pass through argument parsing
|
|
93
|
+
without an error. For example, `--index 7` or `--range 5-9` can result in an
|
|
94
|
+
unfiltered extraction rather than a selection failure.
|
|
95
|
+
|
|
96
|
+
### Anchor whitespace
|
|
97
|
+
|
|
98
|
+
Paragraph resolution uses `normalizeWhitespaceForTargeting`, but
|
|
99
|
+
`findTextInParagraphIndex` uses a literal `String#indexOf`. In addition,
|
|
100
|
+
`normalizeDocumentOperation` currently derives `textToComment` from target text
|
|
101
|
+
when the caller did not explicitly provide an anchor. This creates a split
|
|
102
|
+
contract: the supplied target can resolve a paragraph after whitespace
|
|
103
|
+
normalization, then fail when reused as an exact anchor.
|
|
104
|
+
|
|
105
|
+
The current runner can therefore produce an outcome shaped like:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"hasChanges": false,
|
|
110
|
+
"results": [
|
|
111
|
+
{
|
|
112
|
+
"type": "comment",
|
|
113
|
+
"status": "no_change",
|
|
114
|
+
"warnings": ["Could not find ..."]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
even though the requested operation was not successfully completed.
|
|
121
|
+
|
|
122
|
+
### Atomic classification
|
|
123
|
+
|
|
124
|
+
`applyCommentToParagraphByExactText` returns `hasChanges: false` and warnings
|
|
125
|
+
when no comment was placed. `applyOperationsToDocumentXml` only marks a batch
|
|
126
|
+
operation as failed when `status === "error"` or an `error` object exists. If
|
|
127
|
+
another operation did make a change, the batch can serialize and commit that
|
|
128
|
+
partial result despite atomic mode.
|
|
129
|
+
|
|
130
|
+
Preflight already reports `ANCHOR_NOT_FOUND`, so preflight and apply disagree
|
|
131
|
+
about the same input.
|
|
132
|
+
|
|
133
|
+
## Delivered behavior contract
|
|
134
|
+
|
|
135
|
+
### CLI filters
|
|
136
|
+
|
|
137
|
+
- Paragraph indexes and `P<n>` references remain 1-based.
|
|
138
|
+
- `--index N` is accepted as the single-paragraph form of `--indexes N`.
|
|
139
|
+
- `--range START:END` remains canonical.
|
|
140
|
+
- `START-END` and `START,END` may be accepted as compatibility spellings if
|
|
141
|
+
they can be parsed without ambiguity.
|
|
142
|
+
- Missing, non-numeric, zero, negative, reversed, or partially numeric values
|
|
143
|
+
return `status: "error"` with `INVALID_FILTER`.
|
|
144
|
+
- Unknown flags return `UNKNOWN_OPTION` rather than being ignored.
|
|
145
|
+
- Inspection and extraction results state `indexBase: 1`.
|
|
146
|
+
- A selection filter that matches no paragraphs succeeds with an empty list;
|
|
147
|
+
a malformed filter fails. These cases must remain distinguishable.
|
|
148
|
+
|
|
149
|
+
### Comment anchors
|
|
150
|
+
|
|
151
|
+
- `target` identifies the paragraph; `textToComment` identifies an optional
|
|
152
|
+
sub-paragraph anchor.
|
|
153
|
+
- When `textToComment` is omitted, use the exact canonical text of the resolved
|
|
154
|
+
paragraph. Do not copy caller-supplied target text into the anchor field.
|
|
155
|
+
- For an explicit anchor, try an exact match first.
|
|
156
|
+
- If exact matching fails, allow a mapped space-equivalent match that treats
|
|
157
|
+
ordinary spaces and non-breaking spaces as equivalent. Keep tabs and line
|
|
158
|
+
breaks structurally distinct unless a separate, explicit policy is added.
|
|
159
|
+
- Map normalized match offsets back to the original run and character offsets;
|
|
160
|
+
never rewrite the source text to make a match possible.
|
|
161
|
+
- If no match exists, return `ANCHOR_NOT_FOUND`.
|
|
162
|
+
- If normalization produces more than one candidate, return
|
|
163
|
+
`AMBIGUOUS_ANCHOR` with candidate offsets. Do not select the first candidate.
|
|
164
|
+
- Report how an anchor resolved, for example `exact_anchor`,
|
|
165
|
+
`space_equivalent_anchor`, or `whole_paragraph`.
|
|
166
|
+
|
|
167
|
+
### Operation and batch status
|
|
168
|
+
|
|
169
|
+
- A comment request that places no comment is an error unless the public API
|
|
170
|
+
explicitly defines an idempotent-comment mode in the future.
|
|
171
|
+
- Missing and ambiguous anchors return `status: "error"` and a structured
|
|
172
|
+
error object, not only a warning.
|
|
173
|
+
- `no_change` remains valid for genuine successful no-ops such as an identical
|
|
174
|
+
replacement. Do not make every unchanged operation fatal.
|
|
175
|
+
- An atomic batch containing an anchor error returns the original
|
|
176
|
+
`documentXml`, `hasChanges: false`, `rolledBack: true`, no comments or
|
|
177
|
+
numbering artifacts, and `BATCH_OPERATION_FAILED` at batch level.
|
|
178
|
+
- The package facade returns `written: false`, and the CLI does not create an
|
|
179
|
+
output file.
|
|
180
|
+
- With `atomic: false`, successful operations may be retained, but failed items
|
|
181
|
+
remain explicitly marked `error`.
|
|
182
|
+
|
|
183
|
+
## Completed implementation phases
|
|
184
|
+
|
|
185
|
+
### Phase 1: Harden CLI parsing and selection (completed)
|
|
186
|
+
|
|
187
|
+
Update `node/cli.js` to validate commands against per-command option sets and
|
|
188
|
+
parse selectors through one dedicated helper.
|
|
189
|
+
|
|
190
|
+
- Add `--index` as a supported alias.
|
|
191
|
+
- Parse and validate all supported range spellings in one place.
|
|
192
|
+
- Detect conflicting selectors such as `--index` together with `--range`.
|
|
193
|
+
- Reject unsupported flags and invalid selector values before opening or
|
|
194
|
+
inspecting a DOCX.
|
|
195
|
+
- Add `indexBase: 1` to inspection/extraction CLI results and document the
|
|
196
|
+
convention in `README.md` and `docs/AGENT-WORKFLOW.md`.
|
|
197
|
+
|
|
198
|
+
Keep `inspectDocumentParts` focused on structured filtering; user-facing string
|
|
199
|
+
syntax belongs at the CLI boundary.
|
|
200
|
+
|
|
201
|
+
### Phase 2: Separate target normalization from anchor defaults (completed)
|
|
202
|
+
|
|
203
|
+
Update `services/document-operation-contract.js` so a missing
|
|
204
|
+
`textToComment` remains missing after normalization. Let
|
|
205
|
+
`applyCommentToParagraphByExactText` derive a whole-paragraph anchor from the
|
|
206
|
+
resolved paragraph's canonical text.
|
|
207
|
+
|
|
208
|
+
Preserve compatibility aliases for operation types and target descriptors, but
|
|
209
|
+
do not infer a sub-paragraph anchor from `target.exactText`.
|
|
210
|
+
|
|
211
|
+
### Phase 3: Build a shared anchor resolver (completed)
|
|
212
|
+
|
|
213
|
+
Refactor `services/comment-locator.js` to expose a read-only anchor-resolution
|
|
214
|
+
helper used before marker insertion.
|
|
215
|
+
|
|
216
|
+
The resolver should:
|
|
217
|
+
|
|
218
|
+
1. Build a canonical paragraph index with source offsets and run ownership.
|
|
219
|
+
2. Search exact text and return the unique raw span when found.
|
|
220
|
+
3. Build a mapped representation for ordinary-space/NBSP equivalence.
|
|
221
|
+
4. Convert a normalized candidate back to raw start and end offsets.
|
|
222
|
+
5. Detect multiple candidates.
|
|
223
|
+
6. Return a structured result without modifying the DOM.
|
|
224
|
+
|
|
225
|
+
Marker injection should consume the resolved raw span rather than perform a
|
|
226
|
+
second search. Allocate a comment ID only after successful resolution so a
|
|
227
|
+
failed attempt cannot consume package-scoped IDs.
|
|
228
|
+
|
|
229
|
+
Preflight should call the same resolver instead of `paragraphText.includes`.
|
|
230
|
+
This makes diagnostics and application agree by construction.
|
|
231
|
+
|
|
232
|
+
### Phase 4: Propagate structured anchor failures (completed)
|
|
233
|
+
|
|
234
|
+
Update `services/comment-engine.js`,
|
|
235
|
+
`services/document-operation-mutations.js`, and
|
|
236
|
+
`services/document-operation-applier.js` to preserve structured locator
|
|
237
|
+
errors.
|
|
238
|
+
|
|
239
|
+
- Return `ANCHOR_NOT_FOUND` and `AMBIGUOUS_ANCHOR` with useful diagnostics.
|
|
240
|
+
- Restore the per-operation DOM savepoint on either error.
|
|
241
|
+
- Ensure failed comment definitions and markers are never added to output.
|
|
242
|
+
- Retain warnings for non-fatal information only.
|
|
243
|
+
|
|
244
|
+
The batch orchestrator should continue to roll back explicit errors. Add a
|
|
245
|
+
defensive invariant that a comment operation reporting zero applied comments
|
|
246
|
+
cannot be classified as a successful `no_change`.
|
|
247
|
+
|
|
248
|
+
### Phase 5: Migrate the external plugin wrappers (completed)
|
|
249
|
+
|
|
250
|
+
The wrapper copies from the original report live outside this repository. The
|
|
251
|
+
package now includes compatible `extract_text.mjs` and `apply_changes.mjs`
|
|
252
|
+
entrypoints; update the separately installed plugin copies after publishing.
|
|
253
|
+
|
|
254
|
+
- Delegate extraction and application to the supported Node facade or CLI.
|
|
255
|
+
- Use 1-based indexes and emit `P<n>` references.
|
|
256
|
+
- Default to strict targets, atomic batches, and package validation.
|
|
257
|
+
- Run preflight before apply or surface equivalent apply-time errors.
|
|
258
|
+
- Exit nonzero and omit the output path when an atomic transaction fails.
|
|
259
|
+
- Pin a package release containing these fixes and remove duplicated legacy
|
|
260
|
+
targeting logic.
|
|
261
|
+
|
|
262
|
+
## Regression test strategy and coverage
|
|
263
|
+
|
|
264
|
+
The tests below deliberately overlap at different boundaries. A locator unit
|
|
265
|
+
test alone will not catch a wrapper that discards its error, and a CLI test
|
|
266
|
+
alone will not isolate an offset-mapping regression.
|
|
267
|
+
|
|
268
|
+
### CLI argument and filter tests
|
|
269
|
+
|
|
270
|
+
Extend `tests/agent_cli_edge_tests.mjs` with:
|
|
271
|
+
|
|
272
|
+
- `--index 7` returns only paragraph 7.
|
|
273
|
+
- `--indexes 2,5,8` returns exactly those paragraphs in document order.
|
|
274
|
+
- Canonical `--range 5:9` includes both endpoints.
|
|
275
|
+
- Supported compatibility range spellings return the same indexes.
|
|
276
|
+
- `--range 7:7` returns one paragraph.
|
|
277
|
+
- Zero, negative, reversed, missing-end, non-numeric, and decimal selectors
|
|
278
|
+
return `INVALID_FILTER`.
|
|
279
|
+
- `--index` combined with `--range` returns a clear conflict error.
|
|
280
|
+
- A valid range outside the document returns an empty paragraph list.
|
|
281
|
+
- A misspelled flag such as `--indxe` returns `UNKNOWN_OPTION` and never falls
|
|
282
|
+
back to an unfiltered extraction.
|
|
283
|
+
- `runCli` returns a nonzero exit code for all invalid selector cases.
|
|
284
|
+
- Inspection/extraction JSON includes `indexBase: 1` and stable `P<n>` refs.
|
|
285
|
+
|
|
286
|
+
Add command-specific unknown-option tests so an `apply`-only flag cannot be
|
|
287
|
+
silently accepted by `extract`, and vice versa.
|
|
288
|
+
|
|
289
|
+
### Canonical extraction tests
|
|
290
|
+
|
|
291
|
+
Extend `tests/canonical_paragraph_text_tests.mjs` and
|
|
292
|
+
`tests/document_inspection_edge_tests.mjs` with paragraphs containing:
|
|
293
|
+
|
|
294
|
+
- ordinary spaces split across differently formatted runs;
|
|
295
|
+
- NBSPs before and after an underlined run;
|
|
296
|
+
- repeated spaces and leading/trailing spaces;
|
|
297
|
+
- tabs, line breaks, soft hyphens, and non-breaking hyphens;
|
|
298
|
+
- comments and tracked revisions crossing run boundaries.
|
|
299
|
+
|
|
300
|
+
Assert that extraction preserves the exact source characters and never
|
|
301
|
+
synthesizes NBSPs because formatting changes between runs. Verify accepted,
|
|
302
|
+
rejected, and current revision views independently.
|
|
303
|
+
|
|
304
|
+
### Anchor resolver unit tests
|
|
305
|
+
|
|
306
|
+
Extend `tests/comment_tests.mjs` or add a focused
|
|
307
|
+
`tests/comment_anchor_locator_tests.mjs` covering:
|
|
308
|
+
|
|
309
|
+
- exact anchor within one run;
|
|
310
|
+
- exact anchor spanning two or more formatted runs;
|
|
311
|
+
- anchor beginning or ending at a run boundary;
|
|
312
|
+
- ASCII spaces matching source NBSPs;
|
|
313
|
+
- source ASCII spaces matching anchor NBSPs;
|
|
314
|
+
- multiple adjacent space characters with correct raw offset mapping;
|
|
315
|
+
- punctuation or letter differences remaining unmatched;
|
|
316
|
+
- tabs and line breaks not being silently treated as ordinary spaces;
|
|
317
|
+
- duplicate exact anchors returning `AMBIGUOUS_ANCHOR` if uniqueness is
|
|
318
|
+
required by the selected policy;
|
|
319
|
+
- duplicate space-equivalent anchors returning `AMBIGUOUS_ANCHOR`;
|
|
320
|
+
- missing anchors returning `ANCHOR_NOT_FOUND`;
|
|
321
|
+
- failed resolution leaving the paragraph DOM byte-equivalent after
|
|
322
|
+
serialization;
|
|
323
|
+
- successful insertion preserving formatting, hyperlinks, bookmarks,
|
|
324
|
+
revision wrappers, and existing comment markers around the anchor;
|
|
325
|
+
- run splitting continuing to allocate unique `w:rPrChange` IDs.
|
|
326
|
+
|
|
327
|
+
For every successful case, inspect the actual text enclosed by
|
|
328
|
+
`w:commentRangeStart` and `w:commentRangeEnd`, not merely the presence of marker
|
|
329
|
+
elements.
|
|
330
|
+
|
|
331
|
+
### Operation contract and preflight parity tests
|
|
332
|
+
|
|
333
|
+
Extend `tests/agent_operation_contract_tests.mjs` with:
|
|
334
|
+
|
|
335
|
+
- a comment target with no `textToComment` anchors the exact resolved
|
|
336
|
+
paragraph, even when caller target whitespace differs;
|
|
337
|
+
- an explicit space-equivalent anchor resolves identically in preflight and
|
|
338
|
+
apply;
|
|
339
|
+
- a missing anchor produces `ANCHOR_NOT_FOUND` in both paths;
|
|
340
|
+
- an ambiguous anchor produces `AMBIGUOUS_ANCHOR` in both paths;
|
|
341
|
+
- `resolvedBy`, resolved raw offsets, and candidate diagnostics agree between
|
|
342
|
+
preflight and apply;
|
|
343
|
+
- a genuine identical replacement remains `no_change` and is not mislabeled
|
|
344
|
+
as an error.
|
|
345
|
+
|
|
346
|
+
Use a table-driven parity test that runs each anchor scenario first through
|
|
347
|
+
`preflightOperations` and then through `applyOperationToDocumentXml`.
|
|
348
|
+
|
|
349
|
+
### Atomic batch tests
|
|
350
|
+
|
|
351
|
+
Extend `tests/standalone_operation_runner_tests.mjs` with:
|
|
352
|
+
|
|
353
|
+
- seven valid operations plus one missing comment anchor;
|
|
354
|
+
- one successful replacement followed by a failed comment;
|
|
355
|
+
- a failed comment scheduled before a replacement;
|
|
356
|
+
- failures at the first, middle, and last logical input indexes;
|
|
357
|
+
- `continueOnError: true` still reporting every attempted operation while the
|
|
358
|
+
atomic transaction rolls back;
|
|
359
|
+
- `continueOnError: false` stopping after the first failure and rolling back;
|
|
360
|
+
- `atomic: false` retaining valid operations while reporting the comment as
|
|
361
|
+
`error`;
|
|
362
|
+
- an all-no-op, genuinely successful batch not being treated as failed;
|
|
363
|
+
- rollback clearing comment and numbering artifacts and leaving runtime target
|
|
364
|
+
snapshots uncommitted;
|
|
365
|
+
- input and returned rollback XML being byte-identical.
|
|
366
|
+
|
|
367
|
+
Assert both original result order and execution order because comments are
|
|
368
|
+
scheduled before replacements.
|
|
369
|
+
|
|
370
|
+
### Package facade and CLI transaction tests
|
|
371
|
+
|
|
372
|
+
Extend `tests/docx_package_facade_tests.mjs`,
|
|
373
|
+
`tests/docx_package_transaction_edge_tests.mjs`, and
|
|
374
|
+
`tests/agent_cli_tests.mjs` with:
|
|
375
|
+
|
|
376
|
+
- a mixed batch whose one missing anchor yields `written: false`;
|
|
377
|
+
- `toBuffer()` returning bytes identical to the original DOCX after rollback;
|
|
378
|
+
- no output file being created by CLI apply after an atomic failure;
|
|
379
|
+
- an existing requested output file remaining untouched after failure;
|
|
380
|
+
- no orphaned comment relationship, content-type entry, marker, or definition;
|
|
381
|
+
- retrying after a failed comment not skipping or colliding comment IDs;
|
|
382
|
+
- existing comments with high IDs remaining intact;
|
|
383
|
+
- unrelated ZIP entries remaining byte-identical;
|
|
384
|
+
- CLI JSON reporting batch error, failed item index, error code, and
|
|
385
|
+
`outputPath: null`;
|
|
386
|
+
- `runCli` returning nonzero for the failed transaction.
|
|
387
|
+
|
|
388
|
+
### End-to-end reported-case fixture
|
|
389
|
+
|
|
390
|
+
Create a minimal, reviewable DOCX fixture modeled on the report:
|
|
391
|
+
|
|
392
|
+
- at least ten paragraphs so selector behavior is observable;
|
|
393
|
+
- a target paragraph containing multiple runs and underlined text;
|
|
394
|
+
- NBSPs around `Subscription Term`;
|
|
395
|
+
- a heading-looking run at the end of the same OOXML paragraph;
|
|
396
|
+
- one existing comment elsewhere in the document;
|
|
397
|
+
- an operation file with seven valid changes and one whitespace-mismatched
|
|
398
|
+
comment anchor.
|
|
399
|
+
|
|
400
|
+
Exercise the complete workflow:
|
|
401
|
+
|
|
402
|
+
1. Extract the target using `--index` and `--range`.
|
|
403
|
+
2. Verify exact text and 1-based references.
|
|
404
|
+
3. Preflight the operation file.
|
|
405
|
+
4. Apply it atomically.
|
|
406
|
+
5. Validate the output package.
|
|
407
|
+
6. Reinspect comments and verify the intended anchored text.
|
|
408
|
+
|
|
409
|
+
Include two variants:
|
|
410
|
+
|
|
411
|
+
- a safely space-equivalent anchor that should apply all eight operations;
|
|
412
|
+
- a genuinely absent anchor that must roll back all eight operations and write
|
|
413
|
+
no output.
|
|
414
|
+
|
|
415
|
+
### Property and fuzz tests
|
|
416
|
+
|
|
417
|
+
Add bounded randomized coverage for the shared anchor resolver:
|
|
418
|
+
|
|
419
|
+
- generate paragraph text split across random run boundaries;
|
|
420
|
+
- randomly replace ordinary spaces with NBSPs in either source or anchor;
|
|
421
|
+
- retain an oracle mapping from logical anchor characters to raw offsets;
|
|
422
|
+
- assert that successful resolution returns the oracle span;
|
|
423
|
+
- insert duplicate anchors and assert ambiguity rather than first-match
|
|
424
|
+
selection;
|
|
425
|
+
- mutate one non-space character and assert no match;
|
|
426
|
+
- verify resolver calls do not mutate their input DOM.
|
|
427
|
+
|
|
428
|
+
Use fixed seeds and print the seed with failures so every case is reproducible.
|
|
429
|
+
|
|
430
|
+
### Type and schema tests
|
|
431
|
+
|
|
432
|
+
Update declarations and schemas if new anchor diagnostics are public:
|
|
433
|
+
|
|
434
|
+
- `ANCHOR_NOT_FOUND` and `AMBIGUOUS_ANCHOR` result shapes compile correctly;
|
|
435
|
+
- `resolvedBy` accepts the new anchor-resolution values;
|
|
436
|
+
- operation schemas continue to make `textToComment` optional;
|
|
437
|
+
- examples with descriptor-only whole-paragraph comments type-check;
|
|
438
|
+
- examples with explicit sub-paragraph anchors type-check.
|
|
439
|
+
|
|
440
|
+
### Performance tests
|
|
441
|
+
|
|
442
|
+
Extend the existing session benchmarks or performance boundary tests to verify:
|
|
443
|
+
|
|
444
|
+
- anchor indexing remains linear in paragraph size;
|
|
445
|
+
- exact matches do not build the fallback mapped representation unnecessarily;
|
|
446
|
+
- batches with many comments reuse paragraph indexes where safe;
|
|
447
|
+
- preflight and application do not repeatedly serialize the entire document;
|
|
448
|
+
- ambiguity detection remains bounded for long repeated paragraphs.
|
|
449
|
+
|
|
450
|
+
Set generous regression thresholds appropriate for CI; the purpose is to catch
|
|
451
|
+
accidental quadratic scans, not microbenchmark noise.
|
|
452
|
+
|
|
453
|
+
## Validation commands
|
|
454
|
+
|
|
455
|
+
During implementation, run the focused suites after each phase:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
node tests/agent_cli_edge_tests.mjs
|
|
459
|
+
node tests/canonical_paragraph_text_tests.mjs
|
|
460
|
+
node tests/comment_tests.mjs
|
|
461
|
+
node tests/agent_operation_contract_tests.mjs
|
|
462
|
+
node tests/standalone_operation_runner_tests.mjs
|
|
463
|
+
node tests/docx_package_facade_tests.mjs
|
|
464
|
+
node tests/docx_package_transaction_edge_tests.mjs
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Before release, run:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
npm test
|
|
471
|
+
npm run test:isolation
|
|
472
|
+
npm run check:types
|
|
473
|
+
npm run lint
|
|
474
|
+
node scripts/export-validation-fixtures.mjs
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
If Word is available on Windows, also run the generated fixture through:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
npm run smoke:word -- path/to/comment-anchor-regression.docx
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Manually confirm that Word opens the output without repair, displays all
|
|
484
|
+
expected comments, selects the intended text for each comment, and accepts or
|
|
485
|
+
rejects unrelated tracked changes correctly.
|
|
486
|
+
|
|
487
|
+
## Acceptance criteria
|
|
488
|
+
|
|
489
|
+
- Invalid or unknown extraction filters can never silently return an
|
|
490
|
+
unfiltered document.
|
|
491
|
+
- All documented paragraph indexes and references are consistently 1-based.
|
|
492
|
+
- Whole-paragraph comments use resolved document text, not reconstructed caller
|
|
493
|
+
text.
|
|
494
|
+
- Safe ASCII-space/NBSP anchor differences resolve without modifying source
|
|
495
|
+
text.
|
|
496
|
+
- Missing and ambiguous anchors are structured errors in both preflight and
|
|
497
|
+
apply.
|
|
498
|
+
- No failed comment operation is reported only as `no_change` plus a warning.
|
|
499
|
+
- Atomic batches with any failed anchor return and preserve the exact original
|
|
500
|
+
DOCX bytes and create no output file.
|
|
501
|
+
- Intentional no-ops remain supported.
|
|
502
|
+
- The legacy plugin wrappers use the supported facade/CLI and surface the same
|
|
503
|
+
failure semantics.
|
|
504
|
+
- Focused, full, isolation, type, lint, package-validation, and optional Word
|
|
505
|
+
smoke tests pass.
|
|
506
|
+
|
|
507
|
+
## External release handoff
|
|
508
|
+
|
|
509
|
+
These deployment steps do not affect the completed implementation status:
|
|
510
|
+
|
|
511
|
+
1. Completed: land the library and CLI changes with the reported-case fixture.
|
|
512
|
+
2. Publish a new package version and document the status-contract change.
|
|
513
|
+
3. Update and republish the external docx-redline plugin/skill wrappers with the
|
|
514
|
+
new package pinned.
|
|
515
|
+
4. Run the reported workflow against the installed plugin, not only the source
|
|
516
|
+
checkout.
|
|
517
|
+
5. Keep compatibility selector spellings for at least one release cycle, while
|
|
518
|
+
documenting `--index`, `--indexes`, and colon-delimited `--range` as the
|
|
519
|
+
canonical interface.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Performance Consolidation Reference
|
|
2
|
+
|
|
3
|
+
This reference records the accuracy boundaries used by Phases 3–5 of the
|
|
4
|
+
performance and complexity plan. Optimizations may share metadata, grammar, or
|
|
5
|
+
route implementation only when the observable text and OOXML contracts remain
|
|
6
|
+
the same.
|
|
7
|
+
|
|
8
|
+
## Paragraph-text walker inventory
|
|
9
|
+
|
|
10
|
+
`core/paragraph-text.js` is the only canonical visible-text definition.
|
|
11
|
+
|
|
12
|
+
| Walker | Category | Purpose and parity rule |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `extractCanonicalParagraphText` / `readCanonicalRunText` | Canonical | Accepted, current, and rejected visible text for targeting and inspection. |
|
|
15
|
+
| `pipeline/ingestion-paragraph.js` | Specialized mapping | Builds run models and offsets. Its accepted visible projection matches canonical tabs, breaks, hyperlinks, soft hyphens, and non-breaking hyphens. Deleted content remains a zero-width model entry. |
|
|
16
|
+
| `engine/surgical-spans.js` | Specialized mapping | Retains run ownership and offsets for edits. Its supported visible projection matches canonical text; existing revisions are rejected or normalized before this route. |
|
|
17
|
+
| `engine/format-extraction.js` and `format-paragraph-targeting.js` | Specialized formatting map | Associates visible characters with runs and formatting spans. It is not a replacement canonical extractor. |
|
|
18
|
+
| `engine/reconstruction-mapper.js` | Specialized structural map | Uses sentinels for fields, drawings, content controls, comments, notes, and breaks so structures survive reconstruction. Sentinel text is intentionally not canonical visible text. |
|
|
19
|
+
| `services/comment-locator.js` | Specialized anchor map | Maps exact visible characters back to individual text nodes for comment range insertion. |
|
|
20
|
+
| `engine/table-cell-context.js` | Canonical consumer | Uses `extractCanonicalParagraphText` for target matching; it does not maintain a second text definition. |
|
|
21
|
+
| `services/document-inspection.js` | Canonical consumer | Uses canonical paragraph and run text for externally visible inventory. |
|
|
22
|
+
|
|
23
|
+
Parity coverage lives in
|
|
24
|
+
`tests/performance_phase4_list_and_text_parity_tests.mjs`, with broader
|
|
25
|
+
revision, field, note, comment, and structural preservation coverage in the
|
|
26
|
+
canonical text, structural field/tab, engine reliability, move revision, and
|
|
27
|
+
round-trip suites.
|
|
28
|
+
|
|
29
|
+
## Shared list vocabulary
|
|
30
|
+
|
|
31
|
+
`pipeline/list-markers.js` owns marker grammar, marker classification,
|
|
32
|
+
numbering-style inference, outline-depth parsing, and the common parsed
|
|
33
|
+
list-item representation. Pipeline parsing, orchestration parsing, targeting,
|
|
34
|
+
and fallback consume these helpers. Numbering allocation, target selection,
|
|
35
|
+
OOXML writing, and package merging remain separate responsibilities.
|
|
36
|
+
|
|
37
|
+
The shared marker vocabulary is:
|
|
38
|
+
|
|
39
|
+
- marker type: `bullet` or `numbered`;
|
|
40
|
+
- numbering style: `bullet`, `decimal`, `lowerAlpha`, `upperAlpha`,
|
|
41
|
+
`lowerRoman`, or `upperRoman`;
|
|
42
|
+
- level: indentation-derived level from 0 through 8;
|
|
43
|
+
- outline level: optional depth encoded by composite decimal markers.
|
|
44
|
+
|
|
45
|
+
## Reconciliation capability matrix
|
|
46
|
+
|
|
47
|
+
The executable internal record is
|
|
48
|
+
`engine/route-selection.js::RECONCILIATION_CAPABILITY_MATRIX`.
|
|
49
|
+
|
|
50
|
+
| Route | Primary capability | Accuracy boundary |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `formatOnly` | Formatting-only edits/removal | Retains run-scoped formatting and table-cell scoping. |
|
|
53
|
+
| `surgical` | Localized edits in table-bearing scopes | Preserves surrounding runs, hyperlinks, fields, comments, and notes. |
|
|
54
|
+
| `reconstruction` | General paragraph reconstruction | Preserves structural nodes through sentinels and reference maps. |
|
|
55
|
+
| `table` | Markdown table creation/reconciliation | Owns table structure; formatting behavior remains cell-dependent. |
|
|
56
|
+
| `listDirect` | One-source-paragraph expansion into a list | Uses focused list generation with numbering artifacts and established result wrapping. |
|
|
57
|
+
| `listCompatibilityPipeline` | Multi-paragraph marked-list edits | Retains run-aware patching and paragraph-boundary behavior until broader parity evidence permits migration. |
|
|
58
|
+
|
|
59
|
+
Route selection can be observed without changing result shapes by passing the
|
|
60
|
+
internal `_routeInstrumentation.onRoute` callback. Run `npm run profile:routes`
|
|
61
|
+
for the checked synthetic frequency set. This instrumentation is diagnostic;
|
|
62
|
+
it is not a public routing-policy switch.
|
|
63
|
+
|
|
64
|
+
## Compatibility window
|
|
65
|
+
|
|
66
|
+
`ReconciliationPipeline`, `serializeToOoxml`, and `wrapInDocumentFragment`
|
|
67
|
+
remain public and supported for the current major version. Internal callers may
|
|
68
|
+
use focused modules directly, but removal of these exports requires a future
|
|
69
|
+
major release, deprecation notice, and migration example.
|