Skip to content

Commit eb419a7

Browse files
committed
docs: add ROADMAP.md as the tracked source of truth for what is next
The backlog lived in an untracked ISSUES.md that existed only on one machine. It is now ROADMAP.md: current status, the design commitments every future item inherits, an ordered Next list (#73 decomposition of track_changes.py first), demand-gated Later items, the deliberate refusals, and a per-release Shipped list generated from the old Done entries. Item numbers are unchanged; PR titles and docs now cite (ROADMAP.md #N).
1 parent 27f7016 commit eb419a7

4 files changed

Lines changed: 108 additions & 9 deletions

File tree

‎CLAUDE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ iterating.
3636

3737
## Pull Requests
3838

39-
**Title:** `type: what changed in the code (ISSUES.md #N)` — imperative, under ~72
39+
**Title:** `type: what changed in the code (ROADMAP.md #N)` — imperative, under ~72
4040
chars. Describe the change, not where it came from. No round numbers, batch
4141
names, phase names, or "as requested".
4242

43-
- ✅ `docs: document the changeset tier and error fields (ISSUES.md #55)`
43+
- ✅ `docs: document the changeset tier and error fields (ROADMAP.md #55)`
4444
- ❌ `docs: SKILL.md round-2 sync + [create] extra` — "round-2" is our process, not the reader's problem
4545

4646
**Body:** four short sections, ~200 words total. Longer only if the change is

‎ROADMAP.md‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Roadmap
2+
3+
docx-editor lets an LLM agent propose edits to a Word document as tracked changes (redlines) and comments, then lets a human adjudicate them in Word. This file is the single source of truth for what is next, what is deliberately not planned, and what shipped.
4+
5+
Item numbers (`#N`) are stable identifiers, not GitHub issue numbers. PR titles cite them as `type: what changed (ROADMAP.md #N)`. Current release: **0.8.1** (2026-08-30).
6+
7+
## Where the library stands
8+
9+
- **Editing**: `replace`/`delete`/`insert_*`/`rewrite_paragraph` on hash-anchored paragraph refs (`P3#a7b2`), batched atomically with dry-run validation; `\n` in edit text is a tracked paragraph split; a `note=` on any edit anchors a rationale comment on its revisions.
10+
- **Revisions, three tiers**: revision < group (one edit) < changeset (one batch call). Groups and changesets survive save/reopen by reconstruction from author + collision-bumped `w:date`. There is no fourth tier.
11+
- **Honest on foreign documents**: editing inside another author's insertion preserves their proposal; `list_revisions` and every resolve verb handle `w:ins`/`w:del`/`w:moveFrom`/`w:moveTo`/`w:pPrChange`, and never claim success while other revision types remain (`list_unhandled_revisions`, `ResolveResult.unhandled`).
12+
- **Text model**: the paragraph text map covers `w:t` and `w:tab` (one atomic `\t`); text-box content is excluded; only `word/document.xml` is edited.
13+
- **Verification**: ~1,800 tests; a 77-file, 15-producer corpus (24 of them Word-authored redlines) runs weekly in CI through open → edit → save → reopen, a LibreOffice re-save survival check, and a PDF render.
14+
- **Interfaces**: Python API, `docx-session` CLI (persistent Jupyter kernel with a JSON `eval`), and `skills/docx/SKILL.md` as the agent-facing contract.
15+
16+
## Design commitments (constraints every future item inherits)
17+
18+
- Refs are session-scoped: a paragraph's hash may change between sessions and after edits to it; consumers re-find, they never persist refs.
19+
- `\n` means "tracked paragraph split". Any future visible element mapped into the text map must therefore use a character that is not `\n` (see #6).
20+
- Replacing text inside your own pending insertion amends it in place (no new revision, `group_id=None`); undo is rejecting the amended insertion's group.
21+
- The collision-bumped `w:date` per changeset is the join key for return-leg reconciliation (#69). Do not change the stamping scheme.
22+
- Resolve verbs (`accept_all`, `reject_all`, …) report what they could not resolve rather than returning a clean count.
23+
- Write only what LibreOffice and Word both keep on re-save; the corpus gate exists because an unknown element is dropped silently.
24+
25+
## Next
26+
27+
Ordered by value. Each is one board task and one PR unless noted.
28+
29+
### 73. Decompose `track_changes.py` [refactor, no behaviour change]
30+
31+
`docx_editor/track_changes.py` is 5,215 lines; `RevisionManager` alone is 107 methods over 3,739 lines. Every review round still finds real bugs in it, which says the file — not the reviewers — is the problem: each edit site re-derives the same run/insertion/boundary facts, and a fix in one site does not reach its siblings. The method list already clusters cleanly:
32+
33+
| cluster | representative methods | ~lines |
34+
|---|---|---|
35+
| public dataclasses | `EditOperation`, `EditResult`, `SearchResult`, `Revision`, `ResolveResult`, `UnhandledRevision` | 700 |
36+
| group/changeset registry | `_reconstruct_groups`, `_grouped`, `_changeset`, `group_spans`, `groups_are_dead` | 500 |
37+
| locate/search | `find_text`, `find_all`, `count_matches`, `_locate_*`, `_find_across_boundaries*` | 300 |
38+
| batch + rewrite | `batch_edit`, `validate_batch`, `batch_rewrite`, `rewrite_paragraph`, `_rewrite_*` | 550 |
39+
| replace sites | `_replace_same_context`, `_replace_within_own_ins`, `_replace_mixed_state`, `_replace_across_nodes` | 450 |
40+
| delete sites | `_delete_*`, `_remove_from_insertion`, `_split_foreign_ins_at`, `_split_ins_after_child` | 550 |
41+
| insert sites + paragraph split/rejoin | `insert_text_*`, `_insert_*`, `_split_*`, `_collect_tail_nodes`, `_rejoin_paragraph` | 550 |
42+
| listing/parsing | `list_revisions`, `get_markup_text`, `_parse_revision`, `_revision_element_index` | 300 |
43+
| resolution | `accept_*`/`reject_*`, `_resolve_*`, `_restore_*`, `_sweep_*` | 600 |
44+
45+
Plan: turn the module into a package `docx_editor/track_changes/` with one module per cluster; `RevisionManager` stays as the façade so the public API and `from docx_editor.track_changes import …` do not change. Rules: one cluster per PR, each PR a pure move (`git` must show renames, zero logic edits), gates unchanged — the test suite, the walk-count pins, and the corpus harness are the safety net that makes this mechanical. Do this **before** any further edit-site work (#6, #63c); it is what lets a fix land once instead of six times.
46+
47+
### 6. `w:br` in the paragraph text map [second half of #6; `w:tab` shipped in 0.8.1]
48+
49+
A run-level line break is still invisible to search, hashes and edit placement. It cannot map to `\n` (that means paragraph split — see commitments); candidate is a distinct atomic character (e.g. `\u2028`, LINE SEPARATOR) with the same boundary-only edit rules as tabs. Refs of affected paragraphs change, as they did for tabs.
50+
51+
### 63c. Refuse edits that land inside a field result
52+
53+
Only `w:instrText` is known to the library today. A `replace()` targeting text inside a TOC entry, `REF` or page-number field applies, and Word silently discards it on the next field update — the same silent-loss family already eliminated elsewhere. Minimum: detect that a match falls inside `fldSimple`/`fldChar` run ranges and refuse with a teaching error; better: a read-side field inventory. Board task `f812f113` exists in the backlog.
54+
55+
### 72. Flaky on CI: `tests/test_session.py::test_main_full_lifecycle`
56+
57+
`main(["start", …])` returned 1 on one Python 3.12 job (run 33277764141, xdist worker); passed on every other version and on rerun; the PR did not touch `session.py`. Suspect: kernel start-up readiness (`_kernel_alive`, 5 s probe) under CI load. Reproduce under load or raise the start budget, and make a failed `start` print *why*.
58+
59+
### 69. Return-leg reconciliation: which changesets survived? [design]
60+
61+
The product loop is agent proposes → human adjudicates in Word → document comes back; accepted revisions become plain text, rejected ones vanish, and there is no way to ask "which of my changesets survived?". A sent-vs-returned compare keyed on changeset `w:date` + content. Design first; large.
62+
63+
## Later (demand-gated — revisit when a named consumer asks)
64+
65+
- **74. Author moves.** The library never writes `w:moveFrom`/`w:moveTo`; a move through the API is a delete plus an insert (one changeset when done in one batch call), which Word shows as two revisions rather than one green move pair. Emitting a real move pair when a batch deletes text and inserts the identical text elsewhere is now feasible on #68's machinery (range marks, pairing by `w:name`). No consumer has asked.
66+
- **29. Computed list numbers** ("7.2(a)") from `numbering.xml` — a real subsystem; only once list-context demand is proven.
67+
- **30. Container parts** (headers/footers/footnotes/endnotes): multi-part refs (`H1:P2#hash`), per-part editors, tracked-change routing. Stage (a) read-only enumeration, (b) locations, (c) tracked edits. Zero requests across four dogfood rounds; the failure mode is a visible miss, not silent corruption.
68+
- **MCP server** (PR #7, parked): persistent document caching behind an MCP interface; the `docx-session` kernel covers the same need today.
69+
70+
## Out of scope — deliberate refusals
71+
72+
- **Redaction.** Actively dangerous here: a tracked-changes library preserves history by design, so pseudo-redaction that leaves text in `w:del`, prior versions, or metadata is a liability generator. Permanent refusal.
73+
- **Word automation suite**: table/image/style/section authoring, TOC and field authoring, content controls, mail merge, COM/live-Word integration. Cell *text* edits already work, which is the redline case; the rest enlarges the corruption surface of files shared with external parties.
74+
- **Regex find/replace.** The consumer is an LLM that reads text and emits exact strings; regex adds span-vs-revision-context semantics and a footgun surface for zero demonstrated demand.
75+
- **Two-arbitrary-document diff.** The domain version already exists — `list_revisions` markup view, `get_text`/`get_original_text`, rewrite diffing. The valuable cousin is #69.
76+
- **Read-only schema validators.** The real contract is "opens in Word with zero repair prompts"; a schema check cannot predict that. The corpus gate is the honest version.
77+
- **Text boxes as an editing surface** — only the exclusion fix (#65) shipped; box text is invisible, not half-editable.
78+
79+
## How this roadmap is set
80+
81+
Batches come from evidence, not ideas: four dogfooding rounds (consumer-persona agents driving the library from `SKILL.md` alone, adversarial error QA, token economics, scale), the corpus revision census (which types real producers emit), and a clean-room comparison against KitchenSink4Word (test *names* only — its code is PolyForm-NC and unusable). A finding becomes an item here, an item becomes a PR citing it, and a release closes a theme.
82+
83+
## Shipped
84+
85+
Newest first. Details live in each PR and in the release notes.
86+
87+
- **0.8.1** (2026-08-30): #68 Resolve moves and `w:pPrChange` as revisions ([#82](https://github.com/pablospe/docx-editor/pull/82)); #6a `w:tab` in the paragraph text map ([#81](https://github.com/pablospe/docx-editor/pull/81)); #71 LibreOffice opens-clean gate + real Word redline fixtures ([#80](https://github.com/pablospe/docx-editor/pull/80))
88+
- **0.8.0** (2026-08-29): #65 Exclude w:txbxContent from the host paragraph text map ([#78](https://github.com/pablospe/docx-editor/pull/78)); #67 Rationale channel ([#79](https://github.com/pablospe/docx-editor/pull/79)); #66 + #70 settings.xml pair + anti-scope statement ([#77](https://github.com/pablospe/docx-editor/pull/77)); #64 Foreign-revision census + accept_all honesty floor ([#76](https://github.com/pablospe/docx-editor/pull/76))
89+
- **0.7.2** (2026-08-28): #56+#62 Perf follow-ups + test hygiene ([#75](https://github.com/pablospe/docx-editor/pull/75)); #39 (also filed as #31) Site D own-insertion replace ordering ([#74](https://github.com/pablospe/docx-editor/pull/74)); #52+#60 Ergonomics grab-bag ([#73](https://github.com/pablospe/docx-editor/pull/73))
90+
- **0.7.1** (2026-07-24): #59 SKILL round-4 notes ([#70](https://github.com/pablospe/docx-editor/pull/70)); #57 Accept-path performance ([#71](https://github.com/pablospe/docx-editor/pull/71)); #58+61 `\n` = tracked paragraph split ([#72](https://github.com/pablospe/docx-editor/pull/72))
91+
- **0.7.0** (2026-07-23): #50 Session eval JSON + structured errors ([#62](https://github.com/pablospe/docx-editor/pull/62)); #48 Error-contract round 3 ([#63](https://github.com/pablospe/docx-editor/pull/63)); #46 Revision groups survive reopen ([#61](https://github.com/pablospe/docx-editor/pull/61)); #49 SKILL/docs round-3 sync ([#64](https://github.com/pablospe/docx-editor/pull/64)); #53 Collision-bumped w:date stamping ([#65](https://github.com/pablospe/docx-editor/pull/65)); #47 Formatting preservation + span trimming ([#66](https://github.com/pablospe/docx-editor/pull/66)); #51 batch_edit apply performance ([#67](https://github.com/pablospe/docx-editor/pull/67)); #54 Changeset tier ([#68](https://github.com/pablospe/docx-editor/pull/68)); #55 Docs/skills full audit + sync ([#69](https://github.com/pablospe/docx-editor/pull/69))
92+
- **0.6.1** (2026-07-22): #44 SKILL.md round-2 sync + [create] extra ([#56](https://github.com/pablospe/docx-editor/pull/56)); #41 Open-path robustness ([#55](https://github.com/pablospe/docx-editor/pull/55)); #45 Session CLI ergonomics ([#58](https://github.com/pablospe/docx-editor/pull/58)); #40 Input-validation bugs ([#57](https://github.com/pablospe/docx-editor/pull/57)); #42 Error contract completion ([#59](https://github.com/pablospe/docx-editor/pull/59)); #43 LLM token ergonomics ([#60](https://github.com/pablospe/docx-editor/pull/60))
93+
- **0.6.0** (2026-07-15): #31 rPr leak + render_wt dedup ([#47](https://github.com/pablospe/docx-editor/pull/47)); #36 Style-chain numbering ([#51](https://github.com/pablospe/docx-editor/pull/51)); #32 SKILL.md sync ([#48](https://github.com/pablospe/docx-editor/pull/48)); #38 Corpus harness ([#50](https://github.com/pablospe/docx-editor/pull/50)); #33 Revision location ([#52](https://github.com/pablospe/docx-editor/pull/52)); #35 Exception contract ([#49](https://github.com/pablospe/docx-editor/pull/49)); #34 find_all + occurrence ergonomics ([#53](https://github.com/pablospe/docx-editor/pull/53)); #37 Revision grouping ([#54](https://github.com/pablospe/docx-editor/pull/54)); #31 rPr correctness + render_wt dedup ([#47](https://github.com/pablospe/docx-editor/pull/47))
94+
- **0.5.0** (2026-07-14): #18 UTF-8 unpack ([#38](https://github.com/pablospe/docx-editor/pull/38)); #26 List info on ParagraphLocation ([#41](https://github.com/pablospe/docx-editor/pull/41)); #27 Style/outline context on ParagraphLocation ([#42](https://github.com/pablospe/docx-editor/pull/42)); #22-24 Workspace hardening ([#44](https://github.com/pablospe/docx-editor/pull/44)); #28 Section index on ParagraphLocation ([#45](https://github.com/pablospe/docx-editor/pull/45)); #19 Author-aware in-place edits ([#43](https://github.com/pablospe/docx-editor/pull/43)); #20 Run-order preservation ([#46](https://github.com/pablospe/docx-editor/pull/46)); #25 + #7 API hygiene ([#40](https://github.com/pablospe/docx-editor/pull/40)); #21 Occurrence search unification ([#39](https://github.com/pablospe/docx-editor/pull/39)); #21 Occurrence drift in document-wide search ([#39](https://github.com/pablospe/docx-editor/pull/39)); #25 Typed EditOperation constructors ([#40](https://github.com/pablospe/docx-editor/pull/40)); #7 Internal types exported as public API ([#40](https://github.com/pablospe/docx-editor/pull/40)); #22 meta.json written non-atomically ([#44](https://github.com/pablospe/docx-editor/pull/44)); #23 mark_dirty() write-ahead contract violations ([#44](https://github.com/pablospe/docx-editor/pull/44)); #24 No cross-process protection ([#44](https://github.com/pablospe/docx-editor/pull/44)); #19 Editing inside another reviewer's insertion destroys their proposal ([#43](https://github.com/pablospe/docx-editor/pull/43))
95+
- **0.4.0** (2026-07-14): #12 Workspace → cache directory ([#29](https://github.com/pablospe/docx-editor/pull/29)); #13 Foreign-revision test fixtures ([#32](https://github.com/pablospe/docx-editor/pull/32)); #14 Original text view ([#35](https://github.com/pablospe/docx-editor/pull/35)); #15 Fixed-point accept_all/reject_all ([#36](https://github.com/pablospe/docx-editor/pull/36)); #16 w:delText → w:t fallback ([#34](https://github.com/pablospe/docx-editor/pull/34)); #17 Stale workspace divergence ([#37](https://github.com/pablospe/docx-editor/pull/37))
96+
- **0.3.2** (2026-07-02): #8 Structured paragraph output ([#26](https://github.com/pablospe/docx-editor/pull/26)); #9 Single-paragraph lookup ([#27](https://github.com/pablospe/docx-editor/pull/27)); #10 Dry-run validation ([#28](https://github.com/pablospe/docx-editor/pull/28))
97+
- **0.3.1** (2026-06-08): #2 batch_edit() rollback ([#19](https://github.com/pablospe/docx-editor/pull/19)); #3 ZIP path traversal / symlink ([#20](https://github.com/pablospe/docx-editor/pull/20))
98+
- **0.2.4** (2026-05-18): #5 Comment anchoring bug ([#21](https://github.com/pablospe/docx-editor/pull/21))
99+
- **earlier**: #1 Stale workspace handling

0 commit comments

Comments
 (0)