Files
hermes-skills/skills/legal/contract-editor/references/add-clause-after-tracked-replace-failure.md

2.5 KiB

add_clause after_search Fails After tracked_replace — Use Direct lxml Insertion

Problem (2026-07-01 凤雅幼儿园劳务派遣协议)

After calling ed.tracked_replace(old, new) on multiple paragraphs, subsequent ed.add_clause(text, after_search="...") calls silently fail — the new paragraph doesn't appear in the output. The function returns without error but the clause is not inserted.

Root Cause

add_clause's after_search parameter searches paragraph text by concatenating all <w:t> elements. After tracked_replace, the paragraph's XML contains interleaved <w:del> and <w:ins> elements. The after_search text-matching logic may:

  1. Include both old (del) and new (ins) text in the concatenation, so neither the old NOR new text matches cleanly
  2. Match the wrong paragraph if the search string appears in unexpected combinations of del+ins text

Solution: Direct lxml addnext Insertion

After all tracked_replace calls, insert new clauses directly using lxml:

# Find reference paragraph by index or by scanning accepted-view text
paras = ed.body.findall(f'{WNS}p')
ref_para = paras[target_index]  # e.g., P68

# Build INS paragraph
new_p = etree.Element(f'{WNS}p')
new_p.append(copy.deepcopy(ref_ppr))  # Clone paragraph formatting

ins = etree.SubElement(new_p, f'{WNS}ins')
ins.set(f'{WNS}id', next_rev_id())
ins.set(f'{WNS}author', 'WB')
ins.set(f'{WNS}date', rev_date)

r = etree.SubElement(ins, f'{WNS}r')
r.set(f'{WNS}rsidR', rsid)
r.insert(0, copy.deepcopy(ref_rpr))

t = etree.SubElement(r, f'{WNS}t')
t.set(XML_SPACE, 'preserve')
t.text = clause_text

# Mark paragraph itself as inserted (pPr/rPr/ins)
ppr = new_p.find(f'{WNS}pPr')
ppr_rpr = etree.SubElement(ppr, f'{WNS}rPr')
ppr_ins = etree.SubElement(ppr_rpr, f'{WNS}ins')
ppr_ins.set(f'{WNS}id', next_rev_id())
ppr_ins.set(f'{WNS}author', 'WB')
ppr_ins.set(f'{WNS}date', rev_date)

# Insert after reference
ref_para.addnext(new_p)
ref_para = new_p  # Chain subsequent inserts

When This Applies

  • You need to both modify existing clauses (tracked_replace) AND add new clauses in the same editing session
  • The after_search text has been altered by prior tracked_replace calls

Correct Operation Order

  1. All ed.tracked_replace(...) calls first
  2. Then find target paragraphs by scanning the body with accepted-view text extraction
  3. Insert new paragraphs directly via addnext
  4. ed.validate() + ed.save()

Verification

After save, scan paragraphs and confirm new clauses appear in accepted-view text at the expected positions.