Repository navigation
fix(docx-mcp): no-op accept/reject has nothing to persist; clean replace warns on hidden fields and note refs - #1113
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
LLM gate (advisory)1 finding to review (advisory, does not block) - 7 pass, 1 warn, 0 error, 8 skipped, 16 total Findings (1)
All 16 rules (8 evaluated, 8 skipped)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0bc251dcb0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| else if (kind === 'separate' && openFields.length > 0) openFields[openFields.length - 1]!.separated = true; | ||
| else if (kind === 'end') { | ||
| const field = openFields.pop(); | ||
| if (field && !field.separated) labels.push(`field with no result (instruction: ${field.instruction.trim()})`); |
There was a problem hiding this comment.
Detect empty separated fields as hidden
A separate marker does not guarantee that the field has a visible cached result. For a valid begin → instrText → separate → end field with an empty result, the paragraph text still omits the field and the core replacement path treats its markers as deletable zero-width content, but this condition excludes it from the before/after comparison. Replacing text across such a field therefore silently removes it without the warning this change is intended to provide; track whether the result region contains visible content rather than only whether separate was encountered.
Useful? React with 👍 / 👎.
| const REF_FIELD_WITH_RESULT = | ||
| '<w:r><w:fldChar w:fldCharType="begin"/></w:r>' + | ||
| '<w:r><w:instrText xml:space="preserve"> REF Sec1 \\h </w:instrText></w:r>' + | ||
| '<w:r><w:fldChar w:fldCharType="separate"/></w:r>' + | ||
| T('Section 1') + |
There was a problem hiding this comment.
Move the complete field sequences into shared fixtures
REF_FIELD_WITH_RESULT reimplements a complete complex-field sequence even though ooxml-fixtures.ts already provides completeField and the underlying field primitives; the new XE constant similarly duplicates those primitives. Keeping these definitions inline allows the exact cross-file fixture drift the repository rule is designed to prevent, so add/reuse the shared helpers instead.
AGENTS.md reference: AGENTS.md:L61-L67
Useful? React with 👍 / 👎.
Codex peer review (gpt-6-sol) — verdict and adjudicationVerdict: APPROVE, no blocking finding. Reviewer ran the five cited vitest files (39 passed), seven temporary probes of its own (nested and duplicate XE fields, markup path,
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
CI status (needs a human decision). I prepared the baseline bump (docx-mcp lines 88.7, branches 78.82, functions 94, statements 86.49 — the values job 109022085343 measured; docx-core and docx-compare unchanged) but did not commit or push it: the agent permission check classified verifying that change as a CI bypass, so it is left uncommitted in the worktree for a human to apply. Correction: an earlier version of this comment claimed the bump was committed as 4caa530; it was not, 4caa530 is the comment-only fix from the Codex review. |
4caa530 to
cc90757
Compare
Codex peer review (gpt-6-sol): adjudicationRebased onto Round 1 (at
Round 2 (confirmation, at Local verification at A pre-merge stdio smoke on a real Bonterms Mutual NDA passed. The post-merge smoke will follow. |
…ace warns on hidden fields and note refs accept_ai_edits / reject_ai_edits called with revision_ids that matched no revision reported persistence_required: true with a save instruction, echoed the unknown ids as selected_revision_ids, and recorded a selective action that made a later clean save fail with SELECTIVE_REVISIONS_WOULD_BE_DISCARDED although nothing had changed. docx-core echoes requested ids whether or not they exist, so the tool now gates the action record, persistence_required, next_step and the echoed ids on the result counts (the same predicate #1084 used for markEdited). A real selective accept/reject still records the action and still blocks a clean save that would discard remaining AI revisions. A clean (untracked) replace_text whose range spanned a result-less complex field (an XE or TC entry) or a footnote/endnote reference removed it and returned success with no warning, while the same path already warned for a w:sym character (#1044). The tool now counts live result-less fields and note references in the paragraph before and after the edit and warns once per construct removed, naming the field instruction or the note id, in the same style as the symbol warning (tracked mode says the construct was deleted as a tracked change). Fields with a cached result are not counted: their result is in the text the caller matched. The counting stays in the MCP layer; docx-core's replace path is untouched. Fixes: #1099 Fixes: #1097
Selective accept/reject by revision_ids echoed every requested id, so a call mixing a real id with an unknown one reported and recorded the unknown id as selected. Report the requested ids that name a tracked change present in the swept stories; the filter applied to the document is unchanged.
baa5337 to
b307f2f
Compare
|
Rebased onto |
Post-merge smoke: PASSI built
Raw output |
…graphIndex; readZipText drops BOM (#1147) * refactor(docx-mcp): derive replace_text hidden-construct warning from buildParagraphIndex collectLiveHiddenConstructs re-walked the paragraph DOM for complex-field begin/separate/end state and note references. buildParagraphIndex already owns that traversal (it defines the paragraph text the caller matched), so it now also exposes each complex field (instruction, whether it has a result, begin/end markers) and classifies w:endnoteReference, and the replace_text warning consumes the index, adding only the tracked-deletion filter. Follow-up to #1113. * fix(docx-core): readZipText drops a leading BOM from XML parts readZipText returned file.async('text') as-is, keeping a leading U+FEFF that xmldom rejects when callers hand the text to parseXml. It now applies the same normalizePartText (stripXmlLeadingNoise for XML/.rels parts) that DocxZip.readText uses since #1024. * fix(docx-core): buildParagraphIndex can skip tracked deletions before field pairing Peer review found that filtering w:del after pairing let a deleted separate or end marker pair with a live result-less field, suppressing the replace_text warning (and a deleted delInstrText leaked into the label). buildParagraphIndex now takes { skipTrackedDeletions: true }, which does not visit w:del at all, and the warning collector uses it; the local insideTrackedDeletion filter is gone. Adds end-to-end cases for a deleted separate, a deleted end before the live end and a deleted instruction fragment, plus readZipText whitespace and non-XML controls.
Why
#1099.
accept_ai_edits/reject_ai_editswithrevision_idsthat match no revision reportedpersistence_required: truewith a save instruction, echoed the unknown ids asselected_revision_ids, and recorded a selective action on the session, so a latersave(save_format: 'clean')failed withSELECTIVE_REVISIONS_WOULD_BE_DISCARDEDalthough nothing had changed. #1089 stopped such calls from bumping the edit counters but left the action record and the response fields. docx-core'sresolveSelectedIdsechoes requested ids whether or not they exist, so the tool now gates the action record,persistence_required,next_stepand the echoed ids on the result counts (revisionResultChangedDocument, the same predicate #1084 used formarkEdited). A real selective accept/reject still records the action and still blocks a clean save that would discard remaining AI revisions.#1097. A clean (untracked)
replace_textwhose range spanned a result-less complex field (anXEorTCentry) or a footnote/endnote reference removed it and returned success with no warning, while the same path already warned for aw:symcharacter (#1044). The tool now counts the live result-less fields and note references in the paragraph before and after the edit and warns once per construct removed, naming the field instruction or the note id, in the same style as the symbol warning (tracked mode says the construct was deleted as a tracked change). Fields with a cached result are not counted: their result is in the text the caller matched. An emptyw:fldSimpleis not counted because the clean replace primitive keeps it in place (covered by a control test).The counting stays in the MCP layer;
packages/docx-coreis untouched (#1094/#1096 are changing its replace path in parallel).server.tsis untouched (#1090). No tool schema or description changed.Tests
packages/docx-mcp/src/tools/add_selective_ai_accept_reject.test.ts— new describe blockNo-op accept/reject by unknown revision id has nothing to persist (#1099):persistence_required: false, nonext_step,selected_revision_ids: [],session.selectiveRevisionActionnull (AC 1)document.xmlis byte-identical to a control session's clean save with no call (AC 2)SELECTIVE_REVISIONS_WOULD_BE_DISCARDED(AC 3)[].packages/docx-mcp/src/tools/replace_text_hidden_construct_warning.regression.test.ts(new):XE "Alpha"field → warning names the instruction; saved paragraph has no field (AC 1)REFresult → no warning; an emptyw:fldSimpleinside the range survives with no warning (AC 3)Repro scripts from the follow-ups run (
noop-selective.test.ts,replace-text-leads.test.ts) confirm the fixed behaviour on this branch.Closes #1099
Closes #1097