Skip to content

Commit d1fc09b

Browse files
yangzhaohuiclaude
andcommitted
质检闭环 + CI 加固 + check_sections 支持合法子型
质检闭环:document-qa skill 默认流程接入 --strict-facts 0 幻觉审计 (成稿后拿「非占位具体值」清单逐项核来源),并列入自动校验能力与示例。 CI 加固:.github/workflows/ci.yml 增 ruff 步骤,产物同步校验从 skill build --check 改为 build_all --check(覆盖在线 + 离线产物)。 check_sections 支持子型:新增 SUBTYPE_SIGNATURES + match_subtype——制式/年度 报告(凭「总体情况」标题)、公布式决定/公布令(凭「现予公布」短语)等结构与标准 模板不同的合法子型,命中签名即按子型校验、不再误报「缺章节」,并打印 INFO 说明。 配套单测。 ruff、build_all --check(skill 54 / offline 67)、pytest 64 均通过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5af99f9 commit d1fc09b

4 files changed

Lines changed: 60 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ jobs:
2323
- name: 安装依赖
2424
run: python -m pip install -r requirements.txt
2525

26-
- name: 校验产物与 prompts/ 主源同步
27-
run: python src/adapters/skill/build.py --check
26+
- name: 代码风格(ruff)
27+
run: |
28+
python -m pip install ruff
29+
python -m ruff check .
30+
31+
- name: 校验在线与离线产物均与 prompts/ 主源同步
32+
run: python src/scripts/build_all.py --check
2833

2934
- name: 运行测试
3035
run: python -m pytest -q

skills/document-qa/SKILL.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ prompts/doc-types/<id>-<文种>/spec.md # 各文种应有板块与职责(结
2424
- **层级结构**:标题跳级、`一是/二是/三是` 被当正式标题、10 页以内却下钻到三级标题等给出提醒。
2525
- **内容机检**:占位符残留、结尾用语是否与文种匹配。
2626
- **格式红线**(GB/T 9704):发文字号是否用六角括号〔〕且不加「第」不补零、成文日期月日是否补零、标题是否误加句号、是否残留「主题词」。
27+
- **0 幻觉审计**`--strict-facts`):列出成稿里**非占位**的具体值(文号/日期/百分比/金额/数量),供逐项核对来源——把「不编造」从口号变成可核对清单;提示性、不影响退出码。
2728
- `--strict-structure` 把结构提醒按错误处理。
2829

2930
```bash
3031
python3 src/scripts/check_sections.py notice 成稿.md
3132
python3 src/scripts/check_sections.py report 成稿.md --strict-structure
33+
python3 src/scripts/check_sections.py report 成稿.md --strict-facts # 0 幻觉核对:列出具体值供查来源
3234
```
3335

3436
## 人工核对(清单)
@@ -44,9 +46,10 @@ python3 src/scripts/check_sections.py report 成稿.md --strict-structure
4446
## 默认流程
4547

4648
1. 判断稿子文种 → 先跑 `check_sections.py <文种> 成稿.md` 拿结构结论(22 个文种均覆盖)。
47-
2. 再按「质量检查清单」做语义和事实核对。
48-
3. 输出「缺什么板块 / 哪里结构不规范 / 哪些表述疑似无依据」,并指明对应主源规则。
49-
4. 只报问题与依据,不擅自改写;需要修订时交回「公文写作」skill。
49+
2. 再加 `--strict-facts` 跑一遍 0 幻觉审计,拿到「非占位具体值」清单,逐项核对是否有可核实来源(无来源的应改占位或标「待核实」)。
50+
3. 再按「质量检查清单」做语义核对。
51+
4. 输出「缺什么板块 / 哪里结构不规范 / 哪些具体值疑似无依据」,并指明对应主源规则。
52+
5. 只报问题与依据,不擅自改写;需要修订时交回「公文写作」skill。
5053

5154
## 边界
5255

src/scripts/check_sections.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
"work-plan": ["标题", "制定背景", "总体要求", "工作目标", "主要任务", "实施步骤", "保障措施"],
3939
}
4040

41+
# 合法子型:结构与标准模板不同的真实写法(见对应 spec.md 撰写思路)。命中「签名」即视为
42+
# 该子型,放过标准模板章节缺失、改为提示,避免拿制式年报、公布式决定等去校验时误报缺章节。
43+
# 签名为 headings(须出现的标题集,子集匹配)或 phrase(正文标志短语)。
44+
SUBTYPE_SIGNATURES: dict[str, list[tuple[str, dict]]] = {
45+
"report": [("制式/年度报告", {"headings": {"总体情况"}})],
46+
"decision": [("公布式决定", {"phrase": "现予公布"})],
47+
"order": [("公布令", {"phrase": "现予公布"})],
48+
}
49+
50+
4151
# 各文种常见结尾用语(与 prompts/core/drafting-thinking.md、style.md 一致)。
4252
# 只收录有明确套语的文种;用于成稿结尾用语核对,提示性、不作硬错误。
4353
ENDING_PHRASES = {
@@ -221,6 +231,16 @@ def check_format_redlines(content: str) -> list[str]:
221231
return warnings
222232

223233

234+
def match_subtype(doc_type: str, content: str, headings: set[str]) -> str | None:
235+
"""命中合法子型签名时返回子型名,否则 None;用于放过标准模板章节缺失。"""
236+
for name, signature in SUBTYPE_SIGNATURES.get(doc_type, []):
237+
if "headings" in signature and signature["headings"] <= headings:
238+
return name
239+
if "phrase" in signature and signature["phrase"] in content:
240+
return name
241+
return None
242+
243+
224244
def audit_unsourced_specifics(content: str) -> list[str]:
225245
"""0 幻觉审计:列出成稿里非占位的具体值(文号/日期/百分比/金额/数量),供逐项核对来源。"""
226246
findings: list[str] = []
@@ -252,11 +272,18 @@ def main() -> int:
252272
missing = [section for section in REQUIRED_SECTIONS[args.doc_type] if section not in headings]
253273
structure_warnings = check_heading_structure(content)
254274

275+
subtype = None
255276
if missing:
256-
print(f"[ERROR] {args.file} 缺少以下章节:")
257-
for section in missing:
258-
print(f"- {section}")
259-
return 1
277+
subtype = match_subtype(args.doc_type, content, headings)
278+
if subtype is None:
279+
print(f"[ERROR] {args.file} 缺少以下章节:")
280+
for section in missing:
281+
print(f"- {section}")
282+
return 1
283+
print(
284+
f"[INFO] {args.file} 识别为「{subtype}」子型,标准模板章节"
285+
f"(缺 {'、'.join(missing)})不强制,见 {args.doc_type} 的 spec.md 撰写思路。"
286+
)
260287

261288
if structure_warnings and args.strict_structure:
262289
print(f"[ERROR] {args.file} 层级结构存在以下问题:")
@@ -271,7 +298,8 @@ def main() -> int:
271298
+ check_title_punctuation(content)
272299
)
273300

274-
print(f"[OK] {args.file} 章节完整,类型:{args.doc_type}")
301+
status = f"按「{subtype}」子型校验通过" if subtype else "章节完整"
302+
print(f"[OK] {args.file} {status},类型:{args.doc_type}")
275303
if structure_warnings:
276304
print("[WARN] 检测到以下层级结构提醒:")
277305
for item in structure_warnings:

tests/test_template_sections.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
check_title_punctuation,
2020
collect_markdown_headings,
2121
detect_heading_levels,
22+
match_subtype,
2223
)
2324

2425

@@ -116,6 +117,19 @@ def test_format_redlines_detect_common_errors(self) -> None:
116117
self.assertTrue(check_format_redlines("主题词:公文 格式")) # 主题词残留
117118
self.assertEqual(check_format_redlines("国办发〔2026〕5号,2026年6月1日"), [])
118119

120+
def test_subtype_signature_matches_real_variants(self) -> None:
121+
# 制式年报凭「总体情况」标题命中子型;普通报告无签名则不命中。
122+
self.assertEqual(
123+
match_subtype("report", "正文", {"标题", "总体情况", "其他需要报告的事项"}),
124+
"制式/年度报告",
125+
)
126+
self.assertIsNone(match_subtype("report", "正文", {"标题", "主送单位"}))
127+
# 公布式决定凭正文短语「现予公布」命中。
128+
self.assertEqual(
129+
match_subtype("decision", "已经常务会议通过,现予公布,自……起施行。", set()),
130+
"公布式决定",
131+
)
132+
119133
def test_audit_flags_concrete_values_but_skips_placeholders(self) -> None:
120134
# 非占位的具体值应被列出(0 幻觉核对)。
121135
hits = audit_unsourced_specifics(

0 commit comments

Comments
 (0)