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,233 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
交付前闸门 / Delivery Gate
=========================
每次交付南通新东方梳理表前,必须跑这个脚本。
不通过 = 不发文件。不通过 = 回去补 workflow。
用法:
python3 delivery-gate.py <xlsx路径> <工作目录> <校区名>
例:
python3 delivery-gate.py /tmp/金飞达/金飞达-梳理-MJ-20260627.xlsx /tmp/金飞达 金飞达
检查项:
G1. 模版比对 subagent 是否执行过(工作目录是否有 模版比对-*.md)
G2. K/L 分工自检是否通过
G3. 格式自检是否通过(标题酒红底、段标题红底、列头正确)
G4. 整体风险分析段是否存在
G5. 数据行数是否合理(至少 文件数 行)
G6. 文件是否已上传 Nextcloud
G7. OCR占位符残留检测(【…】/〔待核PDF〕/OCR模糊 等,任一残留=禁止交付)
"""
import sys, os, subprocess, re
import openpyxl
def fail(msg):
print(f"{msg}")
return False
def ok(msg):
print(f"{msg}")
return True
def check_g1(workdir):
"""G1: 模版比对 subagent 是否执行过"""
import glob
files = glob.glob(os.path.join(workdir, "模版比对-*.md"))
if not files:
return fail("G1 模版比对:未找到模版比对输出文件。Step2 动作B 必须 delegate_task subagent 做模版比对。")
f = files[0]
size = os.path.getsize(f)
if size < 1000:
return fail(f"G1 模版比对:{os.path.basename(f)}{size} 字节,内容过短,可能未完整执行。")
return ok(f"G1 模版比对:{os.path.basename(f)} ({size:,} 字节)")
def check_g2(xlsx_path):
"""G2: K/L 分工自检"""
# 直接用 kl-separation-check.py
script = os.path.expanduser("~/.hermes/skills/legal/contract-portfolio-analysis/scripts/kl-separation-check.py")
r = subprocess.run(["python3", script, xlsx_path], capture_output=True, text=True)
if r.returncode != 0:
return fail(f"G2 K/L自检:未通过\n{r.stdout}")
return ok("G2 K/L自检:通过")
def check_g3(xlsx_path):
"""G3: 格式自检"""
try:
wb = openpyxl.load_workbook(xlsx_path)
ws = wb[wb.sheetnames[0]]
# 找到第一个列头行(含"序号"的)
hdr_row = None
for r in range(1, 30):
if ws.cell(r, 1).value == '序号':
hdr_row = r
break
if not hdr_row:
return fail("G3 格式:未找到列头行")
checks = [
("标题酒红底", ws.cell(1,1).fill.start_color.rgb == 'FF8B1A2B'),
("段标题(K列头)", ws.cell(hdr_row, 11).value == '法律风险(站乙方立场)'),
("段标题(L列头)", ws.cell(hdr_row, 12).value == '与07标准模版差异'),
]
for name, passed in checks:
if not passed:
return fail(f"G3 格式:{name} 不正确")
return ok("G3 格式自检:通过")
except Exception as e:
return fail(f"G3 格式:打开失败 - {e}")
def check_g4(xlsx_path):
"""G4: 整体风险分析段"""
try:
wb = openpyxl.load_workbook(xlsx_path)
ws = wb[wb.sheetnames[0]]
for r in range(ws.max_row, 1, -1):
v = ws.cell(r, 1).value
if v and '整体风险分析' in str(v):
return ok(f"G4 整体风险分析段:存在(行{r}")
return fail("G4 整体风险分析段:未找到。每校区必须包含「整体风险分析与建议」段。")
except Exception as e:
return fail(f"G4 整体风险分析段:打开失败 - {e}")
def check_g5(xlsx_path, min_rows=3):
"""G5: 数据行数"""
try:
wb = openpyxl.load_workbook(xlsx_path)
ws = wb[wb.sheetnames[0]]
data_rows = 0
for r in range(5, ws.max_row + 1):
v = ws.cell(r, 1).value
if v and re.match(r'^\d+$', str(v).strip()):
data_rows += 1
if data_rows < min_rows:
return fail(f"G5 数据行数:仅 {data_rows} 行,需 ≥ {min_rows}")
return ok(f"G5 数据行数:{data_rows}")
except Exception as e:
return fail(f"G5 数据行数:打开失败 - {e}")
def check_g6(xlsx_path, campus):
"""G6: 文件是否已上传 Nextcloud"""
fname = os.path.basename(xlsx_path)
nc_path = f"/var/www/html/data/admin/files/小Maggie协作区/南通新东方/履约期内非集采合同-综办/房租物业合同/{campus}/{fname}"
r = subprocess.run(
["docker", "exec", "nextcloud-nextcloud-1", "test", "-f", nc_path],
capture_output=True
)
if r.returncode == 0:
return ok(f"G6 Nextcloud:已上传 {campus}/{fname}")
else:
return fail(f"G6 Nextcloud:未上传到 {campus}/{fname}。请先 docker cp + files:scan。")
def check_g7(xlsx_path):
"""G7: OCR占位符残留检测(【…】、〔待核PDF〕、OCR模糊 等)"""
patterns = [
r'\.{1,5}】', # 【…】、【...】
r'【…】', # 中文省略号
r'〔待核PDF〕', # 待核标记
r'〔待核〕',
r'OCR模糊', # OCR模糊标记
r'OCR无法识别',
r'OCR乱码',
r'待补全',
]
try:
wb = openpyxl.load_workbook(xlsx_path, data_only=True)
hits = []
for ws in wb.worksheets:
for r in range(1, ws.max_row + 1):
for c in range(1, ws.max_column + 1):
v = ws.cell(r, c).value
if not v:
continue
sv = str(v)
for pat in patterns:
if re.search(pat, sv):
col_letter = openpyxl.utils.get_column_letter(c)
m = re.search(pat, sv)
start = max(0, m.start() - 10)
end = min(len(sv), m.end() + 10)
ctx = sv[start:end].replace('\n', ' ')
hits.append(f" [{ws.title}] {col_letter}{r}: ...{ctx}...")
if hits:
msg = f"G7 OCR占位符残留:发现 {len(hits)} 处未补全的OCR标记\n" + "\n".join(hits[:5])
if len(hits) > 5:
msg += f"\n ...(共 {len(hits)} 处,仅显示前5处)"
msg += "\n 铁律:OCR读不出的必须用vision看原图补全,不能用占位符交付。"
return fail(msg)
return ok("G7 OCR占位符:无残留(全表扫描通过)")
except Exception as e:
return fail(f"G7 OCR占位符:检查失败 - {e}")
def check_g8(workdir):
"""G8: OCR完整性checkpoint(物理依赖链第一环)"""
checkpoint = os.path.join(workdir, 'step1.verified')
if not os.path.exists(checkpoint):
return fail(f"G8 OCR依赖链:checkpoint不存在\n 路径: {checkpoint}\n 说明: Step1 OCR完成后必须运行 ocr-integrity-check.py 生成checkpoint\n 动作: python3 scripts/ocr-integrity-check.py {workdir}")
# 读取checkpoint内容确认
with open(checkpoint, 'r', encoding='utf-8') as f:
content = f.read()
if 'OCR完整性检查通过' not in content:
return fail(f"G8 OCR依赖链:checkpoint内容异常\n {checkpoint} 未包含'OCR完整性检查通过'")
return ok(f"G8 OCR依赖链:checkpoint存在且有效")
def check_g9(workdir):
"""G9: 模版比对checkpoint(物理依赖链第二环)"""
checkpoint = os.path.join(workdir, 'step2b.verified')
if not os.path.exists(checkpoint):
return fail(f"G9 模版比对依赖链:checkpoint不存在\n 路径: {checkpoint}\n 说明: Step2 动作B必须delegate subagent做模版比对,然后运行验证脚本\n 动作: python3 scripts/template-diff-verify.py {workdir}")
# 读取checkpoint内容确认
with open(checkpoint, 'r', encoding='utf-8') as f:
content = f.read()
if '模版比对验证通过' not in content:
return fail(f"G9 模版比对依赖链:checkpoint内容异常\n {checkpoint} 未包含'模版比对验证通过'")
return ok(f"G9 模版比对依赖链:checkpoint存在且有效")
def main():
if len(sys.argv) < 4:
print("用法: python3 delivery-gate.py <xlsx路径> <工作目录> <校区名>")
print("例: python3 delivery-gate.py /tmp/金飞达/金飞达-梳理-MJ-20260627.xlsx /tmp/金飞达 金飞达")
sys.exit(1)
xlsx = sys.argv[1]
workdir = sys.argv[2]
campus = sys.argv[3]
if not os.path.exists(xlsx):
print(f"❌ 文件不存在: {xlsx}")
sys.exit(1)
bar = "=" * 60
print(bar)
print(f" 交付闸门 · {campus}")
print(bar)
print()
results = [
check_g1(workdir),
check_g2(xlsx),
check_g3(xlsx),
check_g4(xlsx),
check_g5(xlsx, min_rows=3),
check_g6(xlsx, campus),
check_g7(xlsx),
check_g8(workdir),
check_g9(workdir),
]
print()
print(bar)
passed = sum(1 for r in results if r)
total = len(results)
if all(results):
print(f" ✅ 全部通过 ({passed}/{total}) —— 可以交付")
print(bar)
sys.exit(0)
else:
print(f"{total - passed}/{total} 项未通过 —— 禁止交付,回去补 workflow")
print(bar)
sys.exit(1)
if __name__ == "__main__":
main()