feat: export core Hermes skills

This commit is contained in:
2026-07-15 02:45:56 +00:00
parent a028b63eda
commit 54711fee2a
308 changed files with 41310 additions and 1 deletions
@@ -0,0 +1,146 @@
# Comment Restoration from Original File
When comments are lost during docx editing (e.g., paragraph clear operations that remove commentRangeStart/End/Reference elements), restore them from the original file.
## Scenario
- Original file has N comments (e.g., Alice×2, 法务, 杜律 = 4 comments, ids 0-3)
- Edited file lost some/all original comments and may have added new ones (e.g., 华诚-Z comment id=0, Alice id=2)
- Goal: merge all comments — original ones preserved + new ones added, with non-conflicting IDs
## Recovery Technique
### Step 1: Extract original comments
```python
WNS = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
z_orig = zipfile.ZipFile('original.docx')
with z_orig.open('word/comments.xml') as f:
ctree_orig = etree.parse(f)
orig_comments = []
for c in ctree_orig.getroot().findall(f'{WNS}comment'):
orig_comments.append({
'id': c.get(f'{WNS}id'),
'author': c.get(f'{WNS}author'),
'date': c.get(f'{WNS}date'),
'text': ''.join(t.text for t in c.iter(f'{WNS}t') if t.text),
'element': copy.deepcopy(c)
})
z_orig.close()
```
### Step 2: Identify which comments survived in the edited file
```python
z_edit = zipfile.ZipFile('edited.docx')
with z_edit.open('word/comments.xml') as f:
ctree_edit = etree.parse(f)
edit_comment_ids = set()
for c in ctree_edit.getroot().findall(f'{WNS}comment'):
edit_comment_ids.add(c.get(f'{WNS}id'))
```
### Step 3: Find new comments (non-original authors)
```python
new_comments = []
for c in ctree_edit.getroot().findall(f'{WNS}comment'):
if c.get(f'{WNS}author') not in [oc['author'] for oc in orig_comments]:
new_comments.append({
'old_id': c.get(f'{WNS}id'),
'author': c.get(f'{WNS}author'),
'element': copy.deepcopy(c)
})
```
### Step 4: Rebuild comments.xml with all comments
Assign non-conflicting IDs:
- Original comments keep their original IDs (0, 1, 2, 3)
- New comments get IDs starting from max(original_ids) + 1
```python
new_comments_xml = etree.Element(f'{WNS}comments')
new_comments_xml.set('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main')
# ... add other namespaces as needed
max_id = max(int(oc['id']) for oc in orig_comments)
# Add original comments
for oc in orig_comments:
new_comments_xml.append(oc['element'])
# Add new comments with renumbered IDs
for nc in new_comments:
max_id += 1
nc['new_id'] = str(max_id)
nc['element'].set(f'{WNS}id', nc['new_id'])
new_comments_xml.append(nc['element'])
```
### Step 5: Update document.xml comment references
For each new comment, find its commentRangeStart, commentRangeEnd, and commentReference in document.xml and update the ID from old to new:
```python
for nc in new_comments:
old_id = nc['old_id']
new_id = nc['new_id']
# Update commentRangeStart
for elem in root.iter(f'{WNS}commentRangeStart'):
if elem.get(f'{WNS}id') == old_id:
elem.set(f'{WNS}id', new_id)
# Update commentRangeEnd
for elem in root.iter(f'{WNS}commentRangeEnd'):
if elem.get(f'{WNS}id') == old_id:
elem.set(f'{WNS}id', new_id)
# Update commentReference (inside w:r)
for elem in root.iter(f'{WNS}commentReference'):
if elem.get(f'{WNS}id') == old_id:
elem.set(f'{WNS}id', new_id)
```
### Step 6: Write back to docx
```python
z_out = zipfile.ZipFile('output.docx', 'w')
# Copy all files from edited.docx except comments.xml and document.xml
for item in z_edit.namelist():
if item not in ('word/comments.xml', 'word/document.xml'):
z_out.writestr(item, z_edit.read(item))
# Write updated comments.xml
z_out.writestr('word/comments.xml',
etree.tostring(new_comments_xml, encoding='UTF-8', xml_declaration=True, standalone=True))
# Write updated document.xml
z_out.writestr('word/document.xml',
etree.tostring(tree, encoding='UTF-8', xml_declaration=True, standalone=True))
z_edit.close()
z_out.close()
```
## Verification
```python
z = zipfile.ZipFile('output.docx')
with z.open('word/comments.xml') as f:
ctree = etree.parse(f)
for c in ctree.getroot().findall(f'{WNS}comment'):
print(f" id={c.get(f'{WNS}id')} author={c.get(f'{WNS}author')}: {text[:80]}")
# Check all IDs referenced in document.xml exist in comments.xml
content = z.read('word/document.xml').decode('utf-8')
doc_ids = set(re.findall(r'commentRangeStart[^>]*w:id="(\d+)"', content))
doc_ids |= set(re.findall(r'commentRangeEnd[^>]*w:id="(\d+)"', content))
doc_ids |= set(re.findall(r'commentReference[^>]*w:id="(\d+)"', content))
comment_ids = set(c.get(f'{WNS}id') for c in ctree.getroot().findall(f'{WNS}comment'))
assert doc_ids == comment_ids, f"ID mismatch: doc={doc_ids} comments={comment_ids}"
```
## Key Pitfall: Comment Text Extraction
When extracting comment text for comparison, comments may have nested `<w:p>` elements (multi-paragraph comments). Use `.iter()` not `.findall()` to get all text nodes.
## Empirical Case (2026-07-01 反委托代发工资协议)
- Original: 4 comments (Alice id=0, Alice id=1, 法务 id=2, 杜律 id=3)
- v1_doro_updated: 2 comments (华诚-Z id=0, Alice id=2) — lost Alice id=0/1, 法务, 杜律
- Final: 5 comments (Alice id=0, Alice id=1, 法务 id=2, 杜律 id=3, 华诚-Z id=4)
- 华诚-Z's comment was id=0 in v1_doro_updated, renumbered to id=4 in final
- All commentRangeStart/End/Reference IDs updated in document.xml accordingly