overleaf-review 0.3.0 → 0.5.0

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 (3) hide show
  1. package/README.md +198 -15
  2. package/dist/cli.js +3725 -371
  3. package/package.json +6 -2
package/README.md CHANGED
@@ -35,14 +35,24 @@ workflow and carries the review layer Git can't represent.
35
35
  - 📥 **`pull`** — read comments + tracked changes (with anchors) into a Git-friendly sidecar
36
36
  (`.overleaf/reviews.md` + `.json`), so your tools have every co-author note in context.
37
37
  - 📤 **`push`** — turn local edits into **tracked-change suggestions**, mapping files to Overleaf
38
- docs by path (one file, or every changed `.tex` at once). `--dry-run` previews the exact ops;
39
- `--direct` sends plain edits instead of suggestions.
38
+ docs by path (one file, or every changed `.tex` at once). A saved Base/Local/Live merge keeps
39
+ non-overlapping co-author edits, while overlapping edits and active tracked-change ranges stop
40
+ the push for inspection. `--direct` sends plain edits instead of suggestions.
40
41
  - 🔄 **`fetch`** — write Overleaf's current text back down into your repo (read-only on Overleaf, so
41
- it cannot disturb a single comment or tracked change).
42
+ it cannot disturb a single comment or tracked change) and save the synchronization base used by
43
+ future pushes. Existing local files are snapshotted before replacement.
44
+ - 📋 **`review plan` / `review submit`** — save a complete, binding push plan, inspect it, then
45
+ submit it only if the local file, saved base, live document version, and review ranges are still
46
+ unchanged.
42
47
  - 🖼️ **`upload`** — push figures, PDFs, or new files into Overleaf.
43
48
  - 💬 **`comment` / `reply` / `resolve` / `delete-comment` / `delete-message`** — full comment
44
49
  control: start a thread, reply, resolve/reopen, delete a whole thread or a single message.
45
- - **`accept` / `reject`** act on your collaborators' tracked changes from the CLI.
50
+ Identical recent comments and replies are retry-safe by default; `--force` deliberately posts
51
+ another copy.
52
+ - ✅ **`accept` / `reject`** — act on collaborators' tracked changes and verify removal of every
53
+ requested change id before reporting success; rejection also verifies the exact resulting text.
54
+ - 🧾 **Review receipts** — pushes, accept/reject actions, comments, and replies leave durable JSON
55
+ records under `.overleaf/receipts/` showing what was verified or left ambiguous.
46
56
  - 🔑 **`login`** — validated auth stored outside your repo (chmod 600); `--browser` mode is
47
57
  institutional-SSO friendly.
48
58
 
@@ -60,9 +70,11 @@ ops that Overleaf *transforms the review ranges against*, so comments and tracke
60
70
 
61
71
  | Need | Command |
62
72
  | --- | --- |
63
- | Overleaf text your repo | `fetch` |
73
+ | Begin a local review session | `review start` |
74
+ | Overleaf text → your repo and save Base | `fetch` |
64
75
  | Your edits → Overleaf, as suggestions | `push` |
65
76
  | Your edits → Overleaf, directly | `push --direct` |
77
+ | Inspect, then submit the exact same change | `review plan --out plan.json`, then `review submit --plan plan.json` |
66
78
  | Figures / new files → Overleaf | `upload` |
67
79
 
68
80
  Your Git repo stays a completely normal repo — commit whatever you like, `.tex` included — and
@@ -85,14 +97,28 @@ overleaf-review login --browser # opens your Chrome, log in normally (SSO work
85
97
  # 2. Link this repo to an Overleaf project (id from the project URL)
86
98
  overleaf-review link --project 6a4c…bec5a
87
99
 
88
- # 3. Sync the review layer
89
- overleaf-review pull # comments + changes .overleaf/
90
- overleaf-review push --dry-run # preview local edits as suggestions
91
- overleaf-review push # send them as tracked changes
100
+ # 3. Start from Overleaf's current text and review state
101
+ overleaf-review review start # fetch text + save Base + pull reviews
102
+
103
+ # 4. Edit locally, then inspect and submit a binding plan
104
+ overleaf-review review plan --out .overleaf/push-plan.json
105
+ overleaf-review review submit --plan .overleaf/push-plan.json
106
+
107
+ # Or use the safe one-shot form when a separate approval step is unnecessary
108
+ overleaf-review push --dry-run # transient preview; sends nothing
109
+ overleaf-review push # plan, validate, send, and verify
110
+
111
+ # 5. Work with review threads
92
112
  overleaf-review comment --anchor "Introduction" --message "Expand this section"
93
113
  overleaf-review resolve --thread <id> # thread ids come from `pull`
94
114
  ```
95
115
 
116
+ `fetch` and `review start` write Overleaf's text into the local files, so commit or otherwise
117
+ preserve local-only work before running them. They save `.overleaf/base.json`; edit only after that
118
+ base is established. Existing installations should run one of these commands once before their
119
+ first safe push. If an upgraded repository already has unpushed edits, copy or stash them, run
120
+ `fetch`, then restore the edits so the new Base remains the Overleaf version they were based on.
121
+
96
122
  ## 🧭 Commands
97
123
 
98
124
  | Command | What it does |
@@ -100,11 +126,16 @@ overleaf-review resolve --thread <id> # thread ids come from `pull`
100
126
  | `login [--cookie <v>] [--browser]` | Authenticate and store your session (SSO-friendly `--browser`) |
101
127
  | `link --project <id>` | Link this repo to an Overleaf project (`.overleaf/config.json`) |
102
128
  | `pull [--out <dir>]` | Read comments + tracked changes into a sidecar |
103
- | `fetch [--file <f>] [--dry-run]` | Write Overleaf's text down into local files (read-only on Overleaf) |
129
+ | `fetch [--file <f>] [--dry-run]` | Write Overleaf text locally, snapshot replaced files, and save Base (read-only on Overleaf) |
130
+ | `review start [--file <f>] [--out <dir>]` | Fetch text, save the synchronization base, and pull the review sidecar |
131
+ | `review plan --out <plan.json> [--file <f>] [--edits <blocks.json>] [--doc <name>] [--direct] [--allow-overlap] [--unsafe-no-base]` | Create a complete binding plan without changing Overleaf |
132
+ | `review submit --plan <plan.json> [--acknowledge-ambiguous]` | Submit that plan only if all recorded preconditions still match |
133
+ | `review consolidate --doc <path> --author <user-id> --out <preview.json> [--change <id> …]` | Preview consolidation of existing suggestions and save a full backup; no Overleaf changes |
134
+ | `review consolidate --apply --plan <plan.json>` | Apply and verify a checked consolidation plan for your own suggestions |
104
135
  | `upload <path…> [--folder <name>]` | Upload figures / new files into Overleaf |
105
- | `push [--file <f>] [--doc <name>] [--direct] [--dry-run]` | Send local edits as tracked suggestions (all changed `.tex` if no `--file`); `--direct` for plain edits |
106
- | `comment --anchor <text> --message <text> [--doc <name>] [--nth <n>]` | Add a comment anchored on the given text |
107
- | `reply --thread <id> --message <text>` | Reply to an existing comment thread |
136
+ | `push [--file <f>] [--doc <name>] [--direct] [--dry-run] [--plan-out <path>] [--plan <path>] [--allow-overlap] [--unsafe-no-base] [--acknowledge-ambiguous]` | Safely merge and send local edits (all changed `.tex` if no `--file`) |
137
+ | `comment --anchor <text> --message <text> [--doc <name>] [--nth <n>] [--force]` | Add an anchored comment; recent identical retries are skipped unless forced |
138
+ | `reply --thread <id> --message <text> [--force]` | Reply to a thread; recent identical retries are skipped unless forced |
108
139
  | `resolve --thread <id> [--reopen]` | Resolve (or reopen) a comment thread |
109
140
  | `delete-comment --thread <id>` | Delete a whole comment thread |
110
141
  | `delete-message --message-id <id> [--thread <id>]` | Delete a single message within a thread |
@@ -113,6 +144,154 @@ overleaf-review resolve --thread <id> # thread ids come from `pull`
113
144
 
114
145
  Thread and change ids are listed by `pull` (in `.overleaf/reviews.md`).
115
146
 
147
+ ### Safe pushes
148
+
149
+ A normal push compares three versions of each document:
150
+
151
+ - **Base** — the Overleaf text saved by the last `fetch` or successful push;
152
+ - **Local** — your current file;
153
+ - **Live** — the document as it exists in Overleaf when planning.
154
+
155
+ Your Base→Local intent is rebased onto Live. Non-overlapping live edits are preserved; genuinely
156
+ overlapping Base→Local and Base→Live edits abort instead of silently undoing a collaborator's
157
+ work. A proposed edit that intersects an active tracked-change range is also blocked by default and
158
+ the relevant change ids are listed.
159
+
160
+ For intentional sentence or paragraph rewrites, agents should preserve their chosen edit boundaries
161
+ with `--edits`, rather than relying on automatic diff grouping. Edit the local file normally, then
162
+ describe every change in a JSON manifest:
163
+
164
+ ```json
165
+ {
166
+ "replacements": [
167
+ {
168
+ "before": "This is the old paragraph. Its explanation is unclear.",
169
+ "after": "This is the revised paragraph. Its explanation is clearer."
170
+ }
171
+ ]
172
+ }
173
+ ```
174
+
175
+ ```sh
176
+ overleaf-review review plan --file main.tex --edits blocks.json --out plan.json
177
+ overleaf-review review submit --plan plan.json
178
+ ```
179
+
180
+ Each block becomes one deletion and/or insertion, even across multiple sentences or LaTeX markup.
181
+ Overleaf controls the resulting review UI; a replacement is generally two tracked ranges, not a
182
+ guaranteed single accept/reject button. Keep typo corrections small, group a coherent rewrite,
183
+ and keep independent decisions separate. Author attribution remains unchanged.
184
+
185
+ `before` must match the saved Base exactly. Repeated text requires a 1-based `occurrence`.
186
+ Blocks must be separated and must explain the entire local diff. The plan embeds the resolved
187
+ blocks; it never rereads or silently reinterprets the manifest during submission. Concurrent edits
188
+ outside blocks are preserved; any comment, pending suggestion, or concurrent edit touching a block
189
+ stops planning rather than silently fragmenting it. Narrow the block or resolve the conflict.
190
+ `--edits` requires `--file` and a saved base; it cannot be combined with `--direct` or `--unsafe-no-base`.
191
+
192
+ Without explicit blocks, tracked suggestions group nearby word edits into phrase replacements. For example, rewriting
193
+ “old model predicts low” as “revised model explains high” produces one deletion and one insertion,
194
+ instead of six separate word operations. Isolated corrections stay small. Grouping bridges at most
195
+ three unchanged words (40 characters), limits combined spans to 320 characters, and stops at detected
196
+ sentence/clause boundaries, paragraph breaks, and LaTeX markup. This is a deterministic readability
197
+ heuristic; it does not infer which scientific claims should be accepted together. An existing
198
+ multiword insertion remains a single insertion.
199
+
200
+ Unchanged comment anchors, tracked ranges, and edits made on Live since Base prevent grouping across
201
+ them. Overlap checks use the full grouped footprint, and submission reproduces the same grouping.
202
+ `--direct` retains the narrower word-level operations. Plans from before this grouping change must
203
+ be regenerated; submitting an old plan does not silently regroup its approved operations.
204
+
205
+ Planning and submission check a conservative range budget: existing tracked ranges plus proposed
206
+ insert/delete operations must not exceed 2,000 per document. The preview shows both counts.
207
+ Overlap transformations can alter the actual count, so this is a preflight guard rather than an
208
+ exact prediction. Splitting a revision into batches does not remove accumulated pending ranges.
209
+ Grouping applies to new pushes; it does not consolidate or accept suggestions already on Overleaf.
210
+
211
+ ### Consolidation of existing suggestions
212
+
213
+ `review consolidate --doc main.tex --author <user-id> --out .overleaf/consolidation.json`
214
+ captures the live text, all document ranges, and project comment threads. It reconstructs the text
215
+ with that author's selected suggestions rejected, then plans a grouped revision back to the current
216
+ proposed text. By default it considers all current suggestions by that author, including older
217
+ fragments; repeat `--change` to select a subset. Use the author user ID from range metadata.
218
+
219
+ The artifact records both text hashes, a reverse/forward reconstruction proof, projected range
220
+ counts, and any comment or unselected-suggestion blockers. It preserves the *current pending
221
+ proposal*, not a historical review state before older suggestions were absorbed. Consolidation
222
+ would assign new IDs and timestamps. Counts remain projections until server transformations are
223
+ verified. Touching comment anchors or unselected changes blocks the candidate; these are not
224
+ automatically removed or accepted.
225
+
226
+ For offline analysis, use `--snapshot <file.json>` instead of `--doc`. The snapshot must contain
227
+ `projectId`, `docId`, `docPath`, `version`, `text`, `ranges: { changes, comments }`, and `threads`;
228
+ the preview embeds this complete object as `backup`. A normal review sidecar alone is insufficient.
229
+
230
+ Planning sends nothing. Inspect the complete plan, then apply it separately:
231
+
232
+ ```sh
233
+ overleaf-review review consolidate --apply --plan .overleaf/consolidation.json
234
+ ```
235
+
236
+ Only suggestions owned by the authenticated account can be reapplied. Blocked/no-reduction plans
237
+ cannot be submitted. The command reproduces the plan from its backup and requires unchanged text,
238
+ document version, ranges, and project threads. It journals the full snapshot before sending undo
239
+ and reapply operations together in one update. It never accepts suggestions as a prerequisite.
240
+
241
+ Verification checks the proposed text, text with selected suggestions rejected, text with all
242
+ suggestions rejected, disappearance of old IDs, new-ID attribution, reduced range count, and
243
+ unchanged unselected ranges, comment anchors and thread messages. New IDs and timestamps are
244
+ expected. Any uncertain send or failed verification quarantines subsequent attempts: inspect the
245
+ receipt and live document before manually reconciling it; there is no automatic rollback/retry.
246
+ Reusing an already successful plan reports its receipt without sending it again.
247
+
248
+ Coordinate a quiet editing window for consolidation. Overleaf has no conditional transaction
249
+ covering text, ranges and threads, so preflight cannot prevent every last-moment collaborator race;
250
+ readback detects discrepancies but cannot make an already-sent operation un-happen. A backup is
251
+ recovery evidence, not a promise of automatic review-history restoration.
252
+
253
+ Development check `npm run probe:consolidation-model` exercises pinned upstream range logic in
254
+ memory without accessing an Overleaf project. The opt-in `npm run probe:review-live -- --project
255
+ <id> --confirm-test-project` creates and retains a new scratch document in the specified test
256
+ project, verifies consolidation and protected review state, and checks that existing documents
257
+ were unchanged. It does not require or spoof another account. Foreign-author preservation is also
258
+ covered by the upstream model check. Never run this probe against a manuscript project.
259
+
260
+ ### Push overrides and submission
261
+
262
+ Use `--allow-overlap` only after inspecting those tracked changes. `--unsafe-no-base` is a deliberate
263
+ legacy escape hatch that treats the current Live document as Base; it loses the protection against
264
+ co-author edits made since your local file was obtained. `--direct` changes how the validated ops
265
+ are represented in Overleaf, not the merge and conflict checks.
266
+
267
+ For a separate approval step, `review plan --out plan.json` stores the full operation list and its
268
+ preconditions, active tracked ranges, and any comment overlaps. `review submit --plan plan.json`
269
+ re-derives the plan from Base/Local/Live and aborts if preflight finds that the project, local source,
270
+ synchronization base, live document version/content, or active review ranges changed. Plans contain
271
+ manuscript fragments, so store or commit them with the same care as the source itself. `push
272
+ --plan-out <path>` and `push --plan <path>` expose the same workflow through the one-level command.
273
+
274
+ Overleaf can transform an update that races with a collaborator after the final preflight. The tool
275
+ therefore reads every result back and fails rather than claiming success when the exact planned text
276
+ is not present. A multi-file submission is not transactional; if a later file fails, its receipt
277
+ identifies every earlier file already verified on Overleaf.
278
+
279
+ Pushes, accept/reject actions, comments, and replies write audit receipts beneath
280
+ `.overleaf/receipts/`. Keep an ambiguous receipt: it means the request may have reached Overleaf but
281
+ readback could not prove the result, so inspect the live project before retrying. For comments and
282
+ replies, an identical recent message is treated as a retry and skipped; pass `--force` only when a
283
+ second identical message is intentional.
284
+
285
+ An ambiguous push quarantines its affected documents even if a newly generated plan has a different
286
+ timestamp or tracked-change seed. After waiting for delayed updates and manually reconciling the
287
+ receipt against live Overleaf, `--acknowledge-ambiguous` explicitly clears that submission-time
288
+ guard; it should never be used as an automatic retry flag.
289
+
290
+ Mutating commands are serialized by a working-tree-specific lock in the system temporary directory,
291
+ preventing two local agents from racing through the same preflight. A normal exit removes the lock.
292
+ If a process crashes and leaves it behind, the next command prints its exact path; inspect any
293
+ relevant receipt and the live Overleaf document before removing it manually.
294
+
116
295
  ## 🧠 How it works
117
296
 
118
297
  overleaf.com's editor speaks an old **socket.io 0.9** protocol over a WebSocket. The client
@@ -122,8 +301,12 @@ overleaf.com's editor speaks an old **socket.io 0.9** protocol over a WebSocket.
122
301
  - a **tracked change** is an insert/delete op with a `meta.tc` flag;
123
302
  - a **comment** is a `c` op plus a REST post of the message text.
124
303
 
125
- `push` diffs your local file against Overleaf's current content and translates the hunks into
126
- sequential OT ops, so your edits land as reviewable suggestions rather than silent changes.
304
+ `push` performs the Base/Local/Live merge described above, translates Live→merged text into
305
+ sequential OT ops, and waits for Overleaf's applied-update event. It then reads the document back
306
+ and verifies the final text—and, in suggestion mode, tracked-change ids—before updating the saved
307
+ base. Accept and reject operations verify that the requested ids disappeared; rejection also
308
+ verifies the exact planned text. Push, accept/reject, comment, and reply fail loudly when their
309
+ readback cannot prove success.
127
310
 
128
311
  ## ⚠️ Disclaimer
129
312