paperfit-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/adjust-length.md +21 -0
- package/.claude/commands/check-visual.md +27 -0
- package/.claude/commands/fix-layout.md +31 -0
- package/.claude/commands/migrate-template.md +23 -0
- package/.claude/commands/repair-table.md +21 -0
- package/.claude/commands/show-status.md +32 -0
- package/.claude-plugin/README.md +77 -0
- package/.claude-plugin/marketplace.json +41 -0
- package/.claude-plugin/plugin.json +39 -0
- package/CLAUDE.md +266 -0
- package/CONTRIBUTING.md +131 -0
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/agents/code-surgeon-agent.md +214 -0
- package/agents/layout-detective-agent.md +229 -0
- package/agents/orchestrator-agent.md +254 -0
- package/agents/quality-gatekeeper-agent.md +270 -0
- package/agents/rule-engine-agent.md +224 -0
- package/agents/semantic-polish-agent.md +250 -0
- package/bin/paperfit.js +176 -0
- package/config/agent_roles.yaml +56 -0
- package/config/layout_rules.yaml +54 -0
- package/config/templates.yaml +241 -0
- package/config/vto_taxonomy.yaml +489 -0
- package/config/writing_rules.yaml +64 -0
- package/install.sh +30 -0
- package/package.json +52 -0
- package/requirements.txt +5 -0
- package/scripts/benchmark_runner.py +629 -0
- package/scripts/compile.sh +244 -0
- package/scripts/config_validator.py +339 -0
- package/scripts/cv_detector.py +600 -0
- package/scripts/evidence_collector.py +167 -0
- package/scripts/float_fixers.py +861 -0
- package/scripts/inject_defects.py +549 -0
- package/scripts/install-claude-global.js +148 -0
- package/scripts/install.js +66 -0
- package/scripts/install.sh +106 -0
- package/scripts/overflow_fixers.py +656 -0
- package/scripts/package-for-opensource.sh +138 -0
- package/scripts/parse_log.py +260 -0
- package/scripts/postinstall.js +38 -0
- package/scripts/pre_tool_use.py +265 -0
- package/scripts/render_pages.py +244 -0
- package/scripts/session_logger.py +329 -0
- package/scripts/space_util_fixers.py +773 -0
- package/scripts/state_manager.py +352 -0
- package/scripts/test_commands.py +187 -0
- package/scripts/test_cv_detector.py +214 -0
- package/scripts/test_integration.py +290 -0
- package/skills/consistency-polisher/SKILL.md +337 -0
- package/skills/float-optimizer/SKILL.md +284 -0
- package/skills/latex_fixers/__init__.py +82 -0
- package/skills/latex_fixers/float_fixers.py +392 -0
- package/skills/latex_fixers/fullwidth_fixers.py +375 -0
- package/skills/latex_fixers/overflow_fixers.py +250 -0
- package/skills/latex_fixers/semantic_micro_tuning.py +362 -0
- package/skills/latex_fixers/space_util_fixers.py +389 -0
- package/skills/latex_fixers/utils.py +55 -0
- package/skills/overflow-repair/SKILL.md +304 -0
- package/skills/space-util-fixer/SKILL.md +307 -0
- package/skills/taxonomy-vto/SKILL.md +486 -0
- package/skills/template-migrator/SKILL.md +251 -0
- package/skills/visual-inspector/SKILL.md +217 -0
- package/skills/writing-polish/SKILL.md +289 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CV Detector 测试脚本
|
|
4
|
+
|
|
5
|
+
用于验证 cv_detector.py 对 VTO Benchmark 样本的检测能力。
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
python scripts/test_cv_detector.py
|
|
9
|
+
|
|
10
|
+
输出:
|
|
11
|
+
- 各类别缺陷检测成功率
|
|
12
|
+
- 漏报/误报分析
|
|
13
|
+
- 检测性能指标
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
23
|
+
from cv_detector import BatchCVDetector, CVDefectDetector
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class GroundTruth:
|
|
28
|
+
"""基准真相"""
|
|
29
|
+
category: str
|
|
30
|
+
defect_id: str
|
|
31
|
+
page: int
|
|
32
|
+
expected: bool # 是否应该被检测到
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# 基准真相定义
|
|
36
|
+
# 根据 inject_defects.py 注入的缺陷
|
|
37
|
+
# 注意:我们检测的是"视觉表现"而非"注入原因"
|
|
38
|
+
# 例如:B2-float-width(1.5 线宽)的视觉表现是 D1-overfull-hbox
|
|
39
|
+
GROUND_TRUTH = {
|
|
40
|
+
"cat_a": [
|
|
41
|
+
GroundTruth("A", "A1-widow-orphan", page=0, expected=True), # 每页都有短行
|
|
42
|
+
GroundTruth("A", "A2-trailing-whitespace", page=3, expected=True), # 末页留白
|
|
43
|
+
],
|
|
44
|
+
"cat_b": [
|
|
45
|
+
# B1 和 B2 的视觉表现已在 Category D 中通过溢出检测捕获
|
|
46
|
+
# 这里只检查是否有 D 类溢出(来自 B2 的 1.5 线宽)
|
|
47
|
+
GroundTruth("D", "D1-overfull-hbox", page=1, expected=True), # B2 导致的溢出
|
|
48
|
+
],
|
|
49
|
+
"cat_d": [
|
|
50
|
+
GroundTruth("D", "D1-overfull-hbox", page=0, expected=True), # 长单词溢出
|
|
51
|
+
# D3 URL 溢出与 D1 在视觉上不可区分,不单独检查
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run_detection(pages_dir: str) -> dict:
|
|
57
|
+
"""运行批量检测"""
|
|
58
|
+
detector = BatchCVDetector(pages_dir)
|
|
59
|
+
return detector.run_batch()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def evaluate_detection(report: dict, ground_truth: List[GroundTruth]) -> dict:
|
|
63
|
+
"""评估检测结果
|
|
64
|
+
|
|
65
|
+
检测策略:只要某个缺陷类型在任何页面被检测到,就视为该缺陷类别被检出
|
|
66
|
+
"""
|
|
67
|
+
tp = 0 # True Positive
|
|
68
|
+
fp = 0 # False Positive
|
|
69
|
+
fn = 0 # False Negative
|
|
70
|
+
|
|
71
|
+
# 收集所有检测到的缺陷类型(不区分页码)
|
|
72
|
+
detected_types = set()
|
|
73
|
+
for page_result in report["page_results"]:
|
|
74
|
+
for defect in page_result["detections"]:
|
|
75
|
+
detected_types.add((defect["category"], defect["defect_id"]))
|
|
76
|
+
|
|
77
|
+
# 检查每个基准真相
|
|
78
|
+
for gt in ground_truth:
|
|
79
|
+
# 检查该缺陷类型是否在任何页面被检测到
|
|
80
|
+
is_detected = (gt.category, gt.defect_id) in detected_types
|
|
81
|
+
|
|
82
|
+
if gt.expected and is_detected:
|
|
83
|
+
tp += 1
|
|
84
|
+
elif gt.expected and not is_detected:
|
|
85
|
+
fn += 1
|
|
86
|
+
print(f" [漏报] {gt.defect_id}")
|
|
87
|
+
elif not gt.expected and is_detected:
|
|
88
|
+
fp += 1
|
|
89
|
+
|
|
90
|
+
# 计算指标
|
|
91
|
+
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
|
|
92
|
+
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
|
|
93
|
+
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"true_positives": tp,
|
|
97
|
+
"false_positives": fp,
|
|
98
|
+
"false_negatives": fn,
|
|
99
|
+
"precision": precision,
|
|
100
|
+
"recall": recall,
|
|
101
|
+
"f1_score": f1,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def print_detection_summary(report: dict):
|
|
106
|
+
"""打印检测摘要"""
|
|
107
|
+
print(f"\nPages analyzed: {report['pages_analyzed']}")
|
|
108
|
+
print(f"Total detections: {report['total_detections']}")
|
|
109
|
+
print(f"Category breakdown: {report['category_breakdown']}")
|
|
110
|
+
|
|
111
|
+
for page_result in report["page_results"]:
|
|
112
|
+
if page_result["detections"]:
|
|
113
|
+
page_num = page_result["page_number"]
|
|
114
|
+
count = page_result["detection_count"]
|
|
115
|
+
print(f"\nPage {page_num}: {count} detection(s)")
|
|
116
|
+
for d in page_result["detections"]:
|
|
117
|
+
print(f" - [{d['category']}] {d['defect_id']}: {d['description']}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
"""主函数"""
|
|
122
|
+
samples_dir = Path(__file__).parent.parent / "data" / "benchmarks" / "samples"
|
|
123
|
+
|
|
124
|
+
print("=" * 60)
|
|
125
|
+
print("CV Detector 测试报告")
|
|
126
|
+
print("=" * 60)
|
|
127
|
+
|
|
128
|
+
all_results = {}
|
|
129
|
+
|
|
130
|
+
# 测试 Category A
|
|
131
|
+
print("\n### Category A: 空间利用缺陷")
|
|
132
|
+
cat_a_dir = samples_dir / "pages_a"
|
|
133
|
+
if cat_a_dir.exists():
|
|
134
|
+
report_a = run_detection(str(cat_a_dir))
|
|
135
|
+
print_detection_summary(report_a)
|
|
136
|
+
eval_a = evaluate_detection(report_a, GROUND_TRUTH["cat_a"])
|
|
137
|
+
all_results["cat_a"] = eval_a
|
|
138
|
+
print(f"\n评估指标:")
|
|
139
|
+
print(f" Precision: {eval_a['precision']:.2%}")
|
|
140
|
+
print(f" Recall: {eval_a['recall']:.2%}")
|
|
141
|
+
print(f" F1 Score: {eval_a['f1_score']:.2%}")
|
|
142
|
+
else:
|
|
143
|
+
print(f" [跳过] {cat_a_dir} 不存在")
|
|
144
|
+
|
|
145
|
+
# 测试 Category B
|
|
146
|
+
print("\n### Category B: 浮动体缺陷")
|
|
147
|
+
cat_b_dir = samples_dir / "pages_b"
|
|
148
|
+
if cat_b_dir.exists():
|
|
149
|
+
report_b = run_detection(str(cat_b_dir))
|
|
150
|
+
print_detection_summary(report_b)
|
|
151
|
+
eval_b = evaluate_detection(report_b, GROUND_TRUTH["cat_b"])
|
|
152
|
+
all_results["cat_b"] = eval_b
|
|
153
|
+
print(f"\n评估指标:")
|
|
154
|
+
print(f" Precision: {eval_b['precision']:.2%}")
|
|
155
|
+
print(f" Recall: {eval_b['recall']:.2%}")
|
|
156
|
+
print(f" F1 Score: {eval_b['f1_score']:.2%}")
|
|
157
|
+
else:
|
|
158
|
+
print(f" [跳过] {cat_b_dir} 不存在")
|
|
159
|
+
|
|
160
|
+
# 测试 Category D
|
|
161
|
+
print("\n### Category D: 溢出缺陷")
|
|
162
|
+
cat_d_dir = samples_dir / "pages_d"
|
|
163
|
+
if cat_d_dir.exists():
|
|
164
|
+
report_d = run_detection(str(cat_d_dir))
|
|
165
|
+
print_detection_summary(report_d)
|
|
166
|
+
eval_d = evaluate_detection(report_d, GROUND_TRUTH["cat_d"])
|
|
167
|
+
all_results["cat_d"] = eval_d
|
|
168
|
+
print(f"\n评估指标:")
|
|
169
|
+
print(f" Precision: {eval_d['precision']:.2%}")
|
|
170
|
+
print(f" Recall: {eval_d['recall']:.2%}")
|
|
171
|
+
print(f" F1 Score: {eval_d['f1_score']:.2%}")
|
|
172
|
+
else:
|
|
173
|
+
print(f" [跳过] {cat_d_dir} 不存在")
|
|
174
|
+
|
|
175
|
+
# 总体汇总
|
|
176
|
+
print("\n" + "=" * 60)
|
|
177
|
+
print("总体汇总")
|
|
178
|
+
print("=" * 60)
|
|
179
|
+
|
|
180
|
+
total_tp = sum(r["true_positives"] for r in all_results.values())
|
|
181
|
+
total_fp = sum(r["false_positives"] for r in all_results.values())
|
|
182
|
+
total_fn = sum(r["false_negatives"] for r in all_results.values())
|
|
183
|
+
|
|
184
|
+
overall_precision = total_tp / (total_tp + total_fp) if (total_tp + total_fp) > 0 else 0
|
|
185
|
+
overall_recall = total_tp / (total_tp + total_fn) if (total_tp + total_fn) > 0 else 0
|
|
186
|
+
overall_f1 = 2 * overall_precision * overall_recall / (overall_precision + overall_recall) if (overall_precision + overall_recall) > 0 else 0
|
|
187
|
+
|
|
188
|
+
print(f"\n总检测数: {total_tp + total_fp} (TP={total_tp}, FP={total_fp}, FN={total_fn})")
|
|
189
|
+
print(f"总体 Precision: {overall_precision:.2%}")
|
|
190
|
+
print(f"总体 Recall: {overall_recall:.2%}")
|
|
191
|
+
print(f"总体 F1 Score: {overall_f1:.2%}")
|
|
192
|
+
|
|
193
|
+
# 保存结果
|
|
194
|
+
output_file = samples_dir / "cv_detection_results.json"
|
|
195
|
+
output_data = {
|
|
196
|
+
"category_results": all_results,
|
|
197
|
+
"overall": {
|
|
198
|
+
"true_positives": total_tp,
|
|
199
|
+
"false_positives": total_fp,
|
|
200
|
+
"false_negatives": total_fn,
|
|
201
|
+
"precision": overall_precision,
|
|
202
|
+
"recall": overall_recall,
|
|
203
|
+
"f1_score": overall_f1,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
207
|
+
json.dump(output_data, f, indent=2, ensure_ascii=False)
|
|
208
|
+
print(f"\n结果已保存到:{output_file}")
|
|
209
|
+
|
|
210
|
+
return 0 if overall_f1 >= 0.7 else 1
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == "__main__":
|
|
214
|
+
sys.exit(main())
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PaperFit 系统集成测试
|
|
4
|
+
|
|
5
|
+
验证 commands、agents 和 skills 之间的集成关系。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Set, Tuple
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IntegrationTester:
|
|
15
|
+
"""PaperFit 集成测试器"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, root_dir: str = "."):
|
|
18
|
+
self.root = Path(root_dir)
|
|
19
|
+
self.results: List[Tuple[str, bool, str]] = []
|
|
20
|
+
|
|
21
|
+
def test_command_agent_links(self) -> bool:
|
|
22
|
+
"""测试 commands 到 agents 的引用链路"""
|
|
23
|
+
print("\n" + "=" * 60)
|
|
24
|
+
print("测试:Commands → Agents 引用链路")
|
|
25
|
+
print("=" * 60)
|
|
26
|
+
|
|
27
|
+
commands_dir = self.root / ".claude" / "commands"
|
|
28
|
+
agents_dir = self.root / "agents"
|
|
29
|
+
|
|
30
|
+
# 获取所有 agent 文件名
|
|
31
|
+
available_agents = {f.name for f in agents_dir.glob("*.md")}
|
|
32
|
+
print(f"\n可用 Agents: {sorted(available_agents)}")
|
|
33
|
+
|
|
34
|
+
# 检查每个命令文件
|
|
35
|
+
for cmd_file in commands_dir.glob("*.md"):
|
|
36
|
+
content = cmd_file.read_text()
|
|
37
|
+
|
|
38
|
+
# 提取引用的 agents
|
|
39
|
+
agent_refs = set(re.findall(r'agents/([\w-]+\.md)', content))
|
|
40
|
+
|
|
41
|
+
if agent_refs:
|
|
42
|
+
missing = agent_refs - available_agents
|
|
43
|
+
if missing:
|
|
44
|
+
self.results.append((
|
|
45
|
+
f"commands/{cmd_file.name}",
|
|
46
|
+
False,
|
|
47
|
+
f"引用不存在的 agent: {missing}"
|
|
48
|
+
))
|
|
49
|
+
else:
|
|
50
|
+
self.results.append((
|
|
51
|
+
f"commands/{cmd_file.name}",
|
|
52
|
+
True,
|
|
53
|
+
f"引用 agents: {sorted(agent_refs)}"
|
|
54
|
+
))
|
|
55
|
+
else:
|
|
56
|
+
# 有些命令可能不引用 agent(如 show-status)
|
|
57
|
+
self.results.append((
|
|
58
|
+
f"commands/{cmd_file.name}",
|
|
59
|
+
True,
|
|
60
|
+
"未引用特定 agent(可能是查询类命令)"
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
return all(r[1] for r in self.results if r[0].startswith("commands/"))
|
|
64
|
+
|
|
65
|
+
def test_agent_skill_links(self) -> bool:
|
|
66
|
+
"""测试 agents 到 skills 的引用链路"""
|
|
67
|
+
print("\n" + "=" * 60)
|
|
68
|
+
print("测试:Agents → Skills 引用链路")
|
|
69
|
+
print("=" * 60)
|
|
70
|
+
|
|
71
|
+
skills_dir = self.root / "skills"
|
|
72
|
+
|
|
73
|
+
# 获取所有 skill 目录名
|
|
74
|
+
available_skills = {
|
|
75
|
+
d.name for d in skills_dir.iterdir()
|
|
76
|
+
if d.is_dir() and (d / "SKILL.md").exists()
|
|
77
|
+
}
|
|
78
|
+
print(f"\n可用 Skills: {sorted(available_skills)}")
|
|
79
|
+
|
|
80
|
+
# 检查每个 agent 文件
|
|
81
|
+
agents_dir = self.root / "agents"
|
|
82
|
+
for agent_file in agents_dir.glob("*.md"):
|
|
83
|
+
content = agent_file.read_text()
|
|
84
|
+
|
|
85
|
+
# 提取引用的 skills
|
|
86
|
+
skill_refs = set(re.findall(r'[\w-]+', content))
|
|
87
|
+
matched_skills = skill_refs & available_skills
|
|
88
|
+
|
|
89
|
+
if matched_skills:
|
|
90
|
+
self.results.append((
|
|
91
|
+
f"agents/{agent_file.name}",
|
|
92
|
+
True,
|
|
93
|
+
f"引用 skills: {sorted(matched_skills)}"
|
|
94
|
+
))
|
|
95
|
+
else:
|
|
96
|
+
self.results.append((
|
|
97
|
+
f"agents/{agent_file.name}",
|
|
98
|
+
True,
|
|
99
|
+
"未显式引用特定 skill(可能是调度类 agent)"
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
return True # 即使没有引用 skill 也不算错误
|
|
103
|
+
|
|
104
|
+
def test_vto_taxonomy_coverage(self) -> bool:
|
|
105
|
+
"""测试 VTO 分类体系的完整性"""
|
|
106
|
+
print("\n" + "=" * 60)
|
|
107
|
+
print("测试:VTO 分类体系覆盖")
|
|
108
|
+
print("=" * 60)
|
|
109
|
+
|
|
110
|
+
taxonomy_file = self.root / "config" / "vto_taxonomy.yaml"
|
|
111
|
+
|
|
112
|
+
if not taxonomy_file.exists():
|
|
113
|
+
self.results.append((
|
|
114
|
+
"config/vto_taxonomy.yaml",
|
|
115
|
+
False,
|
|
116
|
+
"文件不存在"
|
|
117
|
+
))
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
content = taxonomy_file.read_text()
|
|
121
|
+
|
|
122
|
+
# 检查是否包含所有 Category
|
|
123
|
+
required_categories = ["Category A", "Category B", "Category C", "Category D", "Category E"]
|
|
124
|
+
missing_categories = [cat for cat in required_categories if cat not in content]
|
|
125
|
+
|
|
126
|
+
if missing_categories:
|
|
127
|
+
self.results.append((
|
|
128
|
+
"config/vto_taxonomy.yaml",
|
|
129
|
+
False,
|
|
130
|
+
f"缺少分类: {missing_categories}"
|
|
131
|
+
))
|
|
132
|
+
else:
|
|
133
|
+
self.results.append((
|
|
134
|
+
"config/vto_taxonomy.yaml",
|
|
135
|
+
True,
|
|
136
|
+
"包含全部 5 个 VTO 分类"
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
return len(missing_categories) == 0
|
|
140
|
+
|
|
141
|
+
def test_state_manager_integration(self) -> bool:
|
|
142
|
+
"""测试 state_manager.py 的集成"""
|
|
143
|
+
print("\n" + "=" * 60)
|
|
144
|
+
print("测试:State Manager 集成")
|
|
145
|
+
print("=" * 60)
|
|
146
|
+
|
|
147
|
+
state_manager = self.root / "scripts" / "state_manager.py"
|
|
148
|
+
|
|
149
|
+
if not state_manager.exists():
|
|
150
|
+
self.results.append((
|
|
151
|
+
"scripts/state_manager.py",
|
|
152
|
+
False,
|
|
153
|
+
"文件不存在"
|
|
154
|
+
))
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
content = state_manager.read_text()
|
|
158
|
+
|
|
159
|
+
# 检查关键功能
|
|
160
|
+
checks = {
|
|
161
|
+
"_backup": "备份功能",
|
|
162
|
+
"_backup_directory": "目录备份功能",
|
|
163
|
+
"case_dir": "Case 目录支持",
|
|
164
|
+
"data/state.json": "状态文件路径"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
missing = [name for pattern, name in checks.items() if pattern not in content]
|
|
168
|
+
|
|
169
|
+
if missing:
|
|
170
|
+
self.results.append((
|
|
171
|
+
"scripts/state_manager.py",
|
|
172
|
+
False,
|
|
173
|
+
f"缺少功能: {missing}"
|
|
174
|
+
))
|
|
175
|
+
else:
|
|
176
|
+
self.results.append((
|
|
177
|
+
"scripts/state_manager.py",
|
|
178
|
+
True,
|
|
179
|
+
"所有关键功能已实现"
|
|
180
|
+
))
|
|
181
|
+
|
|
182
|
+
return len(missing) == 0
|
|
183
|
+
|
|
184
|
+
def test_compile_script(self) -> bool:
|
|
185
|
+
"""测试编译脚本"""
|
|
186
|
+
print("\n" + "=" * 60)
|
|
187
|
+
print("测试:编译脚本")
|
|
188
|
+
print("=" * 60)
|
|
189
|
+
|
|
190
|
+
compile_script = self.root / "scripts" / "compile.sh"
|
|
191
|
+
|
|
192
|
+
if not compile_script.exists():
|
|
193
|
+
self.results.append((
|
|
194
|
+
"scripts/compile.sh",
|
|
195
|
+
False,
|
|
196
|
+
"文件不存在"
|
|
197
|
+
))
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
content = compile_script.read_text()
|
|
201
|
+
|
|
202
|
+
# 检查是否包含必要的编译命令
|
|
203
|
+
if "latexmk" in content or "pdflatex" in content:
|
|
204
|
+
self.results.append((
|
|
205
|
+
"scripts/compile.sh",
|
|
206
|
+
True,
|
|
207
|
+
"包含 LaTeX 编译命令"
|
|
208
|
+
))
|
|
209
|
+
else:
|
|
210
|
+
self.results.append((
|
|
211
|
+
"scripts/compile.sh",
|
|
212
|
+
False,
|
|
213
|
+
"缺少 LaTeX 编译命令"
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
def generate_report(self) -> str:
|
|
219
|
+
"""生成测试报告"""
|
|
220
|
+
passed = sum(1 for r in self.results if r[1])
|
|
221
|
+
failed = sum(1 for r in self.results if not r[1])
|
|
222
|
+
|
|
223
|
+
report = "\n" + "=" * 60 + "\n"
|
|
224
|
+
report += "PaperFit 集成测试报告\n"
|
|
225
|
+
report + "=" * 60 + "\n\n"
|
|
226
|
+
|
|
227
|
+
report += f"通过:{passed}\n"
|
|
228
|
+
report += f"失败:{failed}\n\n"
|
|
229
|
+
|
|
230
|
+
if failed > 0:
|
|
231
|
+
report += "失败详情:\n"
|
|
232
|
+
for path, success, msg in self.results:
|
|
233
|
+
if not success:
|
|
234
|
+
report += f" ✗ {path}: {msg}\n"
|
|
235
|
+
report += "\n"
|
|
236
|
+
|
|
237
|
+
report += "=" * 60 + "\n"
|
|
238
|
+
report += "系统组件摘要\n"
|
|
239
|
+
report + "=" * 60 + "\n\n"
|
|
240
|
+
|
|
241
|
+
# Commands
|
|
242
|
+
commands_dir = self.root / ".claude" / "commands"
|
|
243
|
+
cmd_files = list(commands_dir.glob("*.md")) if commands_dir.exists() else []
|
|
244
|
+
report += f"Commands: {len(cmd_files)} 个\n"
|
|
245
|
+
for cmd in cmd_files:
|
|
246
|
+
content = cmd.read_text()
|
|
247
|
+
cmd_name = re.search(r'# /(\S+)', content)
|
|
248
|
+
if cmd_name:
|
|
249
|
+
report += f" - /{cmd_name.group(1)}\n"
|
|
250
|
+
|
|
251
|
+
# Agents
|
|
252
|
+
agents_dir = self.root / "agents"
|
|
253
|
+
agent_files = list(agents_dir.glob("*.md")) if agents_dir.exists() else []
|
|
254
|
+
report += f"\nAgents: {len(agent_files)} 个\n"
|
|
255
|
+
for agent in agent_files:
|
|
256
|
+
report += f" - {agent.name}\n"
|
|
257
|
+
|
|
258
|
+
# Skills
|
|
259
|
+
skills_dir = self.root / "skills"
|
|
260
|
+
skill_dirs = [d for d in skills_dir.iterdir() if d.is_dir() and (d / "SKILL.md").exists()] if skills_dir.exists() else []
|
|
261
|
+
report += f"\nSkills: {len(skill_dirs)} 个\n"
|
|
262
|
+
for skill in skill_dirs:
|
|
263
|
+
report += f" - {skill.name}/\n"
|
|
264
|
+
|
|
265
|
+
report += "\n" + "=" * 60 + "\n"
|
|
266
|
+
|
|
267
|
+
return report
|
|
268
|
+
|
|
269
|
+
def run_all_tests(self) -> bool:
|
|
270
|
+
"""运行所有集成测试"""
|
|
271
|
+
self.test_command_agent_links()
|
|
272
|
+
self.test_agent_skill_links()
|
|
273
|
+
self.test_vto_taxonomy_coverage()
|
|
274
|
+
self.test_state_manager_integration()
|
|
275
|
+
self.test_compile_script()
|
|
276
|
+
|
|
277
|
+
report = self.generate_report()
|
|
278
|
+
print(report)
|
|
279
|
+
|
|
280
|
+
failed = sum(1 for r in self.results if not r[1])
|
|
281
|
+
return failed == 0
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
import sys
|
|
286
|
+
|
|
287
|
+
root = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
288
|
+
tester = IntegrationTester(root)
|
|
289
|
+
success = tester.run_all_tests()
|
|
290
|
+
sys.exit(0 if success else 1)
|