Skip to content

feat: add rewrite_paragraph and return paragraph refs from all edit methods - #11

Merged
pablospe merged 21 commits into
mainfrom
user/pablo/rewrites
Apr 4, 2026
Merged

pablospe merged 21 commits into
mainfrom
user/pablo/rewrites

Conversation

@pablospe

@pablospe pablospe commented Apr 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add rewrite_paragraph(ref, new_text) — specify desired paragraph text, auto-generates fine-grained tracked changes via word-level diffing
  • Add batch_rewrite(rewrites) — multi-paragraph rewrite with upfront hash validation
  • All edit methods now return the new paragraph ref as a plain str — enables chaining edits without calling list_paragraphs() between them
  • Position-aware matching to correctly handle paragraphs with duplicate text
  • Formatting preservation: inserted text inherits run properties from adjacent runs

Return value change

All edit methods (replace, delete, insert_after, insert_before, batch_edit) now return the new paragraph reference (str) instead of a change ID (int). This enables:

r1 = doc.replace("30 days", "60 days", paragraph="P2#f3c1")
r2 = doc.replace("net", "gross", paragraph=r1)  # no list_paragraphs() needed

Change IDs for accept/reject are available via doc.list_revisions().

When to use rewrite vs surgical methods

Default: always use surgical methods (replace, delete, insert_after, insert_before, batch_edit).

Use rewrite_paragraph() only when the edit cannot be decomposed into independent find-replace pairs:

  • Sentence restructuring (e.g., passive to active voice)
  • Reordering words, items, or clauses
  • Intertwined changes that overlap or depend on each other

Test plan

  • 463 tests pass (36 new in test_rewrite_paragraph.py)
  • Stress tests: passive-to-active voice, list reordering, full rephrasing, prose tightening, duplicate phrases
  • LLM workflow validation: 10/10 subagent scenarios for str return API
  • LLM decision criteria: 10/10 subagent scenarios for rewrite vs surgical classification

@coderabbitai

coderabbitai Bot commented Apr 2, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Document.rewrite_paragraph() and Document.batch_rewrite() plus RevisionManager implementations that compute word-level diffs (tokenize + SequenceMatcher), map diffs to XML character positions via TextMap, and emit fine-grained Word tracked-change edits (<w:del>/<w:ins>) applied in reverse order; includes docs and tests.

Changes

Cohort / File(s) Summary
Public API Methods
docx_editor/document.py
Added rewrite_paragraph(ref: str, new_text: str) and batch_rewrite(rewrites: list[tuple[str,str]]); adjusted list_paragraphs(max_chars) to clamp max_chars >= 10. Methods call _ensure_open() and delegate to RevisionManager.
Core Rewrite Implementation
docx_editor/track_changes.py
Added RevisionManager.rewrite_paragraph() and batch_rewrite() implementing word/whitespace tokenization, difflib.SequenceMatcher diffs, mapping opcodes to character-offset hunks via TextMap, and applying edits. New helpers: _tokenize_words(), _find_match_at_position(), _rewrite_insert_at(); insertion handles empty paragraphs, end-of-paragraph, splicing into <w:ins>, and run-splitting; deletes/replaces require positional match. Batch validates paragraph hashes up-front, rejects duplicates, and applies rewrites in descending paragraph index order. Imports: difflib, re, TextMap.
Design & Specification
openspec/changes/add-rewrite-paragraph/...
Added design, proposal, spec, and task docs detailing tokenization, diff→XML mapping, formatting inheritance, edge cases (empty paragraphs, rewrite-to-empty, existing tracked changes), and batch semantics.
User-Facing Documentation
README.md, skills/docx/SKILL.md
README Quick Start updated to demonstrate rewrite_paragraph(); skill doc adds guidance on when to prefer surgical edits vs rewrite_paragraph() and shows batch_rewrite() usage; list_paragraphs tip updated.
Tests
tests/test_rewrite_paragraph.py
New comprehensive tests covering tokenizer, single/multi rewrite behaviors (insert-only, delete-only, replace), no-op behavior, stale-hash rejection, formatting preservation (run properties), duplicate-occurrence targeting, persistence across saves/reopens, and batch_rewrite() atomic semantics.
README only
README.md
Replaced prior example set with a rewrite_paragraph() example and noted word-level diffing produces fine-grained tracked changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Doc as Document
    participant RM as RevisionManager
    participant TM as TextMap
    participant Tok as Tokenizer
    participant SM as SequenceMatcher
    participant XML as XMLEditor

    Client->>Doc: rewrite_paragraph(ref, new_text)
    Doc->>RM: rewrite_paragraph(ref, new_text)
    RM->>RM: _resolve_paragraph(ref) / validate hash
    RM->>TM: build_text_map(paragraph)
    TM-->>RM: text_map
    RM->>Tok: _tokenize_words(old_text)
    RM->>Tok: _tokenize_words(new_text)
    Tok-->>RM: old_tokens / new_tokens
    RM->>SM: compute opcodes(old_tokens, new_tokens)
    SM-->>RM: opcodes
    loop apply hunks (in reverse)
        alt delete / replace
            RM->>RM: _find_match_at_position(text_map, search, pos)
            RM->>XML: apply <w:del> / replace across runs
        end
        alt insert / replace
            RM->>RM: _rewrite_insert_at(paragraph, text_map, char_pos, text)
            RM->>XML: create/splice <w:ins>, split runs, inherit w:rPr
        end
    end
    RM-->>Doc: mutated document (tracked changes)
    Doc-->>Client: return
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through tokens, nibbling words old and new,
I wrap small edits in <w:del> and <w:ins> too,
I split and splice runs, keep your bolds in place,
A rabbit's soft diff leaves tracked changes with grace!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title mentions 'rewrite_paragraph' and batch editing, which are primary features in the changeset, but omits the 'max_chars=10' constraint update which is also explicitly part of the commit objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch user/pablo/rewrites

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Apr 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.71523% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.6%. Comparing base (8a38411) to head (02ce217).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
docx_editor/track_changes.py 90.9% 4 Missing and 7 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            main     #11     +/-   ##
=======================================
- Coverage   93.7%   93.6%   -0.2%     
=======================================
  Files         10      10             
  Lines       1993    2133    +140     
  Branches     458     485     +27     
=======================================
+ Hits        1869    1998    +129     
- Misses        31      35      +4     
- Partials      93     100      +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

pablospe added 6 commits April 2, 2026 14:17
Add rewrite_paragraph(ref, new_text) to RevisionManager and Document
that automatically diffs old vs new text at word level and generates
fine-grained tracked changes (insertions, deletions, replacements).
Accepts a list of (ref, new_text) pairs and applies them atomically:
- Validates all paragraph hashes upfront (rejects entire batch on mismatch)
- Rejects duplicate paragraph references
- Applies rewrites in reverse paragraph order (highest index first)
- Delegates each individual rewrite to rewrite_paragraph()
Add TestRewriteWithTrackedChanges class covering:
- Rewrite after prior surgical edit (existing tracked changes)
- Save and reopen round-trip preserving revisions
- Individual rewrite after batch rewrite
Add paragraph rewrite section to SKILL.md with decision table,
concrete examples showing when rewrite is better than surgical edits,
and batch_rewrite usage. Update README.md features and quick start.
Proposal for rewrite_paragraph() and batch_rewrite() with word-level
diffing, design decisions, and spec deltas for text-operations.
@pablospe
pablospe force-pushed the user/pablo/rewrites branch from 6966190 to 2a6303a Compare April 2, 2026 12:17
@pablospe
pablospe changed the base branch from user/pablo/hashes to main April 2, 2026 12:18
pablospe added 4 commits April 2, 2026 19:24
- Use _find_match_at_position() to match text at expected character
  positions instead of always finding first occurrence. Fixes incorrect
  edits when the same text appears multiple times in a paragraph.
- Raise RevisionError when a hunk's text cannot be found after DOM
  mutation, instead of silently skipping.
- Add tests for duplicate text scenarios.
Replaces vague "3+ changes" heuristic with a mechanical decision rule:
"Can each change be expressed as an independent find→replace?" If yes,
use surgical methods. If no (restructuring, reordering, intertwined
changes), use rewrite_paragraph.

Tested with 10 LLM scenarios (5 clear-cut + 5 ambiguous edge cases),
all correctly classified.
7 new tests covering real-world rewrite_paragraph use cases:
- passive→active voice conversion
- list item reordering
- full sentence rephrasing
- prose tightening (multiple deletions)
- duplicate phrases with different replacements per occurrence
- clause insertion combined with value change
- duplicate word with different substitutions
- Remove redundant _find_ancestor call in _rewrite_insert_at (always
  None after early return on the truthy branch)
- Make test_formatting_preserved assert unconditionally that rPr exists
  on w:ins elements, instead of silently skipping when absent

Skipped: naming inconsistency (ref vs ref_str) — consistent within new
methods; multi-w:t sibling handling — pre-existing pattern limitation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docx_editor/track_changes.py`:
- Around line 377-391: The current split logic replaces the entire <w:r> and
loses sibling <w:t> elements; update the block that builds new_xml to iterate
run.getElementsByTagName("w:t") and for each wt: if wt is pos.node, split its
text into before_text/after_text and append preserved <w:t> for before_text,
then the <w:ins><w:r>...<w:t>text</w:t></w:r></w:ins>, then preserved <w:t> for
after_text (using rPr_xml and _escape_xml), else append the sibling wt unchanged
(serialized) so all non-target <w:t> siblings are preserved before calling
self.editor.replace_node(run, new_xml); use the same variables (run, pos.node,
rPr_xml, text, _escape_xml, self.editor.replace_node) to locate and implement
the change.
- Around line 356-359: The duplicated ancestor lookup ins_ancestor_ref =
self._find_ancestor(run, "w:ins") is redundant because an earlier ins_ancestor =
self._find_ancestor(run, "w:ins") already returns early if truthy; remove the
second call and the ins_ancestor_ref variable and always use run as the insert
reference when calling self.editor.insert_after(run, ins_xml). Update the
surrounding code in the same block that builds ins_xml so it no longer declares
or uses ins_ancestor_ref and simply calls self.editor.insert_after(run,
ins_xml).
- Around line 344-346: The early silent return after calling
self._get_run_info(last_pos.node) should be replaced with raising RevisionError
to avoid masking DOM corruption or unexpected document structure; update the
method containing this snippet so that if _get_run_info(...) returns a falsy
run, it raises RevisionError with a clear message (include context like
last_pos.node or the failing node) instead of returning, keeping behavior
consistent with other failure modes in this class.

In `@openspec/changes/add-rewrite-paragraph/design.md`:
- Line 1: Add a top-level H1 heading at the very start of the design document
and leave the existing sections (like the "## Context" heading) unchanged
beneath it; specifically, insert a single-line H1 (e.g., "# Design: Add Rewrite
Paragraph" or another descriptive title) as the first line of
openspec/changes/add-rewrite-paragraph/design.md so the file begins with H1 and
the current "## Context" remains the first subsection.

In `@openspec/changes/add-rewrite-paragraph/tasks.md`:
- Line 1: The file's first line uses a level-2 heading ("## 1. Core Diffing
Infrastructure") which violates markdownlint MD041; add a top-level H1 title
line above that section (e.g., "# ...") so the document begins with an H1 before
"## 1. Core Diffing Infrastructure" to satisfy the rule and keep existing
headings intact.

In `@skills/docx/SKILL.md`:
- Around line 433-437: The example calling batch_edit() uses plain dicts instead
of EditOperation objects; update the example to construct EditOperation
instances for each edit (use the EditOperation type expected by batch_edit), set
action="replace", move "replace"->"replace_with", keep "find" and "paragraph"
fields, and pass the list of EditOperation objects to batch_edit() so the
function receives the proper shape (refer to batch_edit and EditOperation, and
the required fields action, find, replace_with, paragraph).

In `@tests/test_rewrite_paragraph.py`:
- Line 615: The test assigns an unused second value from the fixture return
(tmp) when calling rewrite_doc; remove the unused binding by only capturing the
value you need (doc) or using the conventional underscore placeholder for the
second value so Ruff RUF059 is no longer triggered; update the call site where
rewrite_doc is unpacked (the assignment to doc, tmp) accordingly.
- Around line 265-270: The current test only asserts that if an insertion
element ("w:ins") has a "w:rPr" child then that "w:rPr" contains a "w:b", which
can pass even if no inserted run actually has bold; change the check to require
that at least one inserted run under editor.dom.getElementsByTagName("w:ins")
contains a "w:rPr" element with a "w:b" child. Concretely, iterate inserted runs
inside each "w:ins" (e.g., find "w:r" or direct children of the "w:ins"),
collect whether any run's "w:rPr" contains "w:b", and assert that this
collection has at least one true entry so the test fails when no inserted run
inherits bold formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e0bc8cf2-898e-4793-a802-5c3862f9b94d

📥 Commits

Reviewing files that changed from the base of the PR and between 8a38411 and fb2c2bb.

📒 Files selected for processing (9)
  • README.md
  • docx_editor/document.py
  • docx_editor/track_changes.py
  • openspec/changes/add-rewrite-paragraph/design.md
  • openspec/changes/add-rewrite-paragraph/proposal.md
  • openspec/changes/add-rewrite-paragraph/specs/text-operations/spec.md
  • openspec/changes/add-rewrite-paragraph/tasks.md
  • skills/docx/SKILL.md
  • tests/test_rewrite_paragraph.py

Comment thread docx_editor/track_changes.py
Comment thread docx_editor/track_changes.py Outdated
Comment thread docx_editor/track_changes.py
Comment thread openspec/changes/add-rewrite-paragraph/design.md
Comment thread openspec/changes/add-rewrite-paragraph/tasks.md
Comment thread skills/docx/SKILL.md
Comment thread tests/test_rewrite_paragraph.py
Comment thread tests/test_rewrite_paragraph.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docx_editor/track_changes.py`:
- Around line 362-364: The code currently returns silently when
_get_run_info(pos.node) yields run is None (seen where run, rPr_xml =
self._get_run_info(pos.node); if not run: return), which can hide DOM problems;
instead replace the silent return with a clear failure path: either log an error
including pos.node and rPr_xml and raise a descriptive exception (e.g.,
ValueError or custom DOMParseError) or raise immediately with a message
referencing _get_run_info and pos.node so callers cannot continue on invalid
state; ensure the change is applied in the mid-text insertion method that
contains this snippet so callers get explicit failure instead of silent no-op.

In `@tests/test_rewrite_paragraph.py`:
- Line 354: Replace the Yoda condition in the assertion so the variable
expression comes first: change the equality check in the assert that iterates
over ins_texts (currently using "a" == t.strip()) to use t.strip() == "a"
instead; update the assertion that uses any("a" == t.strip() for t in ins_texts)
to any(t.strip() == "a" for t in ins_texts) to improve readability.
- Line 357: The helper function _make_doc_with_paragraphs is missing an explicit
return type; update its signature to include a concrete return annotation (for
example the actual Document type used in the codebase or typing.Any if unsure)
to improve IDE support and docs, and add any necessary import (e.g., from typing
import Any or the Document type) so the annotated return type resolves
correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d5630576-7420-4796-9c6a-3860813068fc

📥 Commits

Reviewing files that changed from the base of the PR and between fb2c2bb and 39cf603.

📒 Files selected for processing (2)
  • docx_editor/track_changes.py
  • tests/test_rewrite_paragraph.py

Comment thread docx_editor/track_changes.py
Comment thread tests/test_rewrite_paragraph.py Outdated
Comment thread tests/test_rewrite_paragraph.py
- SKILL.md: batch_edit example used plain dicts instead of
  EditOperation objects — would cause runtime errors
- Fix Yoda condition in test assertion

Skipped from review: silent returns (pre-existing pattern), sibling
w:t loss (pre-existing limitation), markdown lint (OpenSpec convention).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
tests/test_rewrite_paragraph.py (2)

357-357: 🧹 Nitpick | 🔵 Trivial

Add a return type annotation to _make_doc_with_paragraphs.

Line 357 is missing an explicit return annotation, which keeps ANN202 noisy.

🧹 Suggested fix
-def _make_doc_with_paragraphs(paragraphs: list[str]):
+def _make_doc_with_paragraphs(paragraphs: list[str]) -> tuple[Document, Path]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rewrite_paragraph.py` at line 357, The helper function
_make_doc_with_paragraphs lacks an explicit return type annotation; update its
signature to include a concrete return type (e.g., -> Document or the actual
type the function constructs) so ANN202 is satisfied. Locate the
_make_doc_with_paragraphs definition and add the correct return type annotation
matching the object it returns (or use typing.Any if uncertain), and run
tests/typechecks to confirm the annotation is correct.

615-615: ⚠️ Potential issue | 🟡 Minor

Use _ for the unused fixture value.

tmp is unpacked but unused on Line 615.

🧹 Suggested fix
-        doc, tmp = rewrite_doc
+        doc, _ = rewrite_doc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rewrite_paragraph.py` at line 615, The tuple unpacking assigns an
unused fixture value to tmp in the test (doc, tmp = rewrite_doc); change tmp to
the conventional unused variable name _ by updating the unpacking to (doc, _)
wherever rewrite_doc is unpacked in tests (e.g., in
tests/test_rewrite_paragraph.py) so the intent is clear and linters won’t flag
the unused variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/docx/SKILL.md`:
- Around line 459-460: The batch_rewrite example uses the variable author when
calling Document.open but never defines it; add a preceding definition of author
(e.g., assign a sample string or pull from function args/config) before the
Document.open(...) call so the example is runnable. Update the example around
Document.open("contract.docx", author=author) and refs = doc.list_paragraphs()
to include a clear author assignment (or note it comes from the surrounding
scope) so readers can copy-paste and run the snippet.

---

Duplicate comments:
In `@tests/test_rewrite_paragraph.py`:
- Line 357: The helper function _make_doc_with_paragraphs lacks an explicit
return type annotation; update its signature to include a concrete return type
(e.g., -> Document or the actual type the function constructs) so ANN202 is
satisfied. Locate the _make_doc_with_paragraphs definition and add the correct
return type annotation matching the object it returns (or use typing.Any if
uncertain), and run tests/typechecks to confirm the annotation is correct.
- Line 615: The tuple unpacking assigns an unused fixture value to tmp in the
test (doc, tmp = rewrite_doc); change tmp to the conventional unused variable
name _ by updating the unpacking to (doc, _) wherever rewrite_doc is unpacked in
tests (e.g., in tests/test_rewrite_paragraph.py) so the intent is clear and
linters won’t flag the unused variable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1bae3a8f-2717-486e-ab4e-2c39b51fd50a

📥 Commits

Reviewing files that changed from the base of the PR and between 39cf603 and 1fc8385.

📒 Files selected for processing (2)
  • skills/docx/SKILL.md
  • tests/test_rewrite_paragraph.py

Comment thread skills/docx/SKILL.md
After batch edits, LLMs only need fresh hashes — not full previews.
list_paragraphs(max_chars=20) reduces output tokens when the LLM
already knows which paragraphs it edited.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
skills/docx/SKILL.md (1)

466-473: ⚠️ Potential issue | 🟡 Minor

Define author variable before using it in the example.

The code block uses author=author on Line 466 without defining the author variable, which will cause a NameError if users copy-paste this example.

This issue was previously flagged but remains unfixed.

🛠️ Suggested fix
+import os
+author = os.environ.get("USER") or os.environ.get("USERNAME") or "Reviewer"
+
 with Document.open("contract.docx", author=author) as doc:
     refs = doc.list_paragraphs()
     doc.batch_rewrite([
         (refs[1].split("|")[0], "Rephrased paragraph 2 text here."),
         (refs[4].split("|")[0], "Restructured paragraph 5 text here."),
     ])
     doc.save()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/docx/SKILL.md` around lines 466 - 473, The example uses
Document.open(..., author=author) without defining author, causing a NameError;
add a prior definition for the author variable (e.g., a string username or
Author-type expected by the SDK) before calling Document.open in the example so
refs = doc.list_paragraphs(), doc.batch_rewrite(...) and doc.save() run
correctly; update the sample to declare author (referencing the example's use of
Document.open, list_paragraphs, batch_rewrite, save) and ensure its value
matches the API's expected type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/docx/SKILL.md`:
- Around line 414-420: The wording "faster hash refresh" is misleading because
list_paragraphs(max_chars=...) only truncates returned preview text and does not
change how hashes are computed; update the SKILL.md text to state that max_chars
reduces the size of returned preview output (and thus output token usage) but
does not affect hash computation, and reference the actual
functions/implementation: list_paragraphs(max_chars=...) controls preview length
while compute_paragraph_hash(p) (used by the paragraph-hashing code path in
document.py) always computes the hash from the paragraph DOM regardless of
max_chars.

---

Duplicate comments:
In `@skills/docx/SKILL.md`:
- Around line 466-473: The example uses Document.open(..., author=author)
without defining author, causing a NameError; add a prior definition for the
author variable (e.g., a string username or Author-type expected by the SDK)
before calling Document.open in the example so refs = doc.list_paragraphs(),
doc.batch_rewrite(...) and doc.save() run correctly; update the sample to
declare author (referencing the example's use of Document.open, list_paragraphs,
batch_rewrite, save) and ensure its value matches the API's expected type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 726e0137-7583-4f27-9dcc-a4ee504848fd

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc8385 and 02dd2da.

📒 Files selected for processing (1)
  • skills/docx/SKILL.md

Comment thread skills/docx/SKILL.md Outdated
pablospe added 2 commits April 3, 2026 08:16
Prevents LLMs from passing absurdly low values like max_chars=1
which would produce useless single-character previews.
Reverts minimum enforcement. max_chars=0 gives a clean "just hashes"
mode for refreshing refs after edits without wasting tokens on previews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docx_editor/document.py`:
- Around line 312-329: The docstring for Document.batch_rewrite claims atomic
behavior but RevisionManager.batch_rewrite applies edits one-by-one and lacks
rollback; either implement true transaction semantics in
RevisionManager.batch_rewrite or remove the atomic claim—preferably implement
rollback by first validating all upfront paragraph hashes (use the same
validation logic currently used per-edit), then apply edits to a copy or staged
buffer and only commit the changes to the live document if all edits succeed (or
on failure discard the copy/staged changes and leave the document unchanged);
update or keep references to Document.batch_rewrite and
RevisionManager.batch_rewrite when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8df95df7-7eb0-435e-8273-972dec113701

📥 Commits

Reviewing files that changed from the base of the PR and between 02dd2da and 6000af3.

📒 Files selected for processing (1)
  • docx_editor/document.py

Comment thread docx_editor/document.py Outdated
pablospe added 5 commits April 3, 2026 12:20
All edit methods (replace, delete, insert_after, insert_before,
rewrite_paragraph, batch_edit, batch_rewrite) now return the new
paragraph reference after mutation. This eliminates the need to call
list_paragraphs() between sequential edits on the same paragraph.

EditResult subclasses int for backwards compatibility — existing code
using the return value as a change ID for accept/reject still works.
New code uses .ref for the fresh paragraph hash.

Adds 9 tests covering ref chaining, backwards compat, and batch refs.
Updates SKILL.md with ref chaining examples.
All edit methods (replace, delete, insert_after, insert_before,
batch_edit) now return the new paragraph reference as a plain string,
matching rewrite_paragraph and batch_rewrite. The EditResult class
is removed entirely — change IDs are available via list_revisions().
Show chaining pattern throughout, document list_revisions() for
getting change IDs, remove stale "returns int change ID" text.
- Remove misleading "atomically" from batch_rewrite docstrings — upfront
  hash validation prevents stale edits, but there's no rollback on failure
- Add author definition to batch_rewrite SKILL.md example for self-contained usage
- Fix ruff import sorting and line length violations
- Fix unused variable r3 in test
- Show ref chaining in README Quick Start
- Fix author inconsistency in Quick Start example
@pablospe pablospe changed the title feat: add rewrite_paragraph with word-level diffing feat: add rewrite_paragraph and return paragraph refs from all edit methods Apr 3, 2026
Cover empty paragraph, insert-at-end, insert inside w:ins, sectPr,
and formatted empty paragraph paths. Raises patch coverage from
76% to ~85%.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant