@ansonlai/docx-redline-js 0.5.4 → 0.6.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.
Files changed (55) hide show
  1. package/AGENTS.md +82 -697
  2. package/ARCHITECTURE.md +13 -1
  3. package/CHANGELOG.md +8 -0
  4. package/README.md +177 -45
  5. package/core/paragraph-targeting.js +14 -2
  6. package/dist/docx-redline-js.esm.js +184 -51
  7. package/dist/docx-redline-js.esm.js.map +3 -3
  8. package/dist/docx-redline-js.esm.min.js +77 -77
  9. package/dist/docx-redline-js.esm.min.js.map +4 -4
  10. package/docs/AGENT_FAST_START.md +59 -0
  11. package/docs/AGENT_KNOWLEDGE_BASE.md +878 -0
  12. package/docs/SKILL_AUTHORING.md +126 -0
  13. package/docs/TESTING.md +35 -1
  14. package/docs/schemas/document-operations.schema.json +5 -1
  15. package/docs/validation-reports/2026-09-12-agent-cli-discovery-baseline.md +56 -0
  16. package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +86 -0
  17. package/docs/validation-reports/2026-09-13-agent-cli-efficiency-rollout.md +86 -0
  18. package/engine/oxml-engine.js +80 -13
  19. package/engine/run-builders.js +5 -15
  20. package/index.d.ts +28 -3
  21. package/node/cli-help.js +209 -0
  22. package/node/cli.js +323 -65
  23. package/node/docx-document.js +120 -69
  24. package/node/index.d.ts +6 -2
  25. package/package.json +15 -3
  26. package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
  27. package/services/batch-operation-orchestrator.js +215 -120
  28. package/services/document-inspection.js +89 -11
  29. package/services/document-operation-applier.js +52 -34
  30. package/services/document-operation-contract.js +10 -6
  31. package/services/document-operation-mutations.js +51 -5
  32. package/services/document-operation-session.js +4 -0
  33. package/services/error-recovery.js +174 -0
  34. package/services/operation-batch-compiler.js +394 -0
  35. package/services/operation-preflight.js +91 -72
  36. package/services/standalone-operation-runner.d.ts +17 -1
  37. package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
  38. package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -1399
  39. package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
  40. package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
  41. package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
  42. package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
  43. package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
  44. package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
  45. package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
  46. package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
  47. package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
  48. package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
  49. package/docs/test-comparison-dashboard.html +0 -4338
  50. package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
  51. package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
  52. package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
  53. package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
  54. package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
  55. package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
@@ -0,0 +1,126 @@
1
+ # Skill and Harness Authoring Contract
2
+
3
+ Use this page when creating or updating an agent skill, MCP server, or custom
4
+ harness for `@ansonlai/docx-redline-js`. Ordinary document-editing agents should
5
+ load [Agent Fast Start](AGENT_FAST_START.md), not this design contract.
6
+
7
+ ## Runtime negotiation
8
+
9
+ Run this before the first document operation when the skill vendors or invokes
10
+ the CLI:
11
+
12
+ ```bash
13
+ docx-redline version
14
+ ```
15
+
16
+ Require contract version 7 and only the capabilities the integration uses. A
17
+ typical shell skill should require `command-help-v1`, `inspection-context-v1`,
18
+ `bounded-inspection-v1`, `human-document-references-v1`,
19
+ `agent-safety-profile-v2`, `deduplicated-cli-receipts`, and
20
+ `recovery-envelope-v1`. Require `operations-stdin` or `compact-cli-json-v1` only
21
+ when the host uses those features. Fail closed with an upgrade instruction when
22
+ a required capability is absent. Never inspect `dist/`, minified source, or ZIP
23
+ parts to compensate for a stale runtime.
24
+
25
+ ## Safety invariants
26
+
27
+ Every generated integration must preserve these rules:
28
+
29
+ - Inspect narrowly and copy `exactText` plus `paragraphId` or `fingerprint`.
30
+ - Treat `modified` as the complete desired accepted-view target content.
31
+ - Use strict targeting, package validation, and a derived output path; never
32
+ overwrite the source unless the caller explicitly requests `--in-place`.
33
+ - Require `completion: true`, `written: true`, a non-null `outputPath`, and no
34
+ per-operation error before reporting completion.
35
+ - Never accept or reject another reviewer's work or delete comments without
36
+ explicit user authorization.
37
+ - Never retry unchanged failed arguments.
38
+
39
+ The `agent` profile enables complete-success exit behavior and retains the
40
+ ordinary progressive default. It does not select atomic execution or a special
41
+ existing-revision policy. A skill must state those choices explicitly when its
42
+ workflow needs them.
43
+
44
+ ## Workflow policy choices
45
+
46
+ Choose and document these separately from safety invariants:
47
+
48
+ | Choice | Options | Guidance |
49
+ |---|---|---|
50
+ | Transaction | progressive default or `--atomic` | Use progressive for independent edits; use atomic when partial output is unacceptable. |
51
+ | Existing revisions | `merge-same-author` default or explicit policy | Use `slice-cross-author` only when editing inside another reviewer's pending insertion is intended. |
52
+ | Reviewer identity | flag, operation author, environment, or fallback | `AI Redliner` is a valid visible fallback. `DOCX_REDLINE_AUTHOR` is an optional harness preference, not a guard. |
53
+ | Output naming | derived sibling or explicit destination | Keep source immutability unless in-place mutation is deliberately authorized. |
54
+
55
+ Examples of valid profile composition:
56
+
57
+ ```bash
58
+ docx-redline apply input.docx --operations operations.json --profile agent --output reviewed.docx
59
+ docx-redline apply input.docx --operations operations.json --profile agent --atomic --output reviewed.docx
60
+ docx-redline apply input.docx --operations operations.json --profile agent --existing-revisions slice-cross-author --output reviewed.docx
61
+ ```
62
+
63
+ Check `effectiveOptions` rather than assuming the resolved transaction,
64
+ revision, author, or redline policy.
65
+
66
+ ## Transport choices
67
+
68
+ A UTF-8 operations file and serializer-backed stdin are peer transports. Choose
69
+ the form the host can construct safely:
70
+
71
+ ```bash
72
+ docx-redline apply input.docx --operations operations.json --profile agent --output reviewed.docx
73
+ node emit-operations.mjs | docx-redline apply input.docx --operations - --profile agent --compact --output reviewed.docx
74
+ ```
75
+
76
+ Use `JSON.stringify` or a structured tool API. Never demonstrate `echo`, a
77
+ heredoc, or shell-interpolated legal text. `--compact` changes stdout formatting,
78
+ not result semantics. A Node byte-oriented wrapper should call `openDocx` and
79
+ return the full structured result rather than recreating the CLI serializer.
80
+
81
+ ## Ordinary generated workflow
82
+
83
+ The first executable document-editing example in a generated skill should be a
84
+ focused contextual extraction:
85
+
86
+ ```bash
87
+ docx-redline extract input.docx --search "force majeure" --around 3
88
+ ```
89
+
90
+ Use `selection.nextAfter` with `--after` when truncated. Apply once per stable
91
+ batch. Run `docx-redline apply --help` for canonical redline, whole-paragraph
92
+ comment, and rejected-view restore shapes; the operation schema remains
93
+ [document-operations.schema.json](schemas/document-operations.schema.json).
94
+
95
+ ## Recovery contract
96
+
97
+ Lead error handling with exactly these runtime fields:
98
+
99
+ 1. `error.recovery.action`
100
+ 2. `error.recovery.requiresReinspection`
101
+ 3. `error.recovery.requiresUserAuthorization`
102
+ 4. `error.recovery.sameArgumentsSafe`
103
+
104
+ Use `retryPlan.base` and its operation indexes to select the original or partial
105
+ output package. Do not generate a second prose decision tree for every error
106
+ code; error messages and bounded diagnostics explain the cause, while the four
107
+ fields above determine the next action.
108
+
109
+ ## Presentation rules
110
+
111
+ Use `humanReference`, `provision`, or `nearestHeading` in user-facing completion
112
+ reports. `P55`, `index: 55`, and phrases such as “the 55th paragraph” are machine
113
+ targeting and pagination coordinates, not locations a Word user can follow.
114
+ When no formal heading exists, use the returned short text lead. Never replace
115
+ an exact machine target with a human-facing reference.
116
+
117
+ ## Generation checklist
118
+
119
+ - Runtime version and required capabilities are checked once.
120
+ - The first ordinary edit command is focused `extract --search ... --around`.
121
+ - Safety invariants are not mixed with transaction or revision-policy choices.
122
+ - Operations-file and stdin examples use structured serialization.
123
+ - The four-field recovery contract precedes any diagnostic commentary.
124
+ - Completion prose uses legal references rather than machine ordinals.
125
+ - The wrapper delegates mutation, validation, rollback, comments, numbering,
126
+ receipts, and OOXML packaging to the library.
package/docs/TESTING.md CHANGED
@@ -22,7 +22,10 @@ fixtures rarely contain.
22
22
  | Multimodal LLM visual spot check | On-demand / sampled | Evaluates rendered real-document pages with vision models for layout, table alignment, and typography regressions | Full-corpus automated coverage (intentionally decoupled and sampled due to cost/time) |
23
23
  | XSD and LibreOffice | See [Release validation and independent oracles](#release-validation-and-independent-oracles) | Schema conformance and acceptance by a second consumer | Word-specific revision semantics |
24
24
  | Agent inspection and package facade | `node tests/document_inspection_tests.mjs`, `node tests/docx_package_facade_tests.mjs` | Canonical text, comment/list resolution, package-scoped IDs, untouched-part preservation, and atomic rollback | Desktop Word rendering |
25
- | Agent CLI | `node tests/agent_cli_tests.mjs` | JSON contracts, exact-text extraction, author requirements, safe output behavior, all command families, and operation-schema readability | Cross-platform CI beyond the current runner |
25
+ | Agent CLI | `node tests/agent_cli_tests.mjs`, `node tests/agent_cli_protocol_tests.mjs`, `node tests/agent_cli_help_tests.mjs`, `node tests/agent_cli_inspection_context_tests.mjs`, `node tests/agent_cli_output_budget_tests.mjs` | JSON contracts, command-specific help, contextual pagination, composable progressive/atomic profiles, deduplicated receipts, compact stdout, file/stdin Unicode fidelity, complete-success exits, source safety, command families, and schema readability | Cross-platform CI beyond the current runner |
26
+ | Example agent session | `node tests/agent_session_example_tests.mjs`, `node tests/localized_edit_example_tests.mjs` | A non-package sample wrapper delegates to the Node facade, binds and refreshes opaque handles across package revisions, returns context windows, compiles exact localized edits, preserves accepted/rejected lifecycle and atomic rollback, and supports review resolution | LLM/provider latency or a production tool-server transport |
27
+ | Batch compilation and recovery | `node tests/batch_source_binding_tests.mjs`, `node tests/error_recovery_contract_tests.mjs`, `node tests/capture_dependency_graph_tests.mjs` | Batch-start source identity survives structural index drift and savepoint restoration; true overlap and capture fan-out fail before mutation; recovery envelopes, retry bases, bounded CLI diagnostics, and complete-success exits remain stable | Whether a model follows the returned recovery action correctly |
28
+ | Agent documentation contract | `node tests/agent_documentation_contract_tests.mjs` | The ordinary fast start stays within its 40–60 line/600-word budget, examples lead with scoped contextual extraction, and packaged skill-authoring guidance separates invariants, policies, transports, recovery, and presentation while the development wrapper stays outside package files | Whether a particular model reads or follows the instructions |
26
29
  | Agent edge cases | `node tests/canonical_paragraph_text_tests.mjs`, `node tests/document_inspection_edge_tests.mjs`, `node tests/docx_package_transaction_edge_tests.mjs`, `node tests/node_zip_archive_tests.mjs`, `node tests/agent_cli_edge_tests.mjs` | Revision-view semantics, cross-paragraph anchors, nested numbering, transaction reuse, multi-author cleanup, malformed ZIP handling, and destructive CLI safeguards | Desktop Word rendering and non-Windows CI |
27
30
  | Performance boundary regression | `node tests/performance_phase2_boundary_tests.mjs` | Stable facade re-exports, leaf imports, session rollback, isolated context commit, and comment-first scheduling | The Phase 1 one-parse/one-serialize performance target |
28
31
  | Live-session accuracy and instrumentation | `node tests/performance_phase1_session_tests.mjs` | One full parse/serialization, sequential semantic equivalence, exact accepted/rejected text, valid revisions, list/table/comment/highlight preservation, savepoint no-ops, and zero-serialization rollback | Desktop Word rendering |
@@ -79,6 +82,37 @@ not correctness gates. Per-operation DOM savepoints are retained because
79
82
  redline accuracy, no-op isolation, and rollback fidelity take precedence over
80
83
  the aspirational speed target.
81
84
 
85
+ Run the observational agent-protocol benchmark with:
86
+
87
+ ```powershell
88
+ npm run benchmark:agent
89
+ ```
90
+
91
+ It writes `tmp/benchmarks/agent-workflow-latest.json` and compares canonical
92
+ stateless Node, legacy file-based CLI, compact stdin/profile CLI, and the
93
+ development-only session example. It records native/in-process CLI time, heap,
94
+ serialized request bytes, protocol calls, instruction words/bytes, exact
95
+ accepted/rejected text, comment preservation, ordering, recovery, and
96
+ cross-author attribution. It does not measure or estimate LLM reasoning, exact
97
+ tokens, tool transport, Claude, or OpenCode wall time; collect those separately
98
+ in the calling harness. The checked WP-07 results are summarized in the
99
+ [agent protocol rollout audit](validation-reports/2026-09-12-agent-protocol-rollout.md).
100
+
101
+ Run the CLI discovery/output benchmark with:
102
+
103
+ ```powershell
104
+ npm run benchmark:agent-cli
105
+ ```
106
+
107
+ It records command-specific help size, broad inspect/search response size,
108
+ context-window call count, pagination metadata, case-insensitive match parity,
109
+ and native elapsed time in `tmp/benchmarks/agent-cli-discovery-latest.json`.
110
+ The checked WP-00 through WP-02 before/after measurements are in the
111
+ [agent CLI discovery baseline](validation-reports/2026-09-12-agent-cli-discovery-baseline.md).
112
+ WP-03 through WP-05 profile, output-size, workflow, documentation, and package
113
+ results are in the
114
+ [agent CLI efficiency rollout audit](validation-reports/2026-09-13-agent-cli-efficiency-rollout.md).
115
+
82
116
  ## Cross-author revision slicing test suite
83
117
 
84
118
  The cross-author revision slicing subsystem introduces six complementary test lanes:
@@ -48,7 +48,11 @@
48
48
  "index": { "type": ["integer", "string"] }, "paragraphIndex": { "type": ["integer", "string"] },
49
49
  "paragraphId": { "type": "string" }, "occurrence": { "type": "integer", "minimum": 1 },
50
50
  "inTable": { "type": "boolean" }, "fingerprint": { "type": "string" }, "sourceFingerprint": { "type": "string" },
51
- "revisionView": { "type": "string", "enum": ["accepted", "rejected"] },
51
+ "revisionView": {
52
+ "type": "string",
53
+ "enum": ["accepted", "rejected"],
54
+ "description": "Defaults to rejected for restore operations and accepted for other operation types."
55
+ },
52
56
  "captureRef": { "type": "string", "minLength": 1, "maxLength": 256 },
53
57
  "select": { "type": "string", "minLength": 1 }
54
58
  },
@@ -0,0 +1,56 @@
1
+ # Agent CLI Discovery Baseline
2
+
3
+ **Date:** 2026-09-12
4
+
5
+ **Scope:** WP-00 baseline for command discovery, inspection breadth, contextual
6
+ search, and compact mutation output
7
+
8
+ ## Before-state
9
+
10
+ Measurements were captured from the source CLI immediately before WP-01/WP-02,
11
+ using `tests/fixtures/sample_doc_test.docx` and pretty-printed CLI JSON:
12
+
13
+ | Case | Result |
14
+ |---|---:|
15
+ | Global help | 171 bytes |
16
+ | `apply --help` | 171 bytes; identical to global help |
17
+ | `inspect --non-empty` | 72,387 bytes / 63 paragraphs |
18
+ | Broad `extract --search "the"` | 13,968 bytes / 31 paragraphs |
19
+ | Context retrieval | Search plus range: 2 commands |
20
+ | Successful one-operation receipt | 464 bytes nested plus the same 464 bytes at root |
21
+
22
+ The help payload did not describe command flags, search case behavior, or
23
+ redline/comment/restore shapes. The detailed unscoped inspection exceeded the
24
+ observed 64 KiB harness ceiling. Receipt deduplication is recorded here but is
25
+ owned by WP-04, not WP-00 through WP-02.
26
+
27
+ ## WP-01/WP-02 result
28
+
29
+ The checked post-change run produced:
30
+
31
+ | Case | Result |
32
+ |---|---:|
33
+ | Global command-index help | 1,390 bytes |
34
+ | Command-specific apply help | 4,400 bytes with flags and three operation shapes |
35
+ | Bounded `inspect --non-empty` | 25,093 bytes / first 20 of 63 paragraphs |
36
+ | Bounded `extract --search "the"` | 12,855 bytes / first 20 of 31 matches |
37
+ | `extract --search "agreement" --around 2 --limit 1` | 2,178 bytes / one command / three paragraphs |
38
+
39
+ Both formerly broad responses remain valid JSON below the 48 KiB soft limit and
40
+ include stable continuation metadata. Search case variants returned identical
41
+ indexes. A paragraph that individually exceeds the soft limit is returned whole
42
+ and marked `oversizeItem` rather than truncating `exactText`.
43
+
44
+ ## Reproduction
45
+
46
+ Run:
47
+
48
+ ```bash
49
+ npm run benchmark:agent-cli
50
+ ```
51
+
52
+ The benchmark retains the observed before-state constants and records the active
53
+ post-change results in `tmp/benchmarks/agent-cli-discovery-latest.json`. It
54
+ measures CLI response bytes, calls, paragraphs, selection metadata, and native
55
+ elapsed time. It does not estimate model tokens, provider wall time, or tool
56
+ transport latency.
@@ -0,0 +1,86 @@
1
+ # Agent Protocol Rollout Audit
2
+
3
+ **Date:** 2026-09-12
4
+ **Scope:** WP-06 shell/documentation work and WP-07 repository-level rollout
5
+ **Benchmark:** `npm run benchmark:agent` on Node v24.11.1 / Windows x64,
6
+ 7 measured iterations after 2 warmups
7
+
8
+ ## Outcome
9
+
10
+ The compact shell path removes the operations-file tool turn, and the
11
+ development session example removes repeated target/policy payload. All measured
12
+ paths produced the requested accepted view, restored the exact source text in
13
+ the rejected view, passed package/revision validation, and preserved comment
14
+ content. These are protocol and native-runtime measurements; no LLM/provider
15
+ latency or token count was estimated.
16
+
17
+ | Measure | Legacy | WP-06/07 result |
18
+ |---|---:|---:|
19
+ | Ordinary agent instructions | 2,023 words | 349 words (82.75% reduction) |
20
+ | Repository launch card | 2,023 words | 527 words |
21
+ | Shell workflow calls | extract + file write + apply (3) | extract + stdin apply (2; 33.33% reduction) |
22
+ | Independent early-split permutations | one previously order-sensitive direction | both directions succeed |
23
+
24
+ ## Measured workflow results
25
+
26
+ Median wall time includes the in-process CLI file/stdin transport for CLI rows.
27
+ It does not include process startup, a model, provider tokenization, or tool
28
+ transport.
29
+
30
+ | Case | Legacy CLI | Compact stdin CLI | Session example | Session request bytes vs legacy |
31
+ |---|---:|---:|---:|---:|
32
+ | Terminal punctuation | 49.63 ms | 46.05 ms | 49.50 ms | 125 vs 255 (50.98% fewer) |
33
+ | Term-duration phrase | 55.50 ms | 51.30 ms | 56.05 ms | 128 vs 630 (79.68% fewer) |
34
+ | Simple mutuality | 62.82 ms | 50.22 ms | 58.45 ms | 281 vs 935 (69.95% fewer) |
35
+ | Full-clause rewrite | 50.55 ms | 50.31 ms | 53.81 ms | 223 vs 407 (45.21% fewer) |
36
+ | Comment + redline | 71.94 ms | 62.11 ms | 63.81 ms | 290 vs 724 (59.94% fewer) |
37
+
38
+ Compared with the canonical stateless Node request envelope, localized session
39
+ requests remained 86.11% smaller for punctuation, 89.96% smaller for a duration
40
+ change, and 82.22% smaller for deterministic mutuality. A complete clause
41
+ rewrite is naturally less compressible and is not presented as a localized-edit
42
+ win.
43
+
44
+ ## Fidelity and recovery audit
45
+
46
+ - The benchmark validates accepted and rejected paragraph text for every text
47
+ edit and verifies the mixed-batch comment definition remains present.
48
+ - Both ten-edit permutations around an early paragraph split complete without
49
+ target errors.
50
+ - An ambiguous target returns two candidates without auto-selection.
51
+ - A stale session handle returns `STALE_TARGET_HANDLE` with recovery action
52
+ `reinspect`.
53
+ - The default cross-author policy refuses the edit with `EXISTING_REVISIONS`.
54
+ `slice-cross-author` succeeds while retaining both original authors (Anson
55
+ Lai and John Doe), and rejecting the benchmark author's work still retains
56
+ those authors.
57
+ - The CLI protocol regression proves UTF-8 stdin transport, package-revision
58
+ enforcement, source immutability, agent-profile defaults, atomic rollback,
59
+ progressive exit code 3, and rejected-view restoration.
60
+
61
+ ## Configuration decision
62
+
63
+ > Historical note: this section records CLI contract version 5. Contract version
64
+ > 7 decomposes `--profile agent`: it now supplies complete-success execution
65
+ > without forcing atomic mode. Skills choose `--atomic` separately.
66
+
67
+ No project or auto-discovered configuration file was added. The CLI already
68
+ defaults author, strict targeting, validation, tracked changes, revision safety,
69
+ and output naming. The explicit `--profile agent` flag reports
70
+ `effectiveOptions`; hidden configuration discovery still would not justify its
71
+ additional precedence reasoning.
72
+
73
+ ## Remaining external measurement
74
+
75
+ Claude/OpenCode wall time, prompt tokens, generated tokens, and provider tool
76
+ latency must be captured by those calling harnesses. The repository benchmark
77
+ records bytes, words, calls, native time, failure behavior, and document
78
+ fidelity so those external observations can be compared without conflating the
79
+ library with model reasoning.
80
+
81
+ ## Automated verification
82
+
83
+ - `npm test`: 107 passed, 0 failed.
84
+ - `npm run check:types`: 123 runtime exports have declarations.
85
+ - `npm run test:isolation`: core dependency and Word-API isolation passed.
86
+ - Focused ESLint for all WP-06/07 source, benchmark, and test files passed.
@@ -0,0 +1,86 @@
1
+ # Agent CLI Efficiency Rollout Audit
2
+
3
+ **Date:** 2026-09-13
4
+ **Scope:** WP-03 through WP-05 profile composition, compact mutation output,
5
+ skill-authoring guidance, and package parity
6
+
7
+ ## Outcome
8
+
9
+ CLI contract version 7 separates complete-success behavior from workflow
10
+ policy. `--profile agent` now retains progressive execution and the ordinary
11
+ `merge-same-author` policy; `--atomic` and explicit revision policies compose
12
+ with it and appear in `effectiveOptions`. Reviewer precedence and the visible
13
+ `AI Redliner` fallback are unchanged.
14
+
15
+ Compact CLI mutation output now contains one authoritative top-level receipt per
16
+ operation and no nested receipt copy. Successful exact-match diagnostics are
17
+ omitted; successful space-equivalent matches retain `{ mode, differenceCount }`.
18
+ Errors keep their bounded recovery evidence. The Node facade and standalone
19
+ runner retain their complete per-result receipt and target-match structures.
20
+
21
+ ## Measured output
22
+
23
+ `npm run benchmark:agent-cli` uses the same one-operation sample established in
24
+ the WP-00 baseline:
25
+
26
+ | Mutation response | Bytes | Change from pre-WP-04 |
27
+ |---|---:|---:|
28
+ | Pre-WP-04 pretty JSON | 2,968 | — |
29
+ | Contract-7 pretty JSON | 2,216 | 25.34% smaller |
30
+ | Contract-7 `--compact` JSON | 1,557 | 47.54% smaller |
31
+
32
+ One-line serialization is 29.74% smaller than the structurally deduplicated
33
+ pretty response, so contract version 7 exposes it explicitly through `--compact`
34
+ and `compact-cli-json-v1`. It changes whitespace only.
35
+
36
+ ## Representative workflow audit
37
+
38
+ The checked repository run records:
39
+
40
+ - one relevant command-help lookup;
41
+ - zero source, distribution-bundle, or ZIP-part inspection calls;
42
+ - one contextual clause search and one apply attempt;
43
+ - progressive agent-profile execution with complete-success exits;
44
+ - exact accepted text and exact source restoration after rejection;
45
+ - unchanged comment count and byte-identical source input; and
46
+ - one top-level receipt with no nested copy.
47
+
48
+ The existing agent workflow benchmark separately covers mixed comment/redline
49
+ work, independent batch permutations, stale and ambiguous targeting recovery,
50
+ and cross-author attribution. File-based and serializer-backed stdin operations
51
+ are both exercised with the same Unicode payload by the CLI protocol suite.
52
+
53
+ ## Documentation and package parity
54
+
55
+ The shipped documentation now includes `docs/SKILL_AUTHORING.md`. It separates
56
+ safety invariants, workflow policy, transport choices, four-field recovery, and
57
+ presentation rules. Executable documentation tests require each ordinary-use
58
+ section to lead with focused contextual extraction and require skills to cite
59
+ `humanReference`, provisions, or headings rather than machine paragraph
60
+ ordinals.
61
+
62
+ The npm allowlist includes the launch card, fast start, knowledge base,
63
+ skill-authoring contract, schema, testing guide, and checked rollout reports.
64
+ Development-only session examples and benchmark scripts remain excluded.
65
+
66
+ The checked `npm pack --dry-run` contains 152 files (approximately 1.07 MB).
67
+ It includes `node/cli-help.js`, the fast start, skill-authoring contract, and this
68
+ audit; it excludes both agent benchmark scripts and the development session
69
+ example.
70
+ An unpacked tarball invocation reported contract version 7 and the same 16
71
+ capabilities as the source CLI.
72
+
73
+ ## Automated verification
74
+
75
+ - `npm test`: 110 passed, 0 failed.
76
+ - `npm run check:types`: 123 runtime exports have declarations.
77
+ - `npm run test:isolation`: dependency and Word-API isolation passed.
78
+ - Focused ESLint for every WP-03 through WP-05 source, test, and benchmark file
79
+ passed.
80
+ - `npm run build` and `npm pack --dry-run`: passed.
81
+
82
+ ## Measurement boundary
83
+
84
+ These are native runtime, serialized-byte, command-count, and correctness
85
+ measurements. They do not estimate Claude/OpenCode tokens, model reasoning time,
86
+ provider transport latency, or end-to-end harness wall time.
@@ -28,7 +28,8 @@ import { applySurgicalMode } from './surgical-mode.js';
28
28
  import { applyReconstructionMode } from './reconstruction-mode.js';
29
29
  import { applyTableReconciliation, applyTextToTableTransformation } from './table-mode.js';
30
30
  import { getDefaultAuthor } from '../adapters/config.js';
31
- import { containsTrackedChanges, getTrackedChangeAuthors, withOoxmlSourceType } from '../core/word-xml.js';
31
+ import { containsTrackedChanges, createWordElement, getTrackedChangeAuthors, withOoxmlSourceType } from '../core/word-xml.js';
32
+ import { clonePropertiesWithoutRevisionHistory } from '../core/revision-cloning.js';
32
33
  import {
33
34
  NS_W,
34
35
  RevisionIdAllocator,
@@ -52,6 +53,62 @@ function getCommentIdsInOoxml(node) {
52
53
  }
53
54
  return [...ids].sort((a, b) => Number(a) - Number(b) || a.localeCompare(b));
54
55
  }
56
+
57
+ function directWordChild(node, localName) {
58
+ return Array.from(node?.childNodes || []).find(child => (
59
+ child?.nodeType === 1
60
+ && child.namespaceURI === NS_W
61
+ && child.localName === localName
62
+ )) || null;
63
+ }
64
+
65
+ function insertedParagraphMarkMetadata(paragraph, author) {
66
+ const marker = directWordChild(directWordChild(directWordChild(paragraph, 'pPr'), 'rPr'), 'ins');
67
+ if (!marker) return null;
68
+ const markerAuthor = marker.getAttribute('w:author') || marker.getAttributeNS(NS_W, 'author') || '';
69
+ if (markerAuthor.trim().toLowerCase() !== String(author || '').trim().toLowerCase()) return null;
70
+ return {
71
+ id: marker.getAttribute('w:id') || marker.getAttributeNS(NS_W, 'id') || '',
72
+ author: markerAuthor,
73
+ date: marker.getAttribute('w:date') || marker.getAttributeNS(NS_W, 'date') || ''
74
+ };
75
+ }
76
+
77
+ function emptyParagraphBaseline(paragraph, serializer) {
78
+ const clone = paragraph.cloneNode(false);
79
+ const pPr = directWordChild(paragraph, 'pPr');
80
+ if (pPr) clone.appendChild(clonePropertiesWithoutRevisionHistory(pPr));
81
+ return serializer.serializeToString(clone);
82
+ }
83
+
84
+ function restoreInsertedParagraphMark(oxml, metadata) {
85
+ if (!metadata || typeof oxml !== 'string' || !oxml.trim()) return oxml;
86
+ const parsed = parseOoxmlSafe(oxml, 'text/xml');
87
+ if (!parsed.doc || parsed.error) return oxml;
88
+ const paragraph = parsed.doc.documentElement?.localName === 'p'
89
+ ? parsed.doc.documentElement
90
+ : getDocumentParagraphs(parsed.doc)[0];
91
+ if (!paragraph) return oxml;
92
+
93
+ let pPr = directWordChild(paragraph, 'pPr');
94
+ if (!pPr) {
95
+ pPr = createWordElement(parsed.doc, 'w:pPr');
96
+ paragraph.insertBefore(pPr, paragraph.firstChild);
97
+ }
98
+ let rPr = directWordChild(pPr, 'rPr');
99
+ if (!rPr) {
100
+ rPr = createWordElement(parsed.doc, 'w:rPr');
101
+ pPr.appendChild(rPr);
102
+ }
103
+ if (!directWordChild(rPr, 'ins')) {
104
+ const marker = createWordElement(parsed.doc, 'w:ins');
105
+ if (metadata.id) marker.setAttribute('w:id', metadata.id);
106
+ marker.setAttribute('w:author', metadata.author);
107
+ if (metadata.date) marker.setAttribute('w:date', metadata.date);
108
+ rPr.appendChild(marker);
109
+ }
110
+ return serializeXml(parsed.doc);
111
+ }
55
112
 
56
113
  /**
57
114
  * Applies redline track changes to OOXML by modifying the DOM in-place.
@@ -77,11 +134,12 @@ export async function applyRedlineToOxml(oxml, originalText, modifiedText, optio
77
134
  const serializer = createSerializer();
78
135
  let parseWarnings = [];
79
136
  const operationWarnings = [];
80
- let normalizedExistingRevisions = false;
137
+ let normalizedExistingRevisions = false;
138
+ let preservedInsertedParagraphMark = null;
81
139
  const existingRevisionsPolicy = options.existingRevisions || 'merge-same-author';
82
140
  const keepNormalizedNoOp = existingRevisionsPolicy === 'accept-all-first-keep-normalized';
83
- const finalize = result => {
84
- const withStatus = { ...result };
141
+ const finalize = result => {
142
+ const withStatus = { ...result };
85
143
  if (normalizedExistingRevisions && withStatus.hasChanges === false && withStatus.status !== 'error') {
86
144
  if (existingRevisionsPolicy === 'merge-same-author' || existingRevisionsPolicy === 'slice-cross-author') {
87
145
  withStatus.oxml = workingOoxml;
@@ -105,10 +163,13 @@ export async function applyRedlineToOxml(oxml, originalText, modifiedText, optio
105
163
  if (warnings.length > 0) {
106
164
  withStatus.warnings = [...new Set(warnings)];
107
165
  }
108
- if (!withStatus.status) {
109
- withStatus.status = withStatus.hasChanges ? 'ok' : 'no-op';
110
- }
111
- return withOoxmlSourceType(withStatus);
166
+ if (!withStatus.status) {
167
+ withStatus.status = withStatus.hasChanges ? 'ok' : 'no-op';
168
+ }
169
+ if (preservedInsertedParagraphMark && withStatus.hasChanges && typeof withStatus.oxml === 'string') {
170
+ withStatus.oxml = restoreInsertedParagraphMark(withStatus.oxml, preservedInsertedParagraphMark);
171
+ }
172
+ return withOoxmlSourceType(withStatus);
112
173
  };
113
174
  const finalizeUnchanged = () => {
114
175
  if (normalizedExistingRevisions && keepNormalizedNoOp) {
@@ -181,9 +242,13 @@ export async function applyRedlineToOxml(oxml, originalText, modifiedText, optio
181
242
  });
182
243
  }
183
244
  log('[OxmlEngine] Existing revisions from same author detected; rejecting previous changes to merge against baseline');
184
- const rejected = rejectTrackedChangesInOoxml(inputOoxml, { author });
185
- if (rejected.status === 'error') return finalize(rejected);
186
- workingOoxml = rejected.oxml;
245
+ const soleParagraph = inputParagraphs.length === 1 ? inputParagraphs[0] : null;
246
+ preservedInsertedParagraphMark = insertedParagraphMarkMetadata(soleParagraph, author);
247
+ const rejected = rejectTrackedChangesInOoxml(inputOoxml, { author });
248
+ if (rejected.status === 'error') return finalize(rejected);
249
+ workingOoxml = preservedInsertedParagraphMark && !String(rejected.oxml || '').trim()
250
+ ? emptyParagraphBaseline(soleParagraph, serializer)
251
+ : rejected.oxml;
187
252
  normalizedExistingRevisions = true;
188
253
  const rejectedParsed = parseOoxmlSafe(workingOoxml, 'text/xml');
189
254
  parseWarnings.push(...rejectedParsed.warnings);
@@ -230,8 +295,10 @@ export async function applyRedlineToOxml(oxml, originalText, modifiedText, optio
230
295
  hasChanges: false,
231
296
  status: 'error',
232
297
  error: {
233
- code: 'EXISTING_REVISIONS',
234
- message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(', ') : 'unattributed'}). Pass existingRevisions: "accept-all-first" or resolve revisions first.`
298
+ code: 'EXISTING_REVISIONS',
299
+ message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(', ') : 'unattributed'}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
300
+ revisionAuthors: authors,
301
+ currentPolicy: existingRevisionsPolicy
235
302
  }
236
303
  });
237
304
  } else {
@@ -9,6 +9,7 @@ import { extractFormatFromRPr, RPR_SCHEMA_ORDER } from './rpr-helpers.js';
9
9
  import { createRevisionMetadata } from '../core/types.js';
10
10
  import { getFirstElementByTag } from '../core/xml-query.js';
11
11
  import { createWordElement } from '../core/word-xml.js';
12
+ import { clonePropertiesWithoutRevisionHistory } from '../core/revision-cloning.js';
12
13
 
13
14
  /**
14
15
  * Creates an insertion/deletion wrapper.
@@ -319,14 +320,9 @@ export function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceN
319
320
  rPrChange.setAttribute('w:author', metadata.author);
320
321
  rPrChange.setAttribute('w:date', dateStr || metadata.date);
321
322
 
322
- const previousRPr = createWordElement(xmlDoc, 'w:rPr');
323
323
  const source = sourceNode || rPr;
324
-
325
- Array.from(source.childNodes).forEach(child => {
326
- if (child.nodeName !== 'w:rPrChange') {
327
- previousRPr.appendChild(child.cloneNode(true));
328
- }
329
- });
324
+ const previousRPr = clonePropertiesWithoutRevisionHistory(source)
325
+ || createWordElement(xmlDoc, 'w:rPr');
330
326
 
331
327
  rPrChange.appendChild(previousRPr);
332
328
 
@@ -369,14 +365,9 @@ export function snapshotAndAttachPPrChange(xmlDoc, pPr, author, dateStr, sourceN
369
365
  pPrChange.setAttribute('w:author', metadata.author);
370
366
  pPrChange.setAttribute('w:date', dateStr || metadata.date);
371
367
 
372
- const previousPPr = createWordElement(xmlDoc, 'w:pPr');
373
368
  const source = sourceNode || pPr;
374
-
375
- Array.from(source.childNodes).forEach(child => {
376
- if (child.nodeType === 1 && child.nodeName !== 'w:pPrChange' && child.localName !== 'pPrChange') {
377
- previousPPr.appendChild(child.cloneNode(true));
378
- }
379
- });
369
+ const previousPPr = clonePropertiesWithoutRevisionHistory(source)
370
+ || createWordElement(xmlDoc, 'w:pPr');
380
371
 
381
372
  pPrChange.appendChild(previousPPr);
382
373
 
@@ -390,4 +381,3 @@ export function snapshotAndAttachPPrChange(xmlDoc, pPr, author, dateStr, sourceN
390
381
  pPr.appendChild(pPrChange);
391
382
  return pPrChange;
392
383
  }
393
-