范围(不含 design-story-foundation、docs/、humanization/README.md 等进行中改动):
1. 新增 harness/ 控制平面
- skill_harness.py 静态审计:32 个运行时 Skill 的 frontmatter/manifest/文档污染,当前 0 问题
- run_selected.py 选择性执行器:manifest 与磁盘一一对账、依赖阻断、
空跑与 skip-only 失败关闭、AST 测试形状门
- manifests/skills.json:32 个 Skill 的合同责任方与协作领域登记
- manifests/test-inventory.json:81 个测试资产登记
- specs/skill-testing.md 与 README.md:测试分层、证据边界与 harness 职责
2. 实现测试从 .claude/skills/*/scripts/ 迁至 tests/skills/<skill>/
- 71 个测试文件迁移并修复项目根与临时目录运行导入
- 数据库触发器测试宽泛异常收窄为 psycopg.errors.RaiseException
- 抽取离线大测试拆出真实 PG smoke(默认阻断,不计入离线通过)
- 抽取 presence 去重边界拆出独立测试:493 + 78 = 571 项检查不变
3. 运行时文档清理
- 13 个 SKILL.md 移除自测/离线验证段落、测试命令与测试文件事实源表述,
只保留运行时合同;业务运行合同、额度、授权与离线模式均保留
4. SoT 同步
- AGENTS.md:新增 Skill 领域索引(7 个合同责任方分组,覆盖 32 个运行时 Skill)
- 领域 07:测试入口改由 harness/manifests/ 登记,SKILL.md 不承载测试命令
- humanization 覆盖矩阵:活动测试路径同步迁移
验证证据: harness 自测 15 项 + runner 自测 13 项通过;静态审计 32 Skill / 0 问题;
73 个非数据库测试通过;8 个集成条目中 6 个 PostgreSQL 项被依赖门明确阻断;
py_compile 与 git diff --check 通过。未连接 PostgreSQL、网络、真实模型或额度。
已知边界: 真正 skill_behavior_eval 仍为 0,尚未验证任何 Skill 自然语言行为;
evaluate-frozen-replay 的 raw 存储边界冲突留待单独治理。
87 lines
3.8 KiB
Python
87 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
||
"""细纲统一合同回归:锁住「技能产出形」与「装配消费形」合一后的装配行为。
|
||
|
||
历史 bug:planning 技能曾只产 chapterGoal/keyEvents/entities/foreshadowing/... 这套
|
||
表达形字段,缺 writer 装配硬依赖的 sourceRef/hardConstraints/adjustableBeats/
|
||
declaredNewFacts,导致 _outline_contract 直接 AssemblyError——planner 产出装配不了。
|
||
统一合同(meta/schemas/fine_outline.yaml)把必填集定为两者并集后,按统一形状产出
|
||
必须能装配;缺任一 writer 必填字段必须失败关闭。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pathlib
|
||
import sys
|
||
import unittest
|
||
|
||
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3]
|
||
SCRIPT_DIR = PROJECT_ROOT / ".claude" / "skills" / "assemble-context" / "scripts"
|
||
sys.path.insert(0, str(SCRIPT_DIR))
|
||
|
||
from assemble_writer_context import AssemblyError, _outline_contract # noqa: E402
|
||
|
||
|
||
def unified_outline() -> dict:
|
||
"""按 meta/schemas/fine_outline.yaml 必填集构造的统一形状细纲。"""
|
||
|
||
return {
|
||
"targetChapter": 3,
|
||
"sourceRef": {"sourceId": "outline:volume_1", "sourceVersion": "v1"},
|
||
"chapterGoal": "茧出击后首次实战,吞食更强异种核心完成升级",
|
||
"hardConstraints": ["衔接第2章结尾:茧自行撕开舱门出击"],
|
||
"keyEvents": [
|
||
{"id": "event-1", "order": 1, "event": "搏杀中被压制",
|
||
"participants": ["林深"], "trigger": "锁定更强异种",
|
||
"resultDirection": "暴露升级前不足"},
|
||
],
|
||
"adjustableBeats": ["搏杀战术细节可调整"],
|
||
"declaredNewFacts": [
|
||
{"factId": "fact-ch3-1", "text": "茧吞食更强核心即升级",
|
||
"sourceRef": {"sourceId": "outline:volume_1", "sourceVersion": "v1"}},
|
||
],
|
||
"mustAppearEntities": ["林深"],
|
||
"chapterEndHook": "深渊更深处回应了这次进食",
|
||
# 推荐字段也在,但不影响装配子集
|
||
"foreshadowing": [{"action": "推进", "subject": "脑内声音", "evidence": "unknown"}],
|
||
"unknowns": [],
|
||
"assumptions": [],
|
||
}
|
||
|
||
|
||
class FineOutlineUnificationTest(unittest.TestCase):
|
||
def test_unified_shape_assembles_and_extracts_writer_subset(self):
|
||
result = _outline_contract(unified_outline())
|
||
# 装配只抽 writer 需要的四字段,表达形字段不倾倒给写手。
|
||
self.assertEqual(
|
||
set(result),
|
||
{"sourceRef", "hardConstraints", "adjustableBeats", "declaredNewFacts"},
|
||
)
|
||
self.assertEqual(result["hardConstraints"], ["衔接第2章结尾:茧自行撕开舱门出击"])
|
||
|
||
def test_legacy_skill_only_shape_fails_closed(self):
|
||
# 旧技能形:只有表达形字段,缺 writer 四必填——必须装配失败(复现原 bug)。
|
||
legacy = {
|
||
"targetChapter": 3,
|
||
"chapterGoal": "本章戏剧任务",
|
||
"keyEvents": [{"id": "e1", "order": 1, "event": "事件"}],
|
||
"entities": [{"name": "林深", "type": "character", "role": "主角"}],
|
||
"foreshadowing": [{"action": "埋", "subject": "伏笔", "evidence": "unknown"}],
|
||
"stateChanges": ["状态变化"],
|
||
"chapterEndHook": "钩子",
|
||
"unknowns": [],
|
||
"assumptions": [],
|
||
}
|
||
with self.assertRaises(AssemblyError):
|
||
_outline_contract(legacy)
|
||
|
||
def test_missing_any_writer_required_field_fails_closed(self):
|
||
for missing in ("sourceRef", "hardConstraints", "adjustableBeats", "declaredNewFacts"):
|
||
outline = unified_outline()
|
||
del outline[missing]
|
||
with self.assertRaises(AssemblyError, msg=f"缺 {missing} 应装配失败"):
|
||
_outline_contract(outline)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|