@ansonlai/docx-redline-js 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -11,8 +11,8 @@ or an installed plugin bundle to infer public behavior.
11
11
  | Edit or review a complete `.docx` | [Agent Fast Start](docs/AGENT_FAST_START.md) and the `docx-redline` CLI |
12
12
  | Build or update an agent skill/tool wrapper | [Skill Authoring Contract](docs/SKILL_AUTHORING.md), then [README wrapper example](README.md#example-agent-session-wrapper-development-only) |
13
13
  | Change paragraph/range reconciliation | `index.js` → `engine/oxml-engine.js` → selected `engine/*-mode.js` |
14
- | Change complete-document operations | `services/standalone-operation-runner.js` → `services/document-operation-*.js` |
15
- | Change DOCX ZIP or CLI behavior | `node/index.js`, `node/docx-document.js`, `node/cli.js` |
14
+ | Change complete-document operations | `document/docx-document.js` → `services/standalone-operation-runner.js` → `services/document-operation-*.js` |
15
+ | Change DOCX ZIP or CLI behavior | `document/zip-archive.js`, `node/index.js`, `node/docx-document.js`, `node/cli.js` |
16
16
  | Choose or add tests | Closest `tests/*.mjs`, then [Testing Guide](docs/TESTING.md) |
17
17
  | Understand ownership/dependencies | [Architecture](ARCHITECTURE.md) |
18
18
 
@@ -21,82 +21,71 @@ specific advanced operation, API, or recovery topic you need.
21
21
 
22
22
  ## Ordinary document edits
23
23
 
24
- For a literal mechanical edit with known old and new text, try one fail-closed
25
- apply call. When global matching is too broad, use a longer, fairly unique
26
- nearby phrase plus a directional range:
27
-
28
- ```bash
29
- docx-redline apply contract.docx --search "distinctive nearby heading or phrase" --context-range 1:3 --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
30
- ```
31
-
32
- Generic or repeated anchors widen the unioned scope and may return
33
- `AMBIGUOUS_TARGET`, costing a recovery turn. On success, treat
34
- `results[i].change.context.anchorMatchCount > 1` as a signal to confirm the
35
- returned location and excerpts.
36
-
37
- For semantic drafting, inspect once and apply once. With a structured wrapper,
38
- use revision-bound handles. For shell-only work, use serializer-backed stdin:
24
+ Use one focused extraction and one apply call. With a structured wrapper, use
25
+ its revision-bound target handles. For shell-only work, use serializer-backed
26
+ stdin and the explicit agent profile:
39
27
 
40
28
  ```bash
41
29
  docx-redline extract contract.docx --search "termination" --around 3
42
30
  node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
43
31
  ```
44
32
 
45
- A single deleted paragraph found in rejected view can be restored verbatim with
46
- `apply --restore --target-id ID`; add one `--find`/`--replace` pair for a small
47
- exact correction. This shortcut does not infer ranges or companion paragraphs.
48
-
49
- For every ordinary text operation, `modified` is the complete desired
50
- accepted-view content. Copy inspected `exactText` verbatim and include
51
- `paragraphId` or `fingerprint`. Independent strong targets are bound against the
52
- batch-start document, so do not manually sort around structural edits.
53
- Consolidate incompatible writes to the same source; use captures for intentional
54
- created-content dependencies.
55
-
56
- Require `completion: true`, `written: true`, a non-null output path, and no
57
- per-operation errors. Localized edits also require
58
- `results[i].change.committed: true`, `finalDisposition: "applied"`, and
59
- `verification.acceptedViewMatchesCompiledText: true`. Follow
33
+ Retain inspected `exactText` for drafting and target with its compact
34
+ `paragraphId` plus `fingerprint`. Use complete `modified` text for a broad
35
+ semantic revision; for a small literal change, use `replacements` so the apply
36
+ payload need not repeat the whole paragraph. Independent strong targets bind to the batch start, so do
37
+ not manually sort around structural edits. Consolidate incompatible writes to
38
+ one source; use captures only for intentional created-content dependencies.
39
+
40
+ If the user requests restoration, search `--view rejected` directly. Otherwise
41
+ follow `selection.hint` if extraction identifies an alternate revision view.
42
+ Restore only the inspected strong target; do not infer adjacent content.
43
+
44
+ Require `completion: true` and a non-null output path. Follow
60
45
  `error.recovery.action` and `retryPlan`; never retry unchanged failed arguments.
61
- Follow `selection.hint` for restorable text. Do not accept/reject another
62
- reviewer's work or remove comments without explicit user authorization. The
63
- source is never overwritten unless `--in-place` is explicit.
46
+ Do not accept/reject another reviewer's work or remove comments without explicit
47
+ user authorization. The source is never overwritten unless `--in-place` is
48
+ explicit.
64
49
 
65
50
  The agent profile preserves progressive execution and the ordinary revision
66
51
  policy. Add `--atomic` or `--existing-revisions slice-cross-author` only when
67
52
  that policy is intended, and confirm the resolved `effectiveOptions`.
68
53
 
69
- Advanced restore, rejected-view insertion, list, table, formatting, comments,
70
- revision policies, and failure examples live in the
54
+ Speculative apply, directional search ranges, advanced restore, rejected-view
55
+ insertion, list, table, formatting, comments, revision policies, and failure
56
+ examples live in the
71
57
  [knowledge base](docs/AGENT_KNOWLEDGE_BASE.md#agent-document-workflow-cli) and
72
58
  [operation schema](docs/schemas/document-operations.schema.json).
73
59
 
74
60
  ## Thin wrappers
75
61
 
76
- Wrap the CLI for shell hosts or `openDocx` from
77
- `@ansonlai/docx-redline-js/node` for byte-oriented Node hosts. A wrapper should
78
- inspect, translate narrow ergonomic inputs into canonical operations, call the
79
- facade once, and return its structured result. Do not reproduce ZIP handling,
80
- targeting, revision allocation, comments, numbering, validation, or rollback.
62
+ Wrap the CLI for shell hosts or `openDocx` from `@ansonlai/docx-redline-js`
63
+ (or `@ansonlai/docx-redline-js/bundle` for zero-dependency sandboxes, or
64
+ `@ansonlai/docx-redline-js/node` for backward-compatible Node hosts) for
65
+ byte-oriented hosts. A wrapper should inspect, translate narrow ergonomic inputs
66
+ into canonical operations, call the facade once, and return its structured result.
67
+ Do not reproduce ZIP handling, targeting, revision allocation, comments, numbering,
68
+ validation, or rollback.
81
69
 
82
70
  The stateful wrapper in `examples/agent-session-wrapper.mjs` is a testable
83
71
  development demonstration only. It is excluded from package files and exports.
84
72
  Production harnesses own their transport and negotiate the minimum CLI
85
- `contractVersion`/capabilities they use. The 0.7.0 localized fast path requires
86
- contract 8 and its localized replacement, speculative apply, and change-summary
87
- capabilities; the narrow restore shortcut additionally requires
88
- `restore-shortcuts-v1`.
73
+ `contractVersion` and capabilities they actually use. Optional capabilities do
74
+ not belong in an ordinary editing prompt merely because the CLI supports them.
89
75
 
90
76
  ## Code map
91
77
 
92
78
  ```text
93
- index.js host-independent public API
94
- core/ OOXML primitives, text views, targeting, validation
79
+ index.js host-independent public API & universal document facade
80
+ adapters/ DOM/XML parser provider with automatic pure-JS fallback
81
+ core/ OOXML primitives, text views, targeting, SHA-256, validation
82
+ document/ universal Uint8Array DOCX facade & fflate ZIP container
95
83
  pipeline/ ingestion, diffing, Markdown, lists, serialization
96
84
  engine/ reconciliation modes and run-level mutation
97
85
  orchestration/ route planning and structural conversion
98
86
  services/ document operations, comments, receipts, artifacts
99
- node/ Node-only ZIP and whole-DOCX facade/CLI
87
+ node/ Node CLI and backward-compatibility re-exports
88
+ dist/ universal ESM and zero-dependency sandbox bundles
100
89
  tests/*.mjs directly runnable suites
101
90
  ```
102
91
 
package/ARCHITECTURE.md CHANGED
@@ -8,11 +8,12 @@ This repository contains only the publishable package surface:
8
8
 
9
9
  - `adapters/`
10
10
  - `core/`
11
+ - `document/` (universal complete-DOCX facade & ZIP archive layer)
11
12
  - `engine/`
12
13
  - `pipeline/`
13
14
  - `services/`
14
15
  - `orchestration/`
15
- - `node/` (separate Node-only package facade)
16
+ - `node/` (Node CLI & backward-compatibility facade)
16
17
  - `index.js`
17
18
  - `index.d.ts`
18
19
  - `dist/`
@@ -22,8 +23,9 @@ No Word add-in entrypoints or host-specific integration layers are part of this
22
23
  ## Goals
23
24
 
24
25
  - Preserve Word-compatible redlines by editing OOXML directly.
25
- - Keep core logic host-independent (no Office.js globals, no Word API calls).
26
- - Reuse the same engine in browser, Node.js, and other JavaScript runtimes.
26
+ - Keep core logic and document-level operations host-independent (no Office.js globals, no Word API calls, no runtime Node built-ins like `node:zlib` or `node:crypto`).
27
+ - Standardize complete-document packages on `Uint8Array` binary payloads, pure-JS SHA-256 revision tokens, and `fflate` ZIP container manipulation.
28
+ - Reuse the same engine across Node.js, browsers, Cloudflare Workers, Deno, Bun, and sandboxed runtimes (e.g. n8n Code nodes).
27
29
  - Keep generated OOXML schema-safe through shared Word element creation and
28
30
  centralized revision metadata helpers.
29
31
 
@@ -37,7 +39,12 @@ No Word add-in entrypoints or host-specific integration layers are part of this
37
39
  │ └── xml-adapter.js
38
40
  ├── core/
39
41
  │ ├── paragraph-text.js
42
+ │ ├── sha256.js
40
43
  │ └── word-xml.js
44
+ ├── document/
45
+ │ ├── docx-document.js
46
+ │ ├── docx-document.d.ts
47
+ │ └── zip-archive.js
41
48
  ├── engine/
42
49
  │ ├── oxml-engine.js
43
50
  │ ├── surgical-mode.js
@@ -63,16 +70,22 @@ No Word add-in entrypoints or host-specific integration layers are part of this
63
70
  │ ├── cli.js
64
71
  │ ├── zip-archive.js
65
72
  │ └── index.js
73
+ ├── dist/
74
+ │ ├── docx-redline-js.esm.js
75
+ │ ├── docx-redline.bundle.js
76
+ │ └── docx-redline.bundle.cjs
66
77
  ├── index.js
67
78
  └── index.d.ts
68
79
  ```
69
80
 
70
81
  ## Entry Points
71
82
 
72
- - `index.js`: primary host-independent exports for OOXML reconciliation.
83
+ - `index.js`: primary host-independent exports for OOXML reconciliation and universal complete-document operations (`openDocx`, `DocxDocument`, `computePackageRevisionToken`).
84
+ - `document/docx-document.js`: universal `Uint8Array`-based document facade and package-level operation runner.
85
+ - `dist/docx-redline.bundle.js` / `dist/docx-redline.bundle.cjs`: zero-dependency standalone sandbox bundles, exported via `@ansonlai/docx-redline-js/bundle`.
73
86
  - `services/standalone-operation-runner.js`: stable complete-document XML
74
87
  operations, exported as `@ansonlai/docx-redline-js/standalone-runner`.
75
- - `node/index.js`: Node-only complete-DOCX buffer facade, exported as
88
+ - `node/index.js`: Node-compatible complete-DOCX facade re-exports, exported as
76
89
  `@ansonlai/docx-redline-js/node`.
77
90
  - `bin/docx-redline.js`: CLI launcher; command implementation lives in
78
91
  `node/cli.js` and shared operation behavior lives in `services/`.
@@ -181,11 +194,18 @@ No Word add-in entrypoints or host-specific integration layers are part of this
181
194
  - `services/document-inspection.js`
182
195
  - Read-only paragraphs/comments inventory with target identity, headings,
183
196
  revision authors, table context, and advisory visible numbering.
184
- - `node/docx-document.js`
185
- - Transactional whole-DOCX editing, artifact wiring, validation, and rollback.
186
- OOXML and package issues are classified as baseline/generated multisets, so
187
- unchanged source defects remain diagnostics while new defects block writes.
188
- This surface is excluded from the browser/root dependency graph.
197
+ - `document/docx-document.js`
198
+ - Universal transactional whole-DOCX editing, artifact wiring, validation, and rollback.
199
+ Standardized on `Uint8Array` binary payloads and pure-JS SHA-256 revision tokens.
200
+ Exposes `openDocx`, `DocxDocument`, and `computePackageRevisionToken` directly across all runtimes.
201
+ - `document/zip-archive.js`
202
+ - Universal, pure-JS ZIP archive reader and writer based on `fflate`. Operates on
203
+ `Uint8Array` byte arrays with zero Node built-ins (`node:zlib`, `Buffer`).
204
+ - `core/sha256.js`
205
+ - Pure-JS synchronous implementation of FIPS 180-4 SHA-256 with zero dependencies,
206
+ enabling deterministic revision tokens in sandboxed runtimes without `node:crypto`.
207
+ - `node/docx-document.js` and `node/zip-archive.js`
208
+ - Node-compatible re-exports maintaining `Buffer` compatibility for `@ansonlai/docx-redline-js/node`.
189
209
  - `node/cli.js` and `bin/docx-redline.js`
190
210
  - Cross-platform, JSON-only agent command boundary. Read commands never
191
211
  mutate; write commands require attribution, use package transactions, and
package/CHANGELOG.md CHANGED
@@ -1,13 +1,73 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
4
-
3
+ ## 0.8.0
4
+
5
+ ### Universal Document Facade & Zero-Node Architecture
6
+
7
+ - **Universal `DocxDocument` & `openDocx`:** Implemented complete-DOCX lifecycle operations
8
+ (`openDocx`, `doc.inspect()`, `doc.applyOperations()`, `doc.resolveRevisions()`, `doc.deleteComments()`,
9
+ `doc.toUint8Array()`, `doc.getRevisionToken()`) on standard `Uint8Array` in `document/docx-document.js`,
10
+ re-exported directly from `index.js`. Enables complete-document redline workflows in any JavaScript
11
+ runtime without Node.js dependencies.
12
+ - **Pure-JS ZIP container (`fflate`):** Replaced `node:zlib` with lightweight, zero-dependency `fflate`
13
+ in `document/zip-archive.js`, providing cross-runtime raw DEFLATE / INFLATE compression and decompression
14
+ with full OOXML archive compatibility.
15
+ - **Pure-JS FIPS 180-4 SHA-256:** Created zero-dependency, synchronous SHA-256 implementation in
16
+ `core/sha256.js`, removing `node:crypto` from package revision token calculations while retaining
17
+ exact deterministic hash parity with standard NIST vectors.
18
+ - **Transparent XML DOM fallback:** Added `resolveDomParserConstructor()` and
19
+ `resolveXmlSerializerConstructor()` in `adapters/xml-adapter.js`, automatically utilizing native
20
+ `globalThis.DOMParser`/`globalThis.XMLSerializer` when present and falling back transparently to
21
+ `@xmldom/xmldom` in headless, server, edge, and sandbox environments without requiring manual provider setup.
22
+ - **Zero-dependency sandbox bundles:** Configured `scripts/build.mjs` with `esbuild` (`platform: 'neutral'`)
23
+ to produce single-file, zero-dependency standalone bundles in both ESM (`dist/docx-redline.bundle.js`)
24
+ and CommonJS (`dist/docx-redline.bundle.cjs`), exported as `@ansonlai/docx-redline-js/bundle` for direct
25
+ use in workflow engines (e.g., n8n Code nodes), Cloudflare Workers, AWS Lambda, Deno, and QuickJS sandboxes.
26
+ - **Strict runtime boundary enforcement:** Removed `node:zlib` and `node:crypto` from allowed external
27
+ imports in `tests/no_word_api_index_check.mjs`. Automated sandbox isolation tests
28
+ (`tests/sandbox_bundle_isolation_tests.mjs`) verify zero Node built-in imports and full document
29
+ reconciliation in restricted VM contexts without `process`, `Buffer`, or `require`.
30
+ - **Backward compatibility:** Preserved full API and `Buffer` compatibility for `@ansonlai/docx-redline-js/node`
31
+ consumers and `doc.toBuffer()`.
32
+
33
+ ## 0.7.2
34
+
35
+ ### Agent Surface Simplification
36
+
37
+ - **Extraction-first golden path:** Re-established the single deterministic two-turn
38
+ "extract-then-apply" sequence as the default agent workflow across `AGENTS.md`,
39
+ `docs/AGENT_FAST_START.md`, and `docs/SKILL_AUTHORING.md`. Eliminates the
40
+ upfront "choice tax" and model deliberation overhead.
41
+ - **Localized replacements after inspection:** Decoupled localized patching from
42
+ speculation. Agents inspect the target paragraph first, then supply compact
43
+ `replacements` instructions, achieving 60.6%–90.8% payload reductions without
44
+ speculative ambiguity risk.
45
+ - **Computed success gate:** Consolidated standard success verification onto
46
+ `completion: true` and non-null `outputPath`, removing multi-field verification
47
+ checklists from primary prompts while retaining full `change` audit evidence.
48
+ - **Advanced features relocated:** Speculative 1-turn apply, directional context
49
+ windows (`--context-range`), and anchor diagnostics are relocated to
50
+ `docs/AGENT_KNOWLEDGE_BASE.md` and CLI help as opt-in harness policies.
51
+ - **Observed-state restoration:** Restructured restoration as a clean request-
52
+ or hint-driven branch (`--view rejected` or `selection.hint`) rather than an
53
+ upfront classification decision.
54
+ - **Empirical surface evaluation:** Added synthetic replay benchmark
55
+ (`npm run benchmark:agent-surface`) and validation report documenting 100%
56
+ first-attempt task correctness and payload savings across three workflow policies.
57
+
58
+ ## 0.7.1
59
+
60
+ ### Reliability and Discovery Refinements
61
+
62
+ - Localized replacement matching now tries exact case-sensitive text first,
63
+ then a narrow ASCII-space/non-breaking-space equivalent match. Ambiguous
64
+ equivalent matches still fail closed, and unchanged boundary NBSPs are
65
+ retained to avoid incidental whitespace redlines.
5
66
  - Added a narrow single-paragraph restore shortcut. A restore with a strong
6
67
  rejected-view target may omit `modified` for verbatim restoration or provide
7
68
  exact `replacements`; the CLI exposes the same path through `apply --restore
8
69
  --target-id ID`. Ranges, captures, weak targets, and inferred companion
9
70
  content remain excluded.
10
-
11
71
  - Fixed rejected-view restore receipts so `resolvedTarget` text, fingerprint,
12
72
  revision view, and match diagnostics describe the view actually used to
13
73
  resolve the target.
package/README.md CHANGED
@@ -16,18 +16,16 @@ Converts AI-generated or programmatic text/markdown edits into valid Office Open
16
16
  - Highlights: apply highlight colors to runs
17
17
  - Markdown and OOXML conversion in both directions
18
18
  - Status/error result fields for parse, targeting, and existing-revision failures
19
- - Localized exact replacements and fail-closed one-turn CLI edits with verified before/after evidence
19
+ - Localized exact replacements against inspected targets, plus optional fail-closed speculative CLI edits
20
20
  - Package plumbing helpers for numbering.xml, comments.xml, content types, and relationships
21
21
  - Zero host dependencies: works in Node.js, browsers, Deno, and similar JS runtimes with DOM parsing support
22
22
 
23
23
  ## What's New in 0.7.0
24
24
 
25
- Version 0.7.0 adds a contract-8 fast path for exact mechanical edits. Agents can
26
- send a small `find`/`replace` request instead of reproducing a complete legal
27
- paragraph, and may omit a strong target when the source literal resolves to one
28
- accepted-view paragraph. A longer, fairly unique nearby phrase can narrow the
29
- attempt with a signed directional window. Generic or repeated anchors widen the
30
- unioned scope and may cause a safe ambiguity failure and another tool turn.
25
+ Version 0.7.0 adds contract-8 localized edits. Agents can send a small
26
+ `find`/`replace` request against an inspected target instead of reproducing a
27
+ complete paragraph. An optional speculative form may omit the strong target
28
+ when the source literal resolves uniquely; advanced scoping is described below.
31
29
 
32
30
  Speculative edits fail without writing when the anchor, paragraph, or source
33
31
  span is missing or ambiguous. Successful localized results include a committed
@@ -249,18 +247,10 @@ latency. Checked comparative results are in the
249
247
  ### Agent CLI
250
248
 
251
249
  ```bash
252
- # One-turn exact mechanical edit; fails closed unless one paragraph matches
253
- docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
254
-
255
- # Directional context using a longer phrase with a higher chance of uniqueness
256
- docx-redline apply contract.docx --search "distinctive nearby heading or phrase" --context-range 1:3 --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
257
-
258
- # Semantic drafting still starts with focused context
250
+ # Default agent workflow: inspect once, then apply once
259
251
  docx-redline extract contract.docx --search "termination" --around 3
260
- docx-redline preflight contract.docx --operations operations.json --author "Editor"
261
- docx-redline apply contract.docx --operations operations.json --author "Editor" --output reviewed.docx
262
- docx-redline validate reviewed.docx
263
- ```
252
+ node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
253
+ ```
264
254
 
265
255
  ```bash
266
256
  # Inline one-liner edit (no operations file needed)
@@ -275,9 +265,9 @@ docx-redline apply contract.docx --target "Another author's clause" --modified "
275
265
  # High-assurance atomic batch with nonzero exit on any incomplete result
276
266
  docx-redline apply contract.docx --operations operations.json --atomic --require-complete --output reviewed.docx
277
267
 
278
- # Agent shell path: JSON is emitted by a serializer, not interpolated by the shell
279
- node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
280
- ```
268
+ # Optional read-only audit before a deliberately staged batch
269
+ docx-redline preflight contract.docx --operations operations.json --author "Editor"
270
+ ```
281
271
 
282
272
  All commands emit JSON on stdout. `apply` defaults:
283
273
  - **Author**: Defaults to `'AI Redliner'` (or `DOCX_REDLINE_AUTHOR` environment variable).
@@ -290,7 +280,8 @@ All commands emit JSON on stdout. `apply` defaults:
290
280
  - **Compact stdout**: `--compact` emits one-line mutation JSON. It changes serialization only, not operation semantics.
291
281
  - **Tracked changes**: Defaults to `generateRedlines: true`. Pass `--no-redlines` when clean direct text edits are desired.
292
282
  - **Inline edits**: Use `--target <text>` with `--modified <text>` or `--comment <text>` for quick one-liners without creating a JSON file.
293
- - **Localized edits**: Use `--find`/`--replace` with a known strong target, or omit the target for fail-closed global resolution. Add a longer, fairly unique `--search` phrase with `--context-range 1:3` for directional scope; `--around 3` is symmetric. Generic or repeated anchors can widen the unioned scope until multiple paragraphs contain `find`, producing a safe `AMBIGUOUS_TARGET` failure and an extra recovery turn. On success, `results[i].change.context.anchorMatchCount > 1` signals a non-unique anchor; confirm the returned location and bounded before/after evidence.
283
+ - **Localized edits**: After extraction, use `replacements` with the inspected `paragraphId` plus `fingerprint` to avoid restating a long paragraph. Retain `exactText` locally for drafting. Targetless `--find`/`--replace` is an optional speculative path described below.
284
+ - **Invisible spaces**: Localized matching is case-sensitive and tries exact text first. If no exact span exists, ordinary spaces may match DOCX non-breaking spaces; ambiguous equivalent matches still fail closed, and `change.replacements[i].matchMode` reports `space_equivalent` when used.
294
285
  - **Compact mutation results**: `apply`, `accept`, `reject`, and `delete-comments` omit full OOXML/package payloads and inspection text from stdout. CLI operation results omit duplicate nested receipt bodies; the ordered top-level `receipts` array is authoritative. Successful exact target-match diagnostics are omitted and equivalent-whitespace matches retain only mode/count. Node and standalone-runner results remain unchanged. Use `validate` when full issue arrays are needed.
295
286
 
296
287
  Inspection commands default to 20 direct matches and a 48 KiB soft response
@@ -312,10 +303,31 @@ user-facing Word locations; use `humanReference`, `provision`, or
312
303
  `agent-safety-profile-v2`, `deduplicated-cli-receipts`, and
313
304
  `compact-cli-json-v1`, `localized-replacements-v1`,
314
305
  `speculative-search-apply-v1`, `localized-change-summary-v1`, and
315
- `restore-shortcuts-v1` capabilities.
306
+ `restore-shortcuts-v1`, plus `localized-space-equivalence-v1` capabilities.
316
307
  Wrappers should negotiate only the
317
308
  capabilities they use. Run `docx-redline <command> --help` for that command's
318
309
  machine-readable options, behavior, exit codes, canonical GitHub documentation links, and compact examples.
310
+
311
+ #### Optional speculative CLI path
312
+
313
+ Harnesses with a measured confidence policy may attempt a targetless localized
314
+ edit. This is not the default agent workflow:
315
+
316
+ ```bash
317
+ # Globally unique literal
318
+ docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
319
+
320
+ # Optional directional scope using a longer, fairly unique nearby phrase
321
+ docx-redline apply contract.docx --search "distinctive nearby heading or phrase" --context-range 1:3 --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
322
+ ```
323
+
324
+ Generic or repeated anchors can widen the unioned scope until multiple
325
+ paragraphs contain `find`, causing a safe `AMBIGUOUS_TARGET` failure and a
326
+ recovery turn. `--around 3` is symmetric. On success,
327
+ `change.context.anchorMatchCount > 1` signals a repeated anchor; confirm the
328
+ returned location and bounded evidence. Matching remains case-sensitive and
329
+ exact-first, with a narrow ordinary-space/NBSP fallback that still refuses
330
+ ambiguity.
319
331
 
320
332
  See the [compact agent fast start](./docs/AGENT_FAST_START.md), the
321
333
  [skill/harness authoring contract](./docs/SKILL_AUTHORING.md), and the
@@ -1,64 +1,92 @@
1
- /**
2
- * XML adapter for parser/serializer portability.
3
- *
4
- * Default behavior uses browser-provided DOMParser/XMLSerializer.
5
- * Consumers can override these constructors for non-browser runtimes.
1
+ /**
2
+ * XML adapter for parser/serializer portability.
3
+ *
4
+ * Transparently uses browser-provided DOMParser/XMLSerializer when available in
5
+ * standard web runtimes, automatically falling back to pure-JS @xmldom/xmldom in
6
+ * Node.js and sandbox environments. Consumers can also explicitly override these
7
+ * constructors via configureXmlProvider.
6
8
  */
7
9
 
10
+ import { DOMParser as XmlDomParser, XMLSerializer as XmlDomSerializer } from '@xmldom/xmldom';
8
11
  import { warn as logWarning, error as logError } from './logger.js';
9
12
 
10
- let _DOMParser = globalThis.DOMParser;
11
- let _XMLSerializer = globalThis.XMLSerializer;
12
-
13
- /**
14
- * Configures XML provider constructors.
15
- *
16
- * @param {Object} [options={}] - Provider overrides
17
- * @param {typeof DOMParser} [options.DOMParser] - DOMParser constructor
18
- * @param {typeof XMLSerializer} [options.XMLSerializer] - XMLSerializer constructor
19
- */
20
- export function configureXmlProvider(options = {}) {
21
- if (options.DOMParser) _DOMParser = options.DOMParser;
22
- if (options.XMLSerializer) _XMLSerializer = options.XMLSerializer;
23
- }
24
-
25
- /**
26
- * Creates a parser instance.
27
- *
28
- * @returns {DOMParser}
29
- */
13
+ let _DOMParser = null;
14
+ let _XMLSerializer = null;
15
+
16
+ /**
17
+ * Configures XML provider constructors.
18
+ *
19
+ * @param {Object} [options={}] - Provider overrides
20
+ * @param {typeof DOMParser} [options.DOMParser] - DOMParser constructor
21
+ * @param {typeof XMLSerializer} [options.XMLSerializer] - XMLSerializer constructor
22
+ */
23
+ export function configureXmlProvider(options = {}) {
24
+ if ('DOMParser' in options) _DOMParser = options.DOMParser;
25
+ if ('XMLSerializer' in options) _XMLSerializer = options.XMLSerializer;
26
+ }
27
+
28
+ /**
29
+ * Resolves the active DOMParser constructor.
30
+ *
31
+ * Priority order:
32
+ * 1. Explicitly configured constructor (via configureXmlProvider)
33
+ * 2. Host environment native DOMParser (globalThis.DOMParser)
34
+ * 3. Pure-JS @xmldom/xmldom fallback
35
+ *
36
+ * @returns {typeof DOMParser}
37
+ */
38
+ export function resolveDomParserConstructor() {
39
+ return _DOMParser || globalThis.DOMParser || XmlDomParser;
40
+ }
41
+
42
+ /**
43
+ * Resolves the active XMLSerializer constructor.
44
+ *
45
+ * Priority order:
46
+ * 1. Explicitly configured constructor (via configureXmlProvider)
47
+ * 2. Host environment native XMLSerializer (globalThis.XMLSerializer)
48
+ * 3. Pure-JS @xmldom/xmldom fallback
49
+ *
50
+ * @returns {typeof XMLSerializer}
51
+ */
52
+ export function resolveXmlSerializerConstructor() {
53
+ return _XMLSerializer || globalThis.XMLSerializer || XmlDomSerializer;
54
+ }
55
+
56
+ /**
57
+ * Creates a parser instance.
58
+ *
59
+ * @param {Object} [options={}] - Parser options (e.g. { onError } for xmldom)
60
+ * @returns {DOMParser}
61
+ */
30
62
  export function createParser(options = {}) {
31
- if (!_DOMParser && globalThis.DOMParser) {
32
- _DOMParser = globalThis.DOMParser;
33
- }
34
- if (!_DOMParser) {
35
- throw new Error('DOMParser is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.');
36
- }
37
- return new _DOMParser(options);
38
- }
39
-
40
- /**
41
- * Creates a serializer instance.
42
- *
43
- * @returns {XMLSerializer}
44
- */
45
- export function createSerializer() {
46
- if (!_XMLSerializer && globalThis.XMLSerializer) {
47
- _XMLSerializer = globalThis.XMLSerializer;
48
- }
49
- if (!_XMLSerializer) {
50
- throw new Error('XMLSerializer is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.');
51
- }
52
- return new _XMLSerializer();
53
- }
54
-
55
- /**
56
- * Parses XML text into a DOM document.
57
- *
58
- * @param {string} xmlString - XML string
59
- * @param {string} [contentType='text/xml'] - MIME type
60
- * @returns {Document}
61
- */
63
+ const ParserCtor = resolveDomParserConstructor();
64
+ if (!ParserCtor) {
65
+ throw new Error('DOMParser is not configured and no fallback XML parser is available.');
66
+ }
67
+ return new ParserCtor(options);
68
+ }
69
+
70
+ /**
71
+ * Creates a serializer instance.
72
+ *
73
+ * @returns {XMLSerializer}
74
+ */
75
+ export function createSerializer() {
76
+ const SerializerCtor = resolveXmlSerializerConstructor();
77
+ if (!SerializerCtor) {
78
+ throw new Error('XMLSerializer is not configured and no fallback XML serializer is available.');
79
+ }
80
+ return new SerializerCtor();
81
+ }
82
+
83
+ /**
84
+ * Parses XML text into a DOM document.
85
+ *
86
+ * @param {string} xmlString - XML string
87
+ * @param {string} [contentType='text/xml'] - MIME type
88
+ * @returns {Document}
89
+ */
62
90
  export function parseXml(xmlString, contentType = 'text/xml') {
63
91
  const result = parseOoxmlSafe(xmlString, contentType);
64
92
  if (result.error) {