muse-agent-example/.claude/skills/deconstruct-book/scripts/test_parse_outline_offline.py
zizi b0bc7a8745 框架: 技能按动作-对象重组 + 先审后入创作闭环
一、技能重组(动作-对象命名)
- 旧目录 clean/confirm/continuation/db/detect/embed/… 重组为
  clean-book-text/decide-candidate/write-next-chapter/access-database/
  check-content-consistency/embed-knowledge/…(git 识别为 rename,内容保持)
- agents/*.md、AGENTS.md/CLAUDE.md 收编、example_skill 登记表同步新名

二、先审后入创作闭环(本次核心)
正文接受从"机械门一过就写正典"改为"机械门+语义审查双通过+用户批准+单事务原子提交",
DB 级兜底,编排层跳步即被硬拒。
- candidate_cas.py + example_candidate_cas(109):持久化 CAS 状态链
- fact_delta.py + example_fact_delta/example_fact_ledger(106):结构化事实增量,
  模型只提六型闭集增量+正文证据引文,仅用户批准的增量随正文同事务入账本
- projection_registry.py + example_projection_run(107):投影登记与恢复
- acceptance_state.py:接受前置实时状态重读
- lesson_registry.py + example_lesson(108):经验升格链,禁止自动升格
- DDL 105:example_candidate 增 semantic_status/semantic_report_sha256
- write_canonical.accept:语义兜底+同事务合并增量+登记投影;
  run_writer_pipeline/persist_writer_run/run_writer_semantic_detector/step2 接入全链
- claude_runtime:兼容新 CLI modelUsage 信息字段

三、审查修复(独立子代理四维审查后)
- 事实增量 propose→approve 翻态正道,不撞唯一键
- 冻结配置探针重刷(CLI 2.1.211→2.1.231 漂移),profileSha256/adapterVersion 再登记
- 可视化合同悬空路径/五六空间矛盾、 SoT 旧技能名漂移、行尾空白清理

测试:离线 65 套 + 真实库集成 5 套(CAS/接受故障注入/事实增量/投影/经验升格)+ 回放 79 项全绿。
创作内容(docs/design、生成正文 artifacts)按"框架与创作分开"未入本提交。
2026-08-14 10:24:08 +08:00

98 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""parse_outline 窗覆盖门离线测试:不连库、不调用模型。"""
import pathlib
import sys
import click
HERE = pathlib.Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
import parse_outline as po # noqa: E402
def check(name, condition):
"""输出与现有 parse-book 离线测试一致的简明结果。"""
if not condition:
raise AssertionError(name)
print(f"[PASS] {name}")
def main():
"""覆盖连续、头部、中部、尾部和重叠窗五类边界。"""
check("coverage-连续无缝", po.outline_coverage_gaps([(1, 33), (34, 65)], 1, 65) == [])
check(
"coverage-识别中部缝隙",
po.outline_coverage_gaps([(951, 983), (1015, 1047)], 951, 1047) == [(984, 1014)],
)
check("coverage-识别头部缝隙", po.outline_coverage_gaps([(5, 10)], 1, 10) == [(1, 4)])
check("coverage-识别尾部缝隙", po.outline_coverage_gaps([(1, 6)], 1, 10) == [(7, 10)])
check(
"coverage-重叠窗不误报",
po.outline_coverage_gaps([(1, 20), (15, 30), (31, 40)], 1, 40) == [],
)
po.ensure_outline_coverage([(1, 33), (34, 65)], 1, 65, context="测试")
try:
po.ensure_outline_coverage([(951, 983), (1015, 1047)], 951, 1047, context="测试")
except click.ClickException as error:
check("coverage-缝隙非零阻断", "984-1014" in str(error))
else:
raise AssertionError("coverage-缝隙非零阻断")
original_chat = po.chat_degrade
try:
replies = iter([("{}", {}), ('{"outline":"补窗成功"}', {"prompt_tokens": 1})])
po.chat_degrade = lambda _prompt, _model: next(replies)
outline, usage = po.generate_outline_with_retry("prompt", "model", max_attempts=2)
check("window-空输出重试后成功", outline == "补窗成功" and usage["prompt_tokens"] == 1)
calls = {"count": 0}
def always_empty(_prompt, _model):
calls["count"] += 1
return "{}", {}
po.chat_degrade = always_empty
try:
po.generate_outline_with_retry("prompt", "model", max_attempts=2)
except click.ClickException as error:
check("window-有限重试耗尽即停书", calls["count"] == 2 and "2 次" in str(error))
else:
raise AssertionError("window-有限重试耗尽即停书")
def exhausted_chain(_prompt, _model):
raise po.SensitiveError("全部渠道耗尽")
po.chat_degrade = exhausted_chain
try:
po.generate_outline_with_retry("prompt", "model", max_attempts=2)
except click.ClickException as error:
check("window-治理链耗尽即停书", "治理链耗尽" in str(error))
else:
raise AssertionError("window-治理链耗尽即停书")
finally:
po.chat_degrade = original_chat
unordered = [
(3, 83, 984, 1014, "补插窗", "revised"),
(1, 1, 1, 33, "首窗", "revised"),
(4, 52, 1015, 1047, "后续窗", "revised"),
(2, 82, 2226, 2250, "末窗", "revised"),
]
ordered = po.order_outline_windows(unordered)
check("check-按绝对章号排序", [row[2] for row in ordered] == [1, 984, 1015, 2226])
rendered = po.render_coherence_outlines(ordered)
check(
"check-阶段号按章序重编",
"【阶段2(第984–1014章)】" in rendered and "【阶段83" not in rendered,
)
print("\n全部离线自测通过:11 项(未连库、未发任何 LLM 调用)")
if __name__ == "__main__":
main()