Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ These invariants are NOT SUGGESTIONS. These MUST be followed.

**Locator flags are unified:** address an existing thing with **`--at LOCATOR`** everywhere (edit, insert, delete, tables, comments, footnotes/endnotes, images, hyperlinks, track-changes; `headers`/`footers` take `--at sN` to scope to one section, defaulting to the whole document). For `insert`, `--at` means AFTER an ordinary block and INTO a bare cell; `--before`/`--after` choose an explicit block side or a cell's start/end boundary, while `--at-start`/`--at-end` address document boundaries (no locator, single-shot only, not batchable). A bare-cell insert fills/reuses Word's mandatory empty paragraph and otherwise appends; bare-cell edit aliases exactly one direct paragraph. Both reject merged/grid-shifted cells, and edit rejects multi-block cells — use `CELL:pK` for precision. `read` uses `--from`/`--to` (a block slice), `comments add` also takes `--anchor PHRASE`, and `wc` takes a positional `[LOCATOR]`. No more `--id`/`--range`/`--to` for addressing.

**`--batch FILE.jsonl` (one JSONL object per line; `-` = stdin)** uses the shared `readJsonlObjects` reader (`cli/parse-helpers.ts`), with each command's path in a sibling `batch.ts`. `find --batch` is the read-only form: independent `{ query, … }` / formatting-filter entries all run against one immutable document read; text output flattens locators in entry order, while `--json` preserves request boundaries in a `batch` array. The mutation form lets `edit`, `insert`, `replace`, `delete`, and the `comments` verbs apply many changes from a single read — keys mirror the command's flags (`delete` entries are just `{ at }`). **All locators in a mutation batch address the document AS READ** — the load-bearing invariant — and each command keeps that true differently: `edit` resolves every entry to a live `XmlNode` ref _before any mutation_ (same-paragraph spans apply right-to-left; a paragraph takes one whole-paragraph edit OR several non-overlapping spans OR one line removal — empty `text` / `delete:true`, routed to the cell-safe `removeParagraphLine` so a form-fill fills cells AND drops leftover placeholder lines in one sweep); `replace` re-reads the live tree between entries (`document.reread()` re-walks `documentTree`, no re-parse, so node identity holds) for sed-like sequential semantics; `insert` pins block/cell anchors to live refs, builds all blocks with zero body mutation, reserves an initially empty cell's mandatory paragraph for its first bare-cell entry, rejects mixed bare-cell + explicit `CELL:pK` targeting in one batch, then splices with a per-anchor offset for block targets and one `CellInsertionCursor` (in `@core/table`) per cell, so stacked inserts keep entry order; `delete` resolves every locator to a live ref before any removal, then splices each by its live `indexOf` (so sibling shifts never misfire); `comments reply` validates every parent `cN` before minting any reply (reply ids append, so parents never shift), and an entry's `"resolve": true` resolves its thread root after all replies land — a finalize pass is `track-changes apply` + ONE `comments reply --batch`. Review work is where weak agents most often go one-call-per-change (the contract scenarios), so the `track-changes`/`comments` noun helps lead with the batched review recipe. Range/span `edit`s and `delete`s aren't batchable (do them one at a time); section (`sN`) and equation (`eqN`) targets aren't `edit`'s surface at all — use `docx sections` / `docx equations edit` (and `delete --at sN` for a section break).
**`--batch FILE.jsonl` (one JSONL object per line; `-` = stdin)** uses the shared `readJsonlObjects` reader (`cli/parse-helpers.ts`), with each command's path in a sibling `batch.ts`. `find --batch` is the read-only form: independent `{ query, … }` / formatting-filter entries all run against one immutable document read; text output flattens locators in entry order, while `--json` preserves request boundaries in a `batch` array. The mutation form lets `edit`, `insert`, `replace`, `delete`, `move`, and the `comments` verbs apply many changes from a single read — keys mirror the command's flags (`delete` entries are just `{ at }`). **All locators in a mutation batch address the document AS READ** — the load-bearing invariant — and each command keeps that true differently: `edit` resolves every entry to a live `XmlNode` ref _before any mutation_ (same-paragraph spans apply right-to-left; a paragraph takes one whole-paragraph edit OR several non-overlapping spans OR one line removal — empty `text` / `delete:true`, routed to the cell-safe `removeParagraphLine` so a form-fill fills cells AND drops leftover placeholder lines in one sweep); `replace` re-reads the live tree between entries (`document.reread()` re-walks `documentTree`, no re-parse, so node identity holds) for sed-like sequential semantics; `insert` pins block/cell anchors to live refs, builds all blocks with zero body mutation, reserves an initially empty cell's mandatory paragraph for its first bare-cell entry, rejects mixed bare-cell + explicit `CELL:pK` targeting in one batch, then splices with a per-anchor offset for block targets and one `CellInsertionCursor` (in `@core/table`) per cell, so stacked inserts keep entry order; `delete` resolves every locator to a live ref before any removal, then splices each by its live `indexOf` (so sibling shifts never misfire); `move` (the `Move` lens in `core/move`) resolves every source AND destination to live refs and runs every refusal before any splice, refuses overlapping sources and any anchor some entry moves, then detaches/splices by live `indexOf` with the same per-anchor offset as `insert` (one `CellInsertionCursor` per bare-cell destination); `comments reply` validates every parent `cN` before minting any reply (reply ids append, so parents never shift), and an entry's `"resolve": true` resolves its thread root after all replies land — a finalize pass is `track-changes apply` + ONE `comments reply --batch`. Review work is where weak agents most often go one-call-per-change (the contract scenarios), so the `track-changes`/`comments` noun helps lead with the batched review recipe. Range/span `edit`s and `delete`s aren't batchable (do them one at a time); section (`sN`) and equation (`eqN`) targets aren't `edit`'s surface at all — use `docx sections` / `docx equations edit` (and `delete --at sN` for a section break).

**Output model — exit code is the success signal** (`0` ok, `1` general, `2` usage, `3` not-found, in `src/cli/respond.ts`), but every command also confirms in text — silence forced (weak) agents to re-read just to know a mutation took effect. The `ok` field appears ONLY on the `--verbose` ack. So:

- A mutator that mints a new addressable handle (`comments add`→`cN`, `comments reply`→`cN`, `footnotes/endnotes add`→`fnN`/`enN`, `hyperlinks add`→`linkN`, `insert`→the new `pN`) prints the bare locator(s) — one per line — by default (`respondMinted`); `--verbose` → full `{ok:true,…}` ack.
- A mutator that mints a new addressable handle (`comments add`→`cN`, `comments reply`→`cN`, `footnotes/endnotes add`→`fnN`/`enN`, `hyperlinks add`→`linkN`, `insert`→the new `pN`, `move`→the moved blocks' NEW locators) prints the bare locator(s) — one per line — by default (`respondMinted`); `--verbose` → full `{ok:true,…}` ack.
- A mutator with no new handle (`create`/`edit`/`delete`/`replace`/`comments resolve`/`tables *`/`track-changes *`/`toggle`) prints a one-line, text-first confirmation on success — `<operation> <target>` (e.g. `edit t1:r0c1:p0`, `edit 7 changes`, `replace 3 occurrences replaced`, `tables.insert-row t2 (1)`) via `respondAck`; `--verbose` → full `{ok,…}` JSON ack. The line is derived centrally in `respondAck`/`summarizeAck` from the ack payload's `operation` + most salient field (locator, count, id, table cell, …), so adding a mutator gets a confirmation for free as long as its ack carries `operation`.
- Query commands are text-first where there's a clean plain form: `find`→locator lines, `wc`→count, `outline`→indented `locator␉text` tree, `read`→markdown — each with `--json`/`--ast` for the structured payload. List verbs print a bare JSON array whose items' `id` is the `--at` handle.
- Errors print `{code, error, hint?}` (no `ok`) + a nonzero exit. Dry-run previews drop `ok` too.
Expand Down
13 changes: 13 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ docx insert FILE (--after | --before) LOCATOR <content> # explicit block side
docx insert FILE (--at-start | --at-end) <content> # no locator — prepend / append to the document
docx edit FILE --at LOCATOR <content> # LOCATOR = pN | pN:S-E | pN-pM | tN:rRcC | tN:rRcC:pK[:S-E]; bare CELL requires one direct paragraph
docx delete FILE --at LOCATOR # LOCATOR = pN | pN-pM | tN | sN | tN:rRcC:pK (cell paragraph)
docx move FILE --at LOCATOR (--after | --before) LOCATOR [--subtree] # relocate blocks in place (see below)
docx sections FILE [--at LOCATOR] [--columns N] [--type T] [--orientation O] [--size SIZE] [--margins M] # LOCATOR = pN-pM | pN (wrap a range in N columns) | sN (edit one section's columns/type/page geometry). Multi-column layout AND page setup live HERE. PAGE GEOMETRY (margins/orientation/size) with NO --at applies to the WHOLE document (every section); --at sN targets one. Columns/type need --at.
docx styles set-default-font FILE "Font Name" [--size N] [--all] # document-wide font: sets styles.xml docDefaults + theme major/minor; --all also repoints styles/runs that pin their own font
docx replace FILE PATTERN REPLACEMENT [--at pN] [--regex] [--ignore-case] [--all] [--limit N] [--current | --baseline] [--exact] [--clear ATTRS] [run-formatting] [--track] [--dry-run]
Expand Down Expand Up @@ -206,6 +207,7 @@ docx edit FILE --batch fills.jsonl # { at, <one of: text|clear|markdown
docx insert FILE --batch additions.jsonl # { at|after|before, <content>, style?, color?, … }
docx replace FILE --batch script.jsonl # { pattern, replacement, at?, regex?, all?, limit?, clear?, bold?, color?, … } applied in order
docx delete FILE --batch drop.jsonl # { at } per line — whole blocks (pN/tN/cell), resolved live-first
docx move FILE --batch moves.jsonl # { at, after|before, subtree? } — applied in order; several after one anchor keep entry order

# All four of insert/edit/delete/replace accept --track to record that one
# invocation as a tracked change even when the doc's track-changes toggle is off.
Expand Down Expand Up @@ -545,6 +547,17 @@ docx edit doc.docx --at p3 --markdown-file revised.md # multi-block source exp

A source starting with `-` (a bullet) works inline too — `docx insert doc.docx --after p3 "- item"`; `--markdown-file` is for long or multi-block sources.

**move a clause, section, or table.** `move` splices the blocks themselves — no copy, no Markdown round-trip — so comment ranges, footnote references, bookmarks, fields, images, text boxes and every paragraph property come along untouched. Don't read → `insert` a copy → `delete` the original: that orphans the comment and turns `[^fn1]` into literal text.

```sh
docx move doc.docx --at p7 --before p2 # one paragraph
docx move doc.docx --at p4-p9 --after p20 # a range (tables between come too)
docx move doc.docx --at p12 --subtree --before p3 # a heading + everything under it (next same-or-higher heading ends it)
docx move doc.docx --at p5 --after t0:r1c2 # into a bare cell (fills an empty cell's blank paragraph)
```

`--at` takes `pN`, `pN-pM`, `tN`, a cell paragraph `tN:rRcC:pK`, or a text-box paragraph `tbxN:pK`; `--after`/`--before` take a block or a bare cell (end/start), exactly like `insert`. It prints the moved blocks' NEW locators. Refused with a nonzero exit and nothing written: a destination inside the source, a move that lands where the blocks already are, a source paragraph carrying a section break, a comment/bookmark range with only one end inside the source, a move between the body and a text box, and any move while track changes is on — tracked moves (`<w:moveFrom>`/`<w:moveTo>`) aren't written yet. A moved list item keeps its own numbering (`numPr`), as in Word. A cell or text box left without a paragraph gets Word's mandatory empty one.

**track-changes review loop.** Toggle tracking on, make edits (they auto-emit `<w:ins>`/`<w:del>`), then inventory and resolve:

```sh
Expand Down
5 changes: 3 additions & 2 deletions skills/docx-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ it if you use `--ast`.
| leave notes on clauses | `comments add FILE --batch -` with lines like `{"anchor":"limitation of liability","text":"Cap is too low."}` |
| finalize a returned redline | `track-changes list` → `track-changes apply FILE --accept rev0 rev2 --reject rev1` → `comments reply FILE --batch -` with `{"at":"c0","text":"Done.","resolve":true}` |
| fill out a contract, NDA, form, or template | `replace FILE --batch -` with `{"pattern":"[Client]","replacement":"Acme, Inc."}` (keeps each field's formatting) |
| reorder / move a clause, section, or table | `move FILE --at p12 --before p3` (`--subtree` on a heading takes its whole section) — never re-insert a copy and delete the original |
| restyle headings, fonts, spacing, margins | `styles set`, `styles set-default-font`, `sections --margins` |
| write a new document | `create out.docx --from draft.md` |

Expand Down Expand Up @@ -142,7 +143,7 @@ docx render FILE --out pages/ # PNG per page — only when LAYOUT is

## 4. Apply many changes from one read — `--batch`

`edit`, `insert`, `replace`, `delete`, and the `comments` verbs take
`edit`, `insert`, `replace`, `delete`, `move`, and the `comments` verbs take
`--batch FILE.jsonl` (one JSON change per line; `-` reads stdin). Every locator
in the batch addresses the document **as read**, so ids stay valid across the whole
batch — one read, one write, no re-reading between changes. Keys mirror the
Expand All @@ -155,7 +156,7 @@ command's flags. This is the right tool for filling a form or applying a review.
have to re-read just to learn whether a mutation landed.
- Mutators overwrite `FILE` **in place** (git is your history). `-o/--output PATH`
writes a copy instead; `--dry-run` previews without writing.
- A command that mints a new handle (`comments add`→`cN`, `insert`→`pN`,
- A command that mints a new handle (`comments add`→`cN`, `insert`/`move`→`pN`,
`footnotes add`→`fnN`, …) prints the bare locator(s), one per line.
- Re-read after structural edits (ids shift), or batch from one read.
- Need exact literal text in (a URL, prose GFM would mangle)? `insert` and
Expand Down
1 change: 1 addition & 0 deletions skills/docx-cli/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ docx info skill # this skill, regenerated from the binary
| `edit FILE` | Replace/strip text or formatting at a locator; a bare simple cell fills its sole paragraph (`--clear`, `--track`, `--batch`). |
| `insert FILE` | Insert content with `--at LOCATOR` (after a block, into a bare cell) or explicit `--before/--after` (`--track`, `--batch`). |
| `delete FILE` | Remove a paragraph, range, table, or section break (`--at`, `--track`, `--batch`). |
| `move FILE` | Relocate blocks in place — comments/footnotes/formatting ride along (`--at`, `--after`/`--before`, `--subtree`, `--batch`; untracked only). |
| `replace FILE PATTERN REPL` | Substitute spans; defaults to the first match (`--all` for every match), preserves formatting/tabs, and can format or clear formatting on the replacement (`--bold`, `--color`, `--clear`, `--batch`). |
| `sections FILE` | Multi-column layout, section breaks, and page setup (margins/orientation/size). The only way to do columns. |
| `styles set/create/set-default-font` | Restyle every heading at once, mint a style, or set the document font. |
Expand Down
18 changes: 18 additions & 0 deletions src/cli/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ mix, and `positive` is checked AFTER rounding (`0.01pt` is a 0-twip value). A ne
(`--widths 25%,…`, `--table-width 50%`) are handled by the caller before the
length parse. Font `--size` and `--border-size` are not lengths — leave them.

## `move` — relocate, never re-author

[move/](move/) is a thin parser over the `Move` lens in
[`core/move`](../core/move/index.ts): `plan()` resolves every source and
destination to LIVE `XmlNode` refs and runs every refusal against the document
as read (no mutation), `apply()` detaches each source and splices it at its
destination by live `indexOf`. Because the nodes themselves move, everything
inside them — comment ranges, note references, bookmarks, fields, drawings,
text boxes, `dcx:raw` blocks — rides along with no per-construct code; resist
adding any (a copy-then-delete path is exactly what issue #38 replaced). Every
refusal is a nonzero exit with nothing written, including a move that lands
where the blocks already are (no-op rule) and a range marker with one end inside
the source (comment/bookmark/perm/move ranges, `RANGE_PAIRS` in
`core/move/guards.ts`). **Untracked only for now**: `move` refuses while
tracking is on rather than silently skipping the journal — the tracked shape
(`<w:moveFrom>`/`<w:moveTo>` + range markers, paired by `w:name` in review)
must be captured from the Word oracle before it is written.

## `raw` — the verbatim `--xml` channel

`docx raw insert/replace` takes `--xml` VERBATIM — never route it through
Expand Down
1 change: 1 addition & 0 deletions src/cli/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Commands (each one-liner names capabilities you'd otherwise miss; see <command>
replace FILE PATTERN REPL Replace the FIRST match sed-style (--all for every match). KEEPS formatting/tabs and can format the replacement (--bold/--color/--clear; --track; --batch).
edit FILE Replace content at a locator — pass the new markdown after the locator (the dialect \`read\` prints; --text is literal). --clear strips formatting, --track redlines, --batch does many edits in one read. ${NOUN_REDIRECTS}
delete FILE Remove content at a locator (--at LOCATOR; --track for tracked deletion; --batch to remove many in one read)
move FILE Relocate blocks in place — "move FILE --at p7 --before p2"; pN-pM / tN / cell paragraphs; --subtree moves a heading with its section; comments, footnotes & formatting ride along (never re-insert a copy + delete); --batch.
outline FILE List headings as a locator tree (pN feeds --at / read --from; --style-prefix, --json)
wc FILE [LOCATOR] Count words in the doc or a slice (--accepted/--baseline/--current tracked view, --json)
sections FILE Multi-column layout, section breaks & PAGE SETUP — columns on a range (--at pN-pM --columns N); page margins/orientation/size for the WHOLE document ("--margins 0.5" with NO --at sets every section) or one section (--at sN). The ONLY way to do columns; insert does not.
Expand Down
1 change: 1 addition & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const COMMANDS: Record<string, () => Promise<{ run: CommandFn }>> = {
info: () => import("./info"),
insert: () => import("./insert"),
lists: () => import("./lists"),
move: () => import("./move"),
outline: () => import("./outline"),
raw: () => import("./raw"),
read: () => import("./read"),
Expand Down
Loading
Loading