@ansonlai/docx-redline-js 0.7.1 → 0.7.2
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 +23 -39
- package/CHANGELOG.md +5 -0
- package/README.md +35 -23
- package/dist/docx-redline-js.esm.js +1 -1
- package/docs/AGENT_FAST_START.md +31 -34
- package/docs/AGENT_KNOWLEDGE_BASE.md +60 -58
- package/docs/SKILL_AUTHORING.md +82 -101
- package/docs/validation-reports/2026-09-14-agent-surface-simplification.md +75 -0
- package/node/cli-help.js +1 -1
- package/node/cli.js +12 -4
- package/package.json +4 -1
- package/services/localized-replacement-compiler.js +73 -7
- package/services/standalone-operation-runner.d.ts +1 -0
package/AGENTS.md
CHANGED
|
@@ -21,53 +21,39 @@ specific advanced operation, API, or recovery topic you need.
|
|
|
21
21
|
|
|
22
22
|
## Ordinary document edits
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Require `completion: true
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
70
|
-
revision policies, and failure
|
|
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
|
|
|
@@ -82,10 +68,8 @@ targeting, revision allocation, comments, numbering, validation, or rollback.
|
|
|
82
68
|
The stateful wrapper in `examples/agent-session-wrapper.mjs` is a testable
|
|
83
69
|
development demonstration only. It is excluded from package files and exports.
|
|
84
70
|
Production harnesses own their transport and negotiate the minimum CLI
|
|
85
|
-
`contractVersion
|
|
86
|
-
|
|
87
|
-
capabilities; the narrow restore shortcut additionally requires
|
|
88
|
-
`restore-shortcuts-v1`.
|
|
71
|
+
`contractVersion` and capabilities they actually use. Optional capabilities do
|
|
72
|
+
not belong in an ordinary editing prompt merely because the CLI supports them.
|
|
89
73
|
|
|
90
74
|
## Code map
|
|
91
75
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Localized replacement matching now tries exact case-sensitive text first,
|
|
6
|
+
then a narrow ASCII-space/non-breaking-space equivalent match. Ambiguous
|
|
7
|
+
equivalent matches still fail closed, and unchanged boundary NBSPs are
|
|
8
|
+
retained to avoid incidental whitespace redlines.
|
|
9
|
+
|
|
5
10
|
- Added a narrow single-paragraph restore shortcut. A restore with a strong
|
|
6
11
|
rejected-view target may omit `modified` for verbatim restoration or provide
|
|
7
12
|
exact `replacements`; the CLI exposes the same path through `apply --restore
|
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
|
|
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
|
|
26
|
-
|
|
27
|
-
paragraph
|
|
28
|
-
|
|
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
|
-
#
|
|
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
|
|
261
|
-
|
|
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
|
-
#
|
|
279
|
-
|
|
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**:
|
|
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,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.7.
|
|
1
|
+
// @ansonlai/docx-redline-js v0.7.2 — https://github.com/AnsonLai/docx-redline-js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
package/docs/AGENT_FAST_START.md
CHANGED
|
@@ -1,58 +1,55 @@
|
|
|
1
1
|
# Agent Fast Start
|
|
2
2
|
|
|
3
|
-
Use this page for ordinary `.docx` edits. For
|
|
3
|
+
Use this page for ordinary `.docx` edits. For unusual Word structures or optional shortcuts, follow the links at the end.
|
|
4
4
|
|
|
5
5
|
## Structured agent tool
|
|
6
6
|
|
|
7
|
-
If
|
|
7
|
+
If the host provides a document-session wrapper:
|
|
8
8
|
|
|
9
|
-
1. Inspect
|
|
10
|
-
2. Apply with the revision-bound handle
|
|
11
|
-
3.
|
|
9
|
+
1. Inspect once with enough surrounding context to draft the change.
|
|
10
|
+
2. Apply once with the returned revision-bound handle.
|
|
11
|
+
3. Accept only the wrapper's complete-success result; refresh failed handles.
|
|
12
12
|
|
|
13
|
-
The repository's `examples/agent-session-wrapper.mjs`
|
|
14
|
-
It is a development sample, not a package API.
|
|
13
|
+
The repository's `examples/agent-session-wrapper.mjs` is a development sample, not a package API.
|
|
15
14
|
|
|
16
|
-
## CLI
|
|
15
|
+
## CLI workflow
|
|
17
16
|
|
|
18
|
-
1.
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The source must occur in one eligible paragraph. To narrow it, use a longer, fairly unique phrase: `--search "distinctive nearby heading or phrase" --context-range 1:3`.
|
|
25
|
-
Generic/repeated anchors can cause `AMBIGUOUS_TARGET` and waste a recovery turn. If `anchorMatchCount > 1`, confirm the returned location and excerpts. `--around 3` is symmetric.
|
|
26
|
-
|
|
27
|
-
2. For semantic drafting such as “make this provision mutual,” extract enough
|
|
28
|
-
context, then copy `exactText` with `paragraphId` or `fingerprint`:
|
|
17
|
+
1. Extract the relevant clause or range:
|
|
29
18
|
|
|
30
19
|
```bash
|
|
31
20
|
docx-redline extract contract.docx --search "termination" --around 3
|
|
32
21
|
```
|
|
33
22
|
|
|
34
|
-
Search is case-insensitive
|
|
23
|
+
Search is case-insensitive. Follow `selection.nextAfter` when truncated. Preserve `selection`, `humanReference`, target descriptors, and revision cues when reshaping output. Report `humanReference`, not a machine paragraph number.
|
|
24
|
+
|
|
25
|
+
2. Retain inspected `exactText` for drafting and target with its `paragraphId` plus `fingerprint`, then apply once with a UTF-8 operations file or serializer-backed stdin:
|
|
35
26
|
|
|
36
27
|
```bash
|
|
37
28
|
node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
|
|
38
29
|
```
|
|
39
30
|
|
|
40
|
-
Use `
|
|
31
|
+
Use complete `modified` text for a broad semantic revision. For a small literal change in that inspected target, use
|
|
32
|
+
`replacements: [{ "find": "old text", "replace": "new text" }]` instead.
|
|
33
|
+
Use `JSON.stringify`; never interpolate document text through shell quoting. Preserve intentionally relative wording unless the user asks for a concrete value.
|
|
34
|
+
|
|
35
|
+
## Restore branch
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
If the user requests deleted text, search with `--view rejected` first. Otherwise follow `selection.hint` only when it identifies an alternate revision view. Restore the inspected strong target; do not infer adjacent content. Run `docx-redline apply --help` for canonical restore shapes.
|
|
43
38
|
|
|
44
|
-
##
|
|
39
|
+
## Completion and recovery
|
|
45
40
|
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
|
|
41
|
+
- Require `completion: true` and a non-null `outputPath`; the CLI computes this from write, operation, commit, and localized-verification results.
|
|
42
|
+
- On failure, follow `error.recovery.action`; never retry unchanged arguments.
|
|
43
|
+
- Use `retryPlan.base: "original"` to replay a batch and `"output"` to retain
|
|
44
|
+
committed progressive work and retry the reported indexes.
|
|
45
|
+
- The source is not overwritten unless `--in-place` is explicit.
|
|
46
|
+
- Never accept/reject revisions or remove comments without authorization.
|
|
47
|
+
- Add `--atomic` or a non-default revision policy only when deliberately needed;
|
|
48
|
+
confirm `effectiveOptions`.
|
|
49
|
+
- Strong inspected targets bind to batch start. Consolidate writes to one source
|
|
50
|
+
and use captures only for intentional created-content dependencies.
|
|
56
51
|
|
|
57
|
-
Advanced
|
|
52
|
+
Advanced speculative apply, directional scoping, operation constraints, and
|
|
53
|
+
recovery: [Agent Knowledge Base](AGENT_KNOWLEDGE_BASE.md). Canonical schema:
|
|
54
|
+
[document-operations.schema.json](schemas/document-operations.schema.json).
|
|
58
55
|
Wrapper design: [README](../README.md#example-agent-session-wrapper-development-only)
|
|
@@ -476,66 +476,41 @@ The canonical machine-readable contract is
|
|
|
476
476
|
`docs/schemas/document-operations.schema.json`. Read that schema or the examples
|
|
477
477
|
here instead of grepping bundled implementation code.
|
|
478
478
|
|
|
479
|
-
#### Standard Workflow (
|
|
480
|
-
|
|
481
|
-
Use this for
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
1.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
per-operation error. For localized patches also require
|
|
504
|
-
`results[i].change.committed: true`, `finalDisposition: "applied"`, and
|
|
505
|
-
positive accepted-view verification. If `anchorMatchCount > 1`, confirm the
|
|
506
|
-
returned location and excerpts because the anchor was repeated.
|
|
507
|
-
6. Run a focused `extract` on changed clauses only when placement or list/table
|
|
508
|
-
structure needs confirmation.
|
|
509
|
-
|
|
510
|
-
Do not probe operation behavior with disposable apply commands or read a vendor
|
|
511
|
-
bundle before this route. If `apply` returns an error, use the recovery matrix
|
|
512
|
-
below and make one cause-specific correction.
|
|
513
|
-
|
|
514
|
-
```bash
|
|
515
|
-
# 1. Globally unique mechanical edit in one call
|
|
516
|
-
docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
|
|
479
|
+
#### Standard Workflow (Extract Then Apply)
|
|
480
|
+
|
|
481
|
+
Use this for ordinary edits. `apply` validates the resulting package and
|
|
482
|
+
revision markup internally before writing, so do not add `preflight` or baseline
|
|
483
|
+
`validate` merely for reassurance.
|
|
484
|
+
|
|
485
|
+
1. Run one focused `extract`. Add `--around 3` when surrounding context is
|
|
486
|
+
needed. Retain `exactText` for drafting and target with `paragraphId` plus
|
|
487
|
+
`fingerprint`.
|
|
488
|
+
2. Build one final operation per target. Use complete `modified` text for broad
|
|
489
|
+
semantic revisions or localized `replacements` for small literal changes in
|
|
490
|
+
an inspected target. Consolidate multiple changes to one paragraph.
|
|
491
|
+
3. Run `apply` once per stable batch. Independent strong targets bind against
|
|
492
|
+
the batch-start document and do not need bottom-up sorting. Use captures only
|
|
493
|
+
for intentional created-content dependencies.
|
|
494
|
+
4. Require `completion: true` and a non-null `outputPath`. The CLI derives
|
|
495
|
+
completion from write status, operation outcomes, commit disposition, and
|
|
496
|
+
localized accepted-view verification.
|
|
497
|
+
5. Run a focused post-apply extraction only when placement or structural output
|
|
498
|
+
needs confirmation.
|
|
499
|
+
|
|
500
|
+
If `apply` returns an error, follow the recovery envelope below and make one
|
|
501
|
+
cause-specific correction. Do not probe behavior with disposable mutations or
|
|
502
|
+
read a vendor bundle.
|
|
517
503
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
# 3. Focused contextual discovery for semantic drafting
|
|
504
|
+
```bash
|
|
505
|
+
# 1. Focused contextual discovery
|
|
522
506
|
docx-redline extract contract.docx --search "termination" --around 3
|
|
523
507
|
|
|
524
|
-
#
|
|
525
|
-
docx-redline apply contract.docx --target "Original clause" --modified "New clause" --output reviewed.docx
|
|
526
|
-
|
|
527
|
-
# 5. Direct edit without tracked changes (clean text, no revision clutter)
|
|
528
|
-
docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
|
|
529
|
-
|
|
530
|
-
# 6. Cross-author edit inside another reviewer's pending insertion
|
|
531
|
-
docx-redline apply contract.docx --target "Pending clause text" --modified "Updated clause text" --existing-revisions slice-cross-author --output reviewed.docx
|
|
532
|
-
|
|
533
|
-
# 7. Batch operations with ops.json
|
|
534
|
-
docx-redline apply contract.docx --operations operations.json --output reviewed.docx
|
|
535
|
-
|
|
536
|
-
# 8. Serializer-backed stdin with compact stdout
|
|
508
|
+
# 2. Serializer-backed apply against the inspected target
|
|
537
509
|
node emit-operations.mjs | docx-redline apply contract.docx --operations - --profile agent --compact --output reviewed.docx
|
|
538
|
-
|
|
510
|
+
|
|
511
|
+
# Optional clean edit without tracked changes
|
|
512
|
+
docx-redline apply contract.docx --target "Typo fix" --modified "Fixed typo" --no-redlines --output clean.docx
|
|
513
|
+
```
|
|
539
514
|
|
|
540
515
|
Key CLI defaults and behaviors:
|
|
541
516
|
- **Author**: Automatically defaults to `'AI Redliner'` (overridable via `--author` or `DOCX_REDLINE_AUTHOR` environment variable).
|
|
@@ -547,9 +522,36 @@ Key CLI defaults and behaviors:
|
|
|
547
522
|
- **Agent profile**: `--profile agent` enables complete-success exit behavior but preserves progressive execution and the ordinary revision policy. Compose it with `--atomic` or an explicit `--existing-revisions` choice when intended; resolved values appear in `effectiveOptions`.
|
|
548
523
|
- **Operation transport**: A UTF-8 operations file and serializer-backed `--operations -` are peers. Use whichever the host can construct without interpolating legal text in the shell.
|
|
549
524
|
- **Compact mutation JSON**: `apply`, `accept`, `reject`, and `delete-comments` omit document/package XML, full validation arrays, and duplicate nested receipts. Top-level `receipts` is authoritative. Pass `--compact` for one-line JSON; run `validate` for full issue records.
|
|
550
|
-
- Check `completion: true
|
|
525
|
+
- Check `completion: true` and a non-null `outputPath` on stdout. Failed,
|
|
526
|
+
partial, rolled-back, unverified, and unwritten work cannot appear complete.
|
|
527
|
+
If an error occurs, follow `error.recovery.action` before correcting the cause.
|
|
551
528
|
|
|
552
|
-
For multi-clause or multi-page reviews,
|
|
529
|
+
For multi-clause or multi-page reviews, use focused batches rather than one
|
|
530
|
+
massive batch. This keeps context compact and simplifies error diagnosis.
|
|
531
|
+
|
|
532
|
+
#### Optional Speculative Localized Apply
|
|
533
|
+
|
|
534
|
+
Targetless localized apply remains available to expert users and harnesses with
|
|
535
|
+
a measured confidence policy. It is not the ordinary agent workflow:
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
# Globally unique literal
|
|
539
|
+
docx-redline apply contract.docx --find "thirty (30) days" --replace "sixty (60) days" --profile agent --output reviewed.docx
|
|
540
|
+
|
|
541
|
+
# Optional directional scope using a longer, fairly unique phrase
|
|
542
|
+
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
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Omitting a target asks the CLI to resolve one eligible paragraph. Generic or
|
|
546
|
+
repeated anchors union their windows and may produce `AMBIGUOUS_TARGET`, costing
|
|
547
|
+
a recovery turn. `--around 3` is symmetric; `--occurrence` disambiguates only
|
|
548
|
+
inside an already selected paragraph. `anchorMatchCount > 1` signals a repeated
|
|
549
|
+
anchor even after a successful edit. Matching is exact and case-sensitive first,
|
|
550
|
+
then permits only ASCII-space/NBSP equivalence; ambiguity still fails closed.
|
|
551
|
+
|
|
552
|
+
Localized `change` evidence retains bounded before/after excerpts, commit
|
|
553
|
+
disposition, and accepted-view verification for audit consumers. Ordinary
|
|
554
|
+
agents rely on the computed top-level `completion` signal.
|
|
553
555
|
|
|
554
556
|
#### High-Assurance / Staged Verification Workflow (Optional)
|
|
555
557
|
|
package/docs/SKILL_AUTHORING.md
CHANGED
|
@@ -1,86 +1,76 @@
|
|
|
1
1
|
# Skill and Harness Authoring Contract
|
|
2
2
|
|
|
3
|
-
Use this page
|
|
4
|
-
|
|
5
|
-
|
|
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
6
|
|
|
7
7
|
> [!WARNING]
|
|
8
|
-
> Before 1.0, every
|
|
9
|
-
>
|
|
10
|
-
>
|
|
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
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Example declaration for a contract-8 shell integration that uses localized
|
|
13
|
+
operations over stdin:
|
|
14
14
|
|
|
15
15
|
```json
|
|
16
16
|
{
|
|
17
|
-
"testedLibraryRelease": "0.7.
|
|
17
|
+
"testedLibraryRelease": "0.7.1",
|
|
18
18
|
"requiredCliContractVersion": 8,
|
|
19
19
|
"requiredCapabilities": [
|
|
20
|
-
"
|
|
20
|
+
"operations-stdin",
|
|
21
|
+
"agent-safety-profile-v2",
|
|
21
22
|
"recovery-envelope-v1",
|
|
22
|
-
"localized-replacements-v1"
|
|
23
|
-
"speculative-search-apply-v1",
|
|
24
|
-
"localized-change-summary-v1"
|
|
23
|
+
"localized-replacements-v1"
|
|
25
24
|
]
|
|
26
25
|
}
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
The package pin anchors the release; runtime negotiation proves the executable
|
|
30
|
-
exposes the required contract. A new release cannot inherit an old claim.
|
|
31
|
-
|
|
32
28
|
## Runtime negotiation
|
|
33
29
|
|
|
34
|
-
Run
|
|
35
|
-
the
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
docx-redline version
|
|
39
|
-
```
|
|
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.
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`recovery-envelope-v1`. Require other capabilities only when used. Fail closed
|
|
45
|
-
when one is absent; never inspect bundles or ZIP parts as a fallback.
|
|
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.
|
|
46
38
|
|
|
47
39
|
## Safety invariants
|
|
48
40
|
|
|
49
|
-
Every generated integration must
|
|
41
|
+
Every generated integration must:
|
|
50
42
|
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
60
52
|
|
|
61
|
-
The `
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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.
|
|
65
57
|
|
|
66
58
|
## Workflow policy choices
|
|
67
59
|
|
|
68
|
-
|
|
60
|
+
Keep these exceptions separate from the default workflow:
|
|
69
61
|
|
|
70
|
-
| Choice |
|
|
62
|
+
| Choice | Default | Override only when |
|
|
71
63
|
|---|---|---|
|
|
72
|
-
| Transaction | progressive
|
|
73
|
-
| Existing revisions | `merge-same-author`
|
|
74
|
-
| Reviewer identity |
|
|
75
|
-
| Output
|
|
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. |
|
|
76
68
|
|
|
77
|
-
Check `effectiveOptions`
|
|
78
|
-
revision, author, or redline policy.
|
|
69
|
+
Check `effectiveOptions` when an integration deliberately sets a policy.
|
|
79
70
|
|
|
80
71
|
## Transport choices
|
|
81
72
|
|
|
82
|
-
A UTF-8 operations file and serializer-backed stdin are peer transports
|
|
83
|
-
the form the host can construct safely:
|
|
73
|
+
A UTF-8 operations file and serializer-backed stdin are peer transports:
|
|
84
74
|
|
|
85
75
|
```bash
|
|
86
76
|
docx-redline apply input.docx --operations operations.json --profile agent --output reviewed.docx
|
|
@@ -88,46 +78,46 @@ node emit-operations.mjs | docx-redline apply input.docx --operations - --profil
|
|
|
88
78
|
```
|
|
89
79
|
|
|
90
80
|
Use `JSON.stringify` or a structured tool API. Never demonstrate `echo`, a
|
|
91
|
-
heredoc, or shell-interpolated
|
|
92
|
-
|
|
93
|
-
|
|
81
|
+
heredoc, or shell-interpolated document text. A Node byte-oriented wrapper should
|
|
82
|
+
call `openDocx` and return the structured result instead of recreating CLI
|
|
83
|
+
serialization, ZIP handling, targeting, validation, or rollback.
|
|
94
84
|
|
|
95
85
|
## Ordinary generated workflow
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
The first executable editing example in a generated skill must be focused
|
|
88
|
+
extraction:
|
|
99
89
|
|
|
100
90
|
```bash
|
|
101
|
-
docx-redline
|
|
91
|
+
docx-redline extract input.docx --search "renewal notice" --around 3
|
|
102
92
|
```
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
success, `anchorMatchCount > 1` signals a repeated anchor: confirm the returned
|
|
109
|
-
location and excerpts. Require `results[i].change` with `committed: true`,
|
|
110
|
-
`finalDisposition: "applied"`, and positive accepted-view verification.
|
|
94
|
+
Then apply once using the inspected strong target. Use complete `modified` text
|
|
95
|
+
for a broad semantic revision or `replacements` for a small literal change in
|
|
96
|
+
that target. Keep requested relative wording relative unless the user asks for
|
|
97
|
+
a concrete value.
|
|
111
98
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
Follow `selection.nextAfter` when extraction is truncated. Output projections must retain
|
|
100
|
+
`selection`, target descriptors, `humanReference`, and revision
|
|
101
|
+
cues.
|
|
115
102
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
### Observed restore branch
|
|
104
|
+
|
|
105
|
+
If the user requests restoration, search `--view rejected` first. Otherwise
|
|
106
|
+
follow an alternate-view `selection.hint` when returned. Restore only the
|
|
107
|
+
inspected strong target. Require `restore-shortcuts-v1` only if the integration
|
|
108
|
+
uses inline `--restore`; canonical operations remain available.
|
|
109
|
+
|
|
110
|
+
### Optional speculative execution
|
|
119
111
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
GitHub URLs. The operation schema remains
|
|
126
|
-
[document-operations.schema.json](schemas/document-operations.schema.json).
|
|
112
|
+
Do not make speculative apply part of an ordinary generated workflow. A harness
|
|
113
|
+
may opt into `speculative-search-apply-v1` only when it owns a measured confidence
|
|
114
|
+
policy and accepts the recovery cost of a miss. Keep anchor selection,
|
|
115
|
+
directional windows, occurrence handling, and exact matching semantics out of
|
|
116
|
+
the ordinary editing prompt; link to the knowledge base or CLI help instead.
|
|
127
117
|
|
|
128
118
|
## Recovery contract
|
|
129
119
|
|
|
130
|
-
Lead
|
|
120
|
+
Lead failure handling with these runtime fields:
|
|
131
121
|
|
|
132
122
|
1. `error.recovery.action`
|
|
133
123
|
2. `error.recovery.requiresReinspection`
|
|
@@ -135,32 +125,23 @@ Lead error handling with exactly these runtime fields:
|
|
|
135
125
|
4. `error.recovery.sameArgumentsSafe`
|
|
136
126
|
|
|
137
127
|
Use `retryPlan.base` and its operation indexes to select the original or partial
|
|
138
|
-
output package. Do not
|
|
139
|
-
code; error messages and bounded diagnostics explain the cause, while the four
|
|
140
|
-
fields above determine the next action.
|
|
128
|
+
output package. Do not reproduce an error-code decision tree in the skill.
|
|
141
129
|
|
|
142
130
|
## Presentation rules
|
|
143
131
|
|
|
144
|
-
Use `humanReference`, `provision`, or `nearestHeading` in
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
When no formal heading exists, use the returned short text lead. Never replace
|
|
148
|
-
an exact machine target with a human-facing reference.
|
|
132
|
+
Use `humanReference`, `provision`, or `nearestHeading` in completion reports.
|
|
133
|
+
Paragraph indexes and `P55`-style references are machine coordinates, not
|
|
134
|
+
locations a document user can follow. Keep the exact machine target internally.
|
|
149
135
|
|
|
150
136
|
## Generation checklist
|
|
151
137
|
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
- The
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
-
|
|
162
|
-
- Operations-file and stdin examples use structured serialization.
|
|
163
|
-
- The four-field recovery contract precedes any diagnostic commentary.
|
|
164
|
-
- Completion prose uses legal references rather than machine ordinals.
|
|
165
|
-
- The wrapper delegates mutation, validation, rollback, comments, numbering,
|
|
166
|
-
receipts, and OOXML packaging to the library.
|
|
138
|
+
- Exact tested release, contract 8, and the minimum capability set are pinned.
|
|
139
|
+
- Runtime compatibility is checked once.
|
|
140
|
+
- The first ordinary command is focused extraction.
|
|
141
|
+
- Apply uses the inspected target with `modified` or localized `replacements`.
|
|
142
|
+
- Restore appears only as a user-requested or observed-view branch.
|
|
143
|
+
- Speculative execution is absent unless the harness owns a measured policy.
|
|
144
|
+
- Ordinary success requires `completion: true` and a non-null `outputPath`.
|
|
145
|
+
- Recovery, authorization, source immutability, and structured serialization
|
|
146
|
+
invariants are preserved.
|
|
147
|
+
- Wrappers delegate mutation and OOXML packaging to the library.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Agent Surface Simplification Replay
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-09-14
|
|
4
|
+
**Runtime:** Node.js 24.11.1 on Windows x64
|
|
5
|
+
**Engine:** Current post-0.7.1 source for every policy
|
|
6
|
+
**Command:** `npm run benchmark:agent-surface`
|
|
7
|
+
**Configuration:** 7 measured iterations and 1 warmup per scenario/policy
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
This repository-controlled replay compares three workflow policies without
|
|
12
|
+
changing the underlying engine:
|
|
13
|
+
|
|
14
|
+
1. `v0.6.2-extract-full`: extraction followed by complete modified text.
|
|
15
|
+
2. `v0.7.1-speculation-led`: speculative literal edits with extraction/apply
|
|
16
|
+
recovery, and extraction for semantic or batch work.
|
|
17
|
+
3. `simplified-extract-localized`: extraction followed by a compact localized
|
|
18
|
+
operation against `paragraphId` plus `fingerprint`.
|
|
19
|
+
|
|
20
|
+
The six wholly synthetic scenarios cover a long literal edit, semantic drafting,
|
|
21
|
+
repeated visible text, rejected-view restoration, an NBSP-bearing target, and an
|
|
22
|
+
independent two-target batch. Every output is re-extracted and compared with its
|
|
23
|
+
predetermined accepted-view text. Benchmark-only verification calls are not
|
|
24
|
+
counted as workflow calls.
|
|
25
|
+
|
|
26
|
+
## Aggregate Results
|
|
27
|
+
|
|
28
|
+
| Policy | Correct runs | Total calls | Calls/run | Failed calls | Edit request bytes | Median local ms |
|
|
29
|
+
|---|---:|---:|---:|---:|---:|---:|
|
|
30
|
+
| v0.6.2 extract/full | 42/42 | 84 | 2.00 | 0 | 27,034 | 3.86 |
|
|
31
|
+
| v0.7.1 speculation-led | 42/42 | 77 | 1.83 | 7 | 8,988 | 3.86 |
|
|
32
|
+
| simplified extract/localized | 42/42 | 84 | 2.00 | 0 | 10,661 | 3.77 |
|
|
33
|
+
|
|
34
|
+
The simplified policy reduced generated edit-request bytes by 60.6% relative to
|
|
35
|
+
the complete-paragraph baseline while preserving a fixed two-call workflow and
|
|
36
|
+
zero failed calls. On the long-paragraph case, its median edit request was 193
|
|
37
|
+
bytes rather than 2,104 bytes, a 90.8% reduction.
|
|
38
|
+
|
|
39
|
+
The speculation-led policy saved seven total calls because two unique literal
|
|
40
|
+
scenarios completed in one call. The repeated-text scenario cost three calls and
|
|
41
|
+
one failure on every iteration. This demonstrates both the legitimate one-turn
|
|
42
|
+
benefit and the recovery cost that the simplified default is intended to avoid.
|
|
43
|
+
|
|
44
|
+
The localized form is not always smaller for a short paragraph. In the NBSP
|
|
45
|
+
scenario it used 344 bytes versus 343 bytes for complete modified text. The
|
|
46
|
+
benefit is strongest when the inspected source is long relative to the changed
|
|
47
|
+
span.
|
|
48
|
+
|
|
49
|
+
## Interpretation
|
|
50
|
+
|
|
51
|
+
The repository evidence supports the surface decision:
|
|
52
|
+
|
|
53
|
+
- keep speculative apply as an available expert/harness capability;
|
|
54
|
+
- do not make ordinary agents classify whether to use it;
|
|
55
|
+
- lead with deterministic extraction and one apply;
|
|
56
|
+
- retain localized replacements after inspection to avoid long output payloads;
|
|
57
|
+
- use compact `paragraphId` plus `fingerprint` targets for localized requests,
|
|
58
|
+
while retaining extracted text locally for drafting; and
|
|
59
|
+
- keep restoration as a request/hint-driven branch.
|
|
60
|
+
|
|
61
|
+
Local execution time is effectively neutral at this scale and remains far below
|
|
62
|
+
agent deliberation or provider latency. No performance claim should be based on
|
|
63
|
+
the small local timing differences in this report.
|
|
64
|
+
|
|
65
|
+
## Limitations
|
|
66
|
+
|
|
67
|
+
This is a deterministic workflow replay, not a model evaluation. It cannot
|
|
68
|
+
measure reasoning tokens, provider tokenization, transport latency, stochastic
|
|
69
|
+
instruction following, or subjective drafting quality. Repeated external agent
|
|
70
|
+
runs using the same model, prompt, document, and engine are still required before
|
|
71
|
+
claiming end-to-end latency improvement.
|
|
72
|
+
|
|
73
|
+
The machine-readable output is written to
|
|
74
|
+
`tmp/benchmarks/agent-surface-simplification-latest.json` and is intentionally
|
|
75
|
+
not committed.
|
package/node/cli-help.js
CHANGED
|
@@ -134,7 +134,7 @@ export const CLI_COMMAND_HELP = Object.freeze({
|
|
|
134
134
|
option('targetId', '--target-id <paragraphId>', 'Strong inline target using a paragraphId from fresh inspection.'),
|
|
135
135
|
option('restore', '--restore', 'Restore one rejected-view paragraph identified by --target-id; omit --modified for verbatim restoration.'),
|
|
136
136
|
option('modified', '--modified <text>', 'Complete desired accepted-view target content.'),
|
|
137
|
-
option('find', '--find <exact text>', '
|
|
137
|
+
option('find', '--find <exact text>', 'Case-sensitive source span; exact match first, then ASCII-space/NBSP equivalence.'),
|
|
138
138
|
option('replace', '--replace <text>', 'Replacement text for --find; an empty string deletes the span.'),
|
|
139
139
|
option('occurrence', '--occurrence <N>', 'Positive 1-based --find occurrence within the uniquely resolved target paragraph.'),
|
|
140
140
|
option('search', '--search <text>', 'Case-insensitive context anchor for speculative find/replace; prefer a longer, fairly unique phrase because all matches contribute scope.'),
|
package/node/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { validateRedlineOoxml } from '../core/redline-validation.js';
|
|
|
7
7
|
import { configureLogger } from '../adapters/logger.js';
|
|
8
8
|
import { isExistingRevisionsPolicy } from '../services/document-operation-contract.js';
|
|
9
9
|
import { normalizeErrorWithRecovery } from '../services/error-recovery.js';
|
|
10
|
+
import { localizedReplacementOffsets } from '../services/localized-replacement-compiler.js';
|
|
10
11
|
import { buildCliHelp, CLI_COMMANDS, commandOptionKeys } from './cli-help.js';
|
|
11
12
|
|
|
12
13
|
const suffixes = { apply: 'redlined', accept: 'accepted', reject: 'rejected', 'delete-comments': 'comments-removed' };
|
|
@@ -31,6 +32,7 @@ const CLI_CAPABILITIES = [
|
|
|
31
32
|
'localized-replacements-v1',
|
|
32
33
|
'speculative-search-apply-v1',
|
|
33
34
|
'localized-change-summary-v1',
|
|
35
|
+
'localized-space-equivalence-v1',
|
|
34
36
|
'restore-shortcuts-v1',
|
|
35
37
|
'deduplicated-cli-receipts',
|
|
36
38
|
'compact-cli-json-v1'
|
|
@@ -242,21 +244,27 @@ function resolveSpeculativePatchTarget(document, flags) {
|
|
|
242
244
|
}
|
|
243
245
|
|
|
244
246
|
const find = String(flags.find);
|
|
245
|
-
const
|
|
247
|
+
const patchCandidates = eligible.map(paragraph => ({
|
|
248
|
+
paragraph,
|
|
249
|
+
match: localizedReplacementOffsets(paragraph.exactText, find)
|
|
250
|
+
})).filter(candidate => candidate.match.offsets.length > 0);
|
|
251
|
+
const exactCandidates = patchCandidates.filter(candidate => candidate.match.matchMode === 'exact');
|
|
252
|
+
const matches = (exactCandidates.length > 0 ? exactCandidates : patchCandidates)
|
|
253
|
+
.map(candidate => candidate.paragraph);
|
|
246
254
|
const context = {
|
|
247
255
|
...(search ? { search, range: range.text, anchorMatchCount: anchors.length } : {})
|
|
248
256
|
};
|
|
249
257
|
if (matches.length === 0) {
|
|
250
258
|
throw speculativeError('PATCH_SOURCE_NOT_FOUND', search
|
|
251
|
-
? `No paragraph in the contextual range contains the
|
|
252
|
-
: `No paragraph contains the
|
|
259
|
+
? `No paragraph in the contextual range contains the patch source: "${find}".`
|
|
260
|
+
: `No paragraph contains the patch source: "${find}".`, {
|
|
253
261
|
context,
|
|
254
262
|
...(anchors.length ? { candidates: anchors.map(speculativeCandidate) } : {}),
|
|
255
263
|
recovery: { action: 'reinspect', requiresReinspection: true, sameArgumentsSafe: false }
|
|
256
264
|
});
|
|
257
265
|
}
|
|
258
266
|
if (matches.length > 1) {
|
|
259
|
-
throw speculativeError('AMBIGUOUS_TARGET', `The
|
|
267
|
+
throw speculativeError('AMBIGUOUS_TARGET', `The patch source matched ${matches.length} eligible paragraphs.`, {
|
|
260
268
|
context,
|
|
261
269
|
candidates: matches.map(speculativeCandidate),
|
|
262
270
|
recovery: { action: 'choose_candidate', requiresReinspection: false, sameArgumentsSafe: false }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ansonlai/docx-redline-js",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Host-independent OOXML reconciliation engine for .docx manipulation with track changes",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"!scripts/benchmark-agent-workflow.mjs",
|
|
48
48
|
"!scripts/benchmark-agent-cli-discovery.mjs",
|
|
49
49
|
"!scripts/benchmark-localized-turn-reduction.mjs",
|
|
50
|
+
"!scripts/benchmark-agent-surface-simplification.mjs",
|
|
50
51
|
"!scripts/lib/agent-performance-cases.mjs",
|
|
51
52
|
"docs/AGENT_FAST_START.md",
|
|
52
53
|
"docs/AGENT_KNOWLEDGE_BASE.md",
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
"docs/validation-reports/2026-09-12-agent-cli-discovery-baseline.md",
|
|
59
60
|
"docs/validation-reports/2026-09-13-agent-cli-efficiency-rollout.md",
|
|
60
61
|
"docs/validation-reports/2026-09-14-localized-patching-rollout.md",
|
|
62
|
+
"docs/validation-reports/2026-09-14-agent-surface-simplification.md",
|
|
61
63
|
"index.js",
|
|
62
64
|
"index.d.ts",
|
|
63
65
|
"dist/",
|
|
@@ -97,6 +99,7 @@
|
|
|
97
99
|
"benchmark:agent": "node scripts/benchmark-agent-workflow.mjs",
|
|
98
100
|
"benchmark:agent-cli": "node scripts/benchmark-agent-cli-discovery.mjs",
|
|
99
101
|
"benchmark:localized": "node scripts/benchmark-localized-turn-reduction.mjs",
|
|
102
|
+
"benchmark:agent-surface": "node scripts/benchmark-agent-surface-simplification.mjs",
|
|
100
103
|
"benchmark:targeting": "node scripts/benchmark-targeting-hot-paths.mjs",
|
|
101
104
|
"benchmark:tests": "node scripts/benchmark-test-runner.mjs",
|
|
102
105
|
"profile:routes": "node scripts/profile-route-selection.mjs",
|
|
@@ -41,6 +41,65 @@ function occurrenceOffsets(sourceText, find) {
|
|
|
41
41
|
return offsets;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function equivalentSpace(left, right) {
|
|
45
|
+
return (left === ' ' || left === '\u00a0')
|
|
46
|
+
&& (right === ' ' || right === '\u00a0');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function spaceEquivalentOffsets(sourceText, find) {
|
|
50
|
+
const offsets = [];
|
|
51
|
+
for (let start = 0; start <= sourceText.length - find.length; start += 1) {
|
|
52
|
+
let matches = true;
|
|
53
|
+
for (let offset = 0; offset < find.length; offset += 1) {
|
|
54
|
+
const sourceCharacter = sourceText[start + offset];
|
|
55
|
+
const findCharacter = find[offset];
|
|
56
|
+
if (sourceCharacter !== findCharacter && !equivalentSpace(sourceCharacter, findCharacter)) {
|
|
57
|
+
matches = false;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (matches) offsets.push(start);
|
|
62
|
+
}
|
|
63
|
+
return offsets;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Resolve exact offsets first, then the narrow ASCII-space/NBSP equivalent fallback. */
|
|
67
|
+
export function localizedReplacementOffsets(sourceText, find) {
|
|
68
|
+
const exact = occurrenceOffsets(sourceText, find);
|
|
69
|
+
if (exact.length > 0) return { offsets: exact, matchMode: 'exact' };
|
|
70
|
+
return { offsets: spaceEquivalentOffsets(sourceText, find), matchMode: 'space_equivalent' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function preserveEquivalentBoundarySpaces(sourceSlice, find, replace) {
|
|
74
|
+
const effective = replace.split('');
|
|
75
|
+
let prefix = 0;
|
|
76
|
+
while (
|
|
77
|
+
prefix < find.length
|
|
78
|
+
&& prefix < replace.length
|
|
79
|
+
&& (find[prefix] === replace[prefix] || equivalentSpace(find[prefix], replace[prefix]))
|
|
80
|
+
) {
|
|
81
|
+
if (equivalentSpace(sourceSlice[prefix], replace[prefix])) effective[prefix] = sourceSlice[prefix];
|
|
82
|
+
prefix += 1;
|
|
83
|
+
}
|
|
84
|
+
let sourceOffset = find.length - 1;
|
|
85
|
+
let replacementOffset = replace.length - 1;
|
|
86
|
+
while (
|
|
87
|
+
sourceOffset >= prefix
|
|
88
|
+
&& replacementOffset >= prefix
|
|
89
|
+
&& (
|
|
90
|
+
find[sourceOffset] === replace[replacementOffset]
|
|
91
|
+
|| equivalentSpace(find[sourceOffset], replace[replacementOffset])
|
|
92
|
+
)
|
|
93
|
+
) {
|
|
94
|
+
if (equivalentSpace(sourceSlice[sourceOffset], replace[replacementOffset])) {
|
|
95
|
+
effective[replacementOffset] = sourceSlice[sourceOffset];
|
|
96
|
+
}
|
|
97
|
+
sourceOffset -= 1;
|
|
98
|
+
replacementOffset -= 1;
|
|
99
|
+
}
|
|
100
|
+
return effective.join('');
|
|
101
|
+
}
|
|
102
|
+
|
|
44
103
|
function offsetCandidate(sourceText, start, length) {
|
|
45
104
|
const excerptStart = Math.max(0, start - 40);
|
|
46
105
|
const excerptEnd = Math.min(sourceText.length, start + length + 40);
|
|
@@ -168,15 +227,15 @@ export function compileExactReplacements(
|
|
|
168
227
|
const replacement = validation.replacements[index];
|
|
169
228
|
const itemField = `${field}[${index}]`;
|
|
170
229
|
const occurrence = replacement.occurrence ?? null;
|
|
171
|
-
const offsets =
|
|
230
|
+
const { offsets, matchMode } = localizedReplacementOffsets(sourceText, replacement.find);
|
|
172
231
|
if (offsets.length === 0 || (occurrence != null && occurrence > offsets.length)) {
|
|
173
232
|
return {
|
|
174
233
|
ok: false,
|
|
175
234
|
error: patchError(
|
|
176
235
|
'PATCH_SOURCE_NOT_FOUND',
|
|
177
236
|
occurrence == null
|
|
178
|
-
? `
|
|
179
|
-
: `Occurrence ${occurrence} of
|
|
237
|
+
? `Patch source was not found: "${replacement.find}".`
|
|
238
|
+
: `Occurrence ${occurrence} of patch source was not found: "${replacement.find}".`,
|
|
180
239
|
itemField,
|
|
181
240
|
'change_patch',
|
|
182
241
|
{ matchCount: offsets.length }
|
|
@@ -188,7 +247,7 @@ export function compileExactReplacements(
|
|
|
188
247
|
ok: false,
|
|
189
248
|
error: patchError(
|
|
190
249
|
'AMBIGUOUS_PATCH_SOURCE',
|
|
191
|
-
`
|
|
250
|
+
`Patch source matched ${offsets.length} locations; provide occurrence.`,
|
|
192
251
|
itemField,
|
|
193
252
|
'choose_occurrence',
|
|
194
253
|
{ candidates: offsets.map(start => offsetCandidate(sourceText, start, replacement.find.length)) }
|
|
@@ -197,12 +256,17 @@ export function compileExactReplacements(
|
|
|
197
256
|
}
|
|
198
257
|
|
|
199
258
|
const start = offsets[(occurrence || 1) - 1];
|
|
259
|
+
const sourceSlice = sourceText.slice(start, start + replacement.find.length);
|
|
200
260
|
resolved.push({
|
|
201
261
|
requestIndex: index,
|
|
202
262
|
start,
|
|
203
263
|
end: start + replacement.find.length,
|
|
204
264
|
find: replacement.find,
|
|
205
265
|
replace: replacement.replace,
|
|
266
|
+
effectiveReplace: matchMode === 'space_equivalent'
|
|
267
|
+
? preserveEquivalentBoundarySpaces(sourceSlice, replacement.find, replacement.replace)
|
|
268
|
+
: replacement.replace,
|
|
269
|
+
matchMode,
|
|
206
270
|
occurrence: occurrence || 1
|
|
207
271
|
});
|
|
208
272
|
}
|
|
@@ -212,7 +276,7 @@ export function compileExactReplacements(
|
|
|
212
276
|
for (const replacement of resolved) {
|
|
213
277
|
const previous = unique.at(-1);
|
|
214
278
|
if (previous && replacement.start === previous.start && replacement.end === previous.end) {
|
|
215
|
-
if (replacement.
|
|
279
|
+
if (replacement.effectiveReplace !== previous.effectiveReplace) {
|
|
216
280
|
return {
|
|
217
281
|
ok: false,
|
|
218
282
|
error: patchError(
|
|
@@ -245,7 +309,7 @@ export function compileExactReplacements(
|
|
|
245
309
|
let desiredText = '';
|
|
246
310
|
for (const replacement of unique) {
|
|
247
311
|
desiredText += sourceText.slice(cursor, replacement.start);
|
|
248
|
-
desiredText += replacement.
|
|
312
|
+
desiredText += replacement.effectiveReplace;
|
|
249
313
|
cursor = replacement.end;
|
|
250
314
|
}
|
|
251
315
|
desiredText += sourceText.slice(cursor);
|
|
@@ -259,9 +323,10 @@ export function compileExactReplacements(
|
|
|
259
323
|
end: replacement.end,
|
|
260
324
|
find: replacement.find,
|
|
261
325
|
replace: replacement.replace,
|
|
326
|
+
matchMode: replacement.matchMode,
|
|
262
327
|
occurrence: replacement.occurrence,
|
|
263
328
|
removedLength: replacement.end - replacement.start,
|
|
264
|
-
insertedLength: replacement.
|
|
329
|
+
insertedLength: replacement.effectiveReplace.length
|
|
265
330
|
}))
|
|
266
331
|
};
|
|
267
332
|
}
|
|
@@ -280,6 +345,7 @@ export function buildLocalizedReplacementChange(compilation, outputText, target
|
|
|
280
345
|
return {
|
|
281
346
|
find: replacement.find,
|
|
282
347
|
replace: replacement.replace,
|
|
348
|
+
...(replacement.matchMode === 'space_equivalent' ? { matchMode: replacement.matchMode } : {}),
|
|
283
349
|
occurrence: replacement.occurrence,
|
|
284
350
|
beforeExcerpt: boundedExcerpt(sourceText, replacement.start, replacement.end),
|
|
285
351
|
afterExcerpt: boundedExcerpt(desiredText, afterStart, afterEnd)
|