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,244 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PaperFit LaTeX 编译脚本
|
|
4
|
+
#
|
|
5
|
+
# 封装 LaTeX 编译流程,支持 latexmk 和 pdflatex 两种方式,
|
|
6
|
+
# 自动处理参考文献、交叉引用的多次编译,并输出结构化日志。
|
|
7
|
+
#
|
|
8
|
+
# 用法:
|
|
9
|
+
# ./compile.sh <main_tex> [--clean] [--engine pdflatex|xelatex|lualatex]
|
|
10
|
+
#
|
|
11
|
+
# 示例:
|
|
12
|
+
# ./compile.sh main.tex
|
|
13
|
+
# ./compile.sh main.tex --clean
|
|
14
|
+
# ./compile.sh main.tex --engine xelatex
|
|
15
|
+
|
|
16
|
+
set -e # 遇到错误立即退出
|
|
17
|
+
|
|
18
|
+
# 颜色输出
|
|
19
|
+
RED='\033[0;31m'
|
|
20
|
+
GREEN='\033[0;32m'
|
|
21
|
+
YELLOW='\033[1;33m'
|
|
22
|
+
NC='\033[0m' # No Color
|
|
23
|
+
|
|
24
|
+
# 默认参数
|
|
25
|
+
MAIN_TEX=""
|
|
26
|
+
CLEAN_MODE=false
|
|
27
|
+
ENGINE="pdflatex"
|
|
28
|
+
USE_LATEXMK=true
|
|
29
|
+
BIBER=false
|
|
30
|
+
|
|
31
|
+
# 帮助信息
|
|
32
|
+
usage() {
|
|
33
|
+
cat << EOF
|
|
34
|
+
用法: $0 <main_tex> [选项]
|
|
35
|
+
|
|
36
|
+
选项:
|
|
37
|
+
--clean 清理临时文件后重新编译
|
|
38
|
+
--engine ENGINE 指定编译引擎 (pdflatex, xelatex, lualatex) [默认: pdflatex]
|
|
39
|
+
--no-latexmk 禁用 latexmk,使用手动多次编译
|
|
40
|
+
--biber 使用 biber 处理参考文献(默认使用 bibtex)
|
|
41
|
+
-h, --help 显示此帮助信息
|
|
42
|
+
|
|
43
|
+
示例:
|
|
44
|
+
$0 main.tex
|
|
45
|
+
$0 main.tex --clean --engine xelatex
|
|
46
|
+
$0 main.tex --no-latexmk --biber
|
|
47
|
+
EOF
|
|
48
|
+
exit 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# 解析参数
|
|
52
|
+
while [[ $# -gt 0 ]]; do
|
|
53
|
+
case $1 in
|
|
54
|
+
--clean)
|
|
55
|
+
CLEAN_MODE=true
|
|
56
|
+
shift
|
|
57
|
+
;;
|
|
58
|
+
--engine)
|
|
59
|
+
ENGINE="$2"
|
|
60
|
+
shift 2
|
|
61
|
+
;;
|
|
62
|
+
--no-latexmk)
|
|
63
|
+
USE_LATEXMK=false
|
|
64
|
+
shift
|
|
65
|
+
;;
|
|
66
|
+
--biber)
|
|
67
|
+
BIBER=true
|
|
68
|
+
shift
|
|
69
|
+
;;
|
|
70
|
+
-h|--help)
|
|
71
|
+
usage
|
|
72
|
+
;;
|
|
73
|
+
*.tex)
|
|
74
|
+
MAIN_TEX="$1"
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
*)
|
|
78
|
+
echo -e "${RED}未知参数: $1${NC}"
|
|
79
|
+
usage
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
# 检查主文件是否提供
|
|
85
|
+
if [[ -z "$MAIN_TEX" ]]; then
|
|
86
|
+
echo -e "${RED}错误: 未指定主 .tex 文件${NC}"
|
|
87
|
+
usage
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# 检查主文件是否存在
|
|
91
|
+
if [[ ! -f "$MAIN_TEX" ]]; then
|
|
92
|
+
echo -e "${RED}错误: 文件不存在: $MAIN_TEX${NC}"
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# 获取文件名(不含扩展名)
|
|
97
|
+
BASENAME=$(basename "$MAIN_TEX" .tex)
|
|
98
|
+
DIRNAME=$(dirname "$MAIN_TEX")
|
|
99
|
+
if [[ "$DIRNAME" == "." ]]; then
|
|
100
|
+
DIRNAME=""
|
|
101
|
+
fi
|
|
102
|
+
MAIN_NAME="${DIRNAME:+$DIRNAME/}$BASENAME"
|
|
103
|
+
|
|
104
|
+
# 进入主文件所在目录
|
|
105
|
+
cd "$(dirname "$MAIN_TEX")" || exit 1
|
|
106
|
+
MAIN_TEX_FILE=$(basename "$MAIN_TEX")
|
|
107
|
+
|
|
108
|
+
echo -e "${GREEN}========================================${NC}"
|
|
109
|
+
echo -e "${GREEN}PaperFit LaTeX 编译${NC}"
|
|
110
|
+
echo -e "${GREEN}========================================${NC}"
|
|
111
|
+
echo "主文件: $MAIN_TEX"
|
|
112
|
+
echo "编译引擎: $ENGINE"
|
|
113
|
+
echo "使用 latexmk: $USE_LATEXMK"
|
|
114
|
+
echo "参考文献工具: $([ "$BIBER" = true ] && echo "biber" || echo "bibtex")"
|
|
115
|
+
echo ""
|
|
116
|
+
|
|
117
|
+
# 清理模式
|
|
118
|
+
if [[ "$CLEAN_MODE" == true ]]; then
|
|
119
|
+
echo -e "${YELLOW}清理临时文件...${NC}"
|
|
120
|
+
if [[ "$USE_LATEXMK" == true ]]; then
|
|
121
|
+
latexmk -C "$MAIN_TEX_FILE"
|
|
122
|
+
else
|
|
123
|
+
rm -f "$BASENAME".{aux,log,out,toc,lof,lot,blg,bbl,bcf,run.xml,fls,fdb_latexmk,synctex.gz,nav,snm,vrb}
|
|
124
|
+
fi
|
|
125
|
+
echo -e "${GREEN}清理完成${NC}\n"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# 编译函数
|
|
129
|
+
compile_with_latexmk() {
|
|
130
|
+
echo -e "${YELLOW}使用 latexmk 编译...${NC}"
|
|
131
|
+
|
|
132
|
+
local latexmk_opts="-pdf -interaction=nonstopmode"
|
|
133
|
+
if [[ "$ENGINE" != "pdflatex" ]]; then
|
|
134
|
+
latexmk_opts="$latexmk_opts -$ENGINE"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if latexmk $latexmk_opts "$MAIN_TEX_FILE"; then
|
|
138
|
+
echo -e "${GREEN}latexmk 编译成功${NC}"
|
|
139
|
+
return 0
|
|
140
|
+
else
|
|
141
|
+
echo -e "${RED}latexmk 编译失败${NC}"
|
|
142
|
+
return 1
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
compile_manual() {
|
|
147
|
+
echo -e "${YELLOW}使用手动多次编译...${NC}"
|
|
148
|
+
|
|
149
|
+
local tex_cmd="$ENGINE -interaction=nonstopmode"
|
|
150
|
+
|
|
151
|
+
# 第一遍编译
|
|
152
|
+
echo "第一遍 $ENGINE..."
|
|
153
|
+
if ! $tex_cmd "$MAIN_TEX_FILE" > /dev/null 2>&1; then
|
|
154
|
+
echo -e "${RED}第一遍编译失败,请查看 ${BASENAME}.log${NC}"
|
|
155
|
+
return 1
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# 处理参考文献
|
|
159
|
+
if grep -q "\\bibliography{" "$MAIN_TEX_FILE" || grep -q "\\bibdata{" "${BASENAME}.aux" 2>/dev/null; then
|
|
160
|
+
echo "处理参考文献..."
|
|
161
|
+
if [[ "$BIBER" == true ]]; then
|
|
162
|
+
if biber "$BASENAME" > /dev/null 2>&1; then
|
|
163
|
+
echo "biber 成功"
|
|
164
|
+
else
|
|
165
|
+
echo -e "${YELLOW}biber 失败,尝试 bibtex...${NC}"
|
|
166
|
+
bibtex "$BASENAME" > /dev/null 2>&1 || echo -e "${YELLOW}警告: 参考文献处理可能有问题${NC}"
|
|
167
|
+
fi
|
|
168
|
+
else
|
|
169
|
+
bibtex "$BASENAME" > /dev/null 2>&1 || echo -e "${YELLOW}警告: bibtex 失败或无需参考文献${NC}"
|
|
170
|
+
fi
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# 第二遍编译(更新交叉引用)
|
|
174
|
+
echo "第二遍 $ENGINE..."
|
|
175
|
+
if ! $tex_cmd "$MAIN_TEX_FILE" > /dev/null 2>&1; then
|
|
176
|
+
echo -e "${RED}第二遍编译失败${NC}"
|
|
177
|
+
return 1
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# 检查是否需要第三次编译(交叉引用未稳定)
|
|
181
|
+
if grep -q "Rerun to get" "${BASENAME}.log"; then
|
|
182
|
+
echo "第三次 $ENGINE (稳定交叉引用)..."
|
|
183
|
+
if ! $tex_cmd "$MAIN_TEX_FILE" > /dev/null 2>&1; then
|
|
184
|
+
echo -e "${YELLOW}第三次编译有警告,但 PDF 已生成${NC}"
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
echo -e "${GREEN}手动编译完成${NC}"
|
|
189
|
+
return 0
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# 执行编译
|
|
193
|
+
START_TIME=$(date +%s)
|
|
194
|
+
|
|
195
|
+
if [[ "$USE_LATEXMK" == true ]]; then
|
|
196
|
+
compile_with_latexmk
|
|
197
|
+
COMPILE_STATUS=$?
|
|
198
|
+
else
|
|
199
|
+
compile_manual
|
|
200
|
+
COMPILE_STATUS=$?
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
END_TIME=$(date +%s)
|
|
204
|
+
DURATION=$((END_TIME - START_TIME))
|
|
205
|
+
|
|
206
|
+
# 检查 PDF 是否生成
|
|
207
|
+
PDF_FILE="${BASENAME}.pdf"
|
|
208
|
+
if [[ -f "$PDF_FILE" ]]; then
|
|
209
|
+
PDF_SIZE=$(du -h "$PDF_FILE" | cut -f1)
|
|
210
|
+
PDF_PAGES=$(pdfinfo "$PDF_FILE" 2>/dev/null | grep "Pages:" | awk '{print $2}' || echo "?")
|
|
211
|
+
echo ""
|
|
212
|
+
echo -e "${GREEN}========================================${NC}"
|
|
213
|
+
echo -e "${GREEN}编译结果${NC}"
|
|
214
|
+
echo -e "${GREEN}========================================${NC}"
|
|
215
|
+
echo "PDF 文件: $PDF_FILE"
|
|
216
|
+
echo "文件大小: $PDF_SIZE"
|
|
217
|
+
echo "页数: $PDF_PAGES"
|
|
218
|
+
echo "编译耗时: ${DURATION}s"
|
|
219
|
+
|
|
220
|
+
# 输出日志摘要
|
|
221
|
+
LOG_FILE="${BASENAME}.log"
|
|
222
|
+
if [[ -f "$LOG_FILE" ]]; then
|
|
223
|
+
ERROR_COUNT=$(grep -c "^!" "$LOG_FILE" 2>/dev/null || echo 0)
|
|
224
|
+
WARNING_COUNT=$(grep -c "Warning:" "$LOG_FILE" 2>/dev/null || echo 0)
|
|
225
|
+
OVERFULL_COUNT=$(grep -c "Overfull" "$LOG_FILE" 2>/dev/null || echo 0)
|
|
226
|
+
echo "错误数: $ERROR_COUNT"
|
|
227
|
+
echo "警告数: $WARNING_COUNT"
|
|
228
|
+
echo "Overfull hbox: $OVERFULL_COUNT"
|
|
229
|
+
fi
|
|
230
|
+
echo -e "${GREEN}========================================${NC}"
|
|
231
|
+
else
|
|
232
|
+
echo -e "${RED}========================================${NC}"
|
|
233
|
+
echo -e "${RED}编译失败: PDF 未生成${NC}"
|
|
234
|
+
echo -e "${RED}========================================${NC}"
|
|
235
|
+
echo "请查看日志文件: ${BASENAME}.log"
|
|
236
|
+
exit 1
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# 根据编译状态返回退出码
|
|
240
|
+
if [[ $COMPILE_STATUS -eq 0 ]] && [[ -f "$PDF_FILE" ]]; then
|
|
241
|
+
exit 0
|
|
242
|
+
else
|
|
243
|
+
exit 1
|
|
244
|
+
fi
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PaperFit 配置验证器
|
|
4
|
+
|
|
5
|
+
验证所有 YAML 配置文件的完整性和一致性。
|
|
6
|
+
防止配置错误导致检测失效或 Agent 行为异常。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
python config_validator.py [--verbose]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import yaml
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import TypedDict
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ============================================================
|
|
22
|
+
# 配置 Schema 定义
|
|
23
|
+
# ============================================================
|
|
24
|
+
|
|
25
|
+
CONFIG_DIR = Path(__file__).parent.parent / "config"
|
|
26
|
+
|
|
27
|
+
CONFIG_FILES = {
|
|
28
|
+
"vto_taxonomy.yaml": "VTO 缺陷分类体系",
|
|
29
|
+
"layout_rules.yaml": "版式硬规则与阈值",
|
|
30
|
+
"writing_rules.yaml": "写作硬规则",
|
|
31
|
+
"templates.yaml": "模板参数",
|
|
32
|
+
"agent_roles.yaml": "Agent 职责描述",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ValidationError:
|
|
38
|
+
"""验证错误"""
|
|
39
|
+
file: str
|
|
40
|
+
field: str
|
|
41
|
+
message: str
|
|
42
|
+
severity: str # "error" | "warning"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class LayoutRulesSchema(TypedDict, total=False):
|
|
46
|
+
"""layout_rules.yaml 的 schema"""
|
|
47
|
+
whitespace: Dict[str, float]
|
|
48
|
+
table: Dict[str, Any]
|
|
49
|
+
float: Dict[str, Any]
|
|
50
|
+
equation: Dict[str, float]
|
|
51
|
+
paragraph: Dict[str, Any]
|
|
52
|
+
font: Dict[str, Any]
|
|
53
|
+
consistency: Dict[str, bool]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class VtoTaxonomySchema(TypedDict, total=False):
|
|
57
|
+
"""vto_taxonomy.yaml 的 schema"""
|
|
58
|
+
version: str
|
|
59
|
+
description: str
|
|
60
|
+
categories: List[Dict[str, Any]]
|
|
61
|
+
defects: List[Dict[str, Any]]
|
|
62
|
+
severity_levels: Dict[str, Any]
|
|
63
|
+
skill_routing: Dict[str, List[str]]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ============================================================
|
|
67
|
+
# 验证器类
|
|
68
|
+
# ============================================================
|
|
69
|
+
|
|
70
|
+
class ConfigValidator:
|
|
71
|
+
"""配置验证器"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, config_dir: Path = CONFIG_DIR):
|
|
74
|
+
self.config_dir = config_dir
|
|
75
|
+
self.errors: List[ValidationError] = []
|
|
76
|
+
self.warnings: List[ValidationError] = []
|
|
77
|
+
self.loaded_configs: Dict[str, Any] = {}
|
|
78
|
+
|
|
79
|
+
def validate_all(self) -> Tuple[bool, List[ValidationError], List[ValidationError]]:
|
|
80
|
+
"""验证所有配置文件"""
|
|
81
|
+
print(f"验证配置目录:{self.config_dir}")
|
|
82
|
+
print("-" * 50)
|
|
83
|
+
|
|
84
|
+
for config_file, description in CONFIG_FILES.items():
|
|
85
|
+
config_path = self.config_dir / config_file
|
|
86
|
+
if not config_path.exists():
|
|
87
|
+
self.errors.append(ValidationError(
|
|
88
|
+
file=config_file,
|
|
89
|
+
field="",
|
|
90
|
+
message=f"配置文件不存在:{config_file}",
|
|
91
|
+
severity="error"
|
|
92
|
+
))
|
|
93
|
+
print(f"[缺失] {config_file}: {description}")
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
print(f"[检查] {config_file}: {description}")
|
|
97
|
+
self._load_and_validate(config_file)
|
|
98
|
+
|
|
99
|
+
print("-" * 50)
|
|
100
|
+
|
|
101
|
+
# 输出错误和警告
|
|
102
|
+
if self.errors:
|
|
103
|
+
print(f"\n发现 {len(self.errors)} 个错误:")
|
|
104
|
+
for err in self.errors:
|
|
105
|
+
print(f" [ERROR] {err.file}: {err.field} - {err.message}")
|
|
106
|
+
|
|
107
|
+
if self.warnings:
|
|
108
|
+
print(f"\n发现 {len(self.warnings)} 个警告:")
|
|
109
|
+
for warn in self.warnings:
|
|
110
|
+
print(f" [WARN] {warn.file}: {warn.field} - {warn.message}")
|
|
111
|
+
|
|
112
|
+
success = len(self.errors) == 0
|
|
113
|
+
print(f"\n验证结果:{'通过' if success else '失败'} ({len(self.warnings)} 个警告)")
|
|
114
|
+
|
|
115
|
+
return success, self.errors, self.warnings
|
|
116
|
+
|
|
117
|
+
def _load_and_validate(self, config_file: str) -> None:
|
|
118
|
+
"""加载并验证单个配置文件"""
|
|
119
|
+
config_path = self.config_dir / config_file
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
123
|
+
config = yaml.safe_load(f)
|
|
124
|
+
self.loaded_configs[config_file] = config
|
|
125
|
+
except yaml.YAMLError as e:
|
|
126
|
+
self.errors.append(ValidationError(
|
|
127
|
+
file=config_file,
|
|
128
|
+
field="",
|
|
129
|
+
message=f"YAML 解析错误:{e}",
|
|
130
|
+
severity="error"
|
|
131
|
+
))
|
|
132
|
+
return
|
|
133
|
+
except Exception as e:
|
|
134
|
+
self.errors.append(ValidationError(
|
|
135
|
+
file=config_file,
|
|
136
|
+
field="",
|
|
137
|
+
message=f"读取失败:{e}",
|
|
138
|
+
severity="error"
|
|
139
|
+
))
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# 根据文件类型进行特定验证
|
|
143
|
+
if config_file == "layout_rules.yaml":
|
|
144
|
+
self._validate_layout_rules(config_file, config)
|
|
145
|
+
elif config_file == "vto_taxonomy.yaml":
|
|
146
|
+
self._validate_vto_taxonomy(config_file, config)
|
|
147
|
+
elif config_file == "templates.yaml":
|
|
148
|
+
self._validate_templates(config_file, config)
|
|
149
|
+
elif config_file == "writing_rules.yaml":
|
|
150
|
+
self._validate_writing_rules(config_file, config)
|
|
151
|
+
elif config_file == "agent_roles.yaml":
|
|
152
|
+
self._validate_agent_roles(config_file, config)
|
|
153
|
+
|
|
154
|
+
def _validate_layout_rules(self, file: str, config: Dict) -> None:
|
|
155
|
+
"""验证 layout_rules.yaml"""
|
|
156
|
+
# 验证 whitespace 部分
|
|
157
|
+
whitespace = config.get('whitespace', {})
|
|
158
|
+
if 'trailing_whitespace_max_ratio' in whitespace:
|
|
159
|
+
val = whitespace['trailing_whitespace_max_ratio']
|
|
160
|
+
if not (0 < val < 1):
|
|
161
|
+
self.errors.append(ValidationError(
|
|
162
|
+
file=file, field="whitespace.trailing_whitespace_max_ratio",
|
|
163
|
+
message=f"阈值必须在 0-1 之间,当前值:{val}", severity="error"
|
|
164
|
+
))
|
|
165
|
+
|
|
166
|
+
# 验证 table 部分
|
|
167
|
+
table = config.get('table', {})
|
|
168
|
+
if 'min_width_utilization' in table and 'max_width_utilization' in table:
|
|
169
|
+
if table['min_width_utilization'] >= table['max_width_utilization']:
|
|
170
|
+
self.errors.append(ValidationError(
|
|
171
|
+
file=file, field="table.width_utilization",
|
|
172
|
+
message="min_width_utilization 必须小于 max_width_utilization",
|
|
173
|
+
severity="error"
|
|
174
|
+
))
|
|
175
|
+
|
|
176
|
+
# 验证 float 部分
|
|
177
|
+
float_cfg = config.get('float', {})
|
|
178
|
+
if 'max_reference_distance_pages' in float_cfg:
|
|
179
|
+
val = float_cfg['max_reference_distance_pages']
|
|
180
|
+
if val < 0:
|
|
181
|
+
self.errors.append(ValidationError(
|
|
182
|
+
file=file, field="float.max_reference_distance_pages",
|
|
183
|
+
message=f"距离不能为负数,当前值:{val}", severity="error"
|
|
184
|
+
))
|
|
185
|
+
|
|
186
|
+
def _validate_vto_taxonomy(self, file: str, config: Dict) -> None:
|
|
187
|
+
"""验证 vto_taxonomy.yaml"""
|
|
188
|
+
# 验证 categories
|
|
189
|
+
categories = config.get('categories', [])
|
|
190
|
+
category_ids = set()
|
|
191
|
+
for cat in categories:
|
|
192
|
+
cat_id = cat.get('id')
|
|
193
|
+
if not cat_id:
|
|
194
|
+
self.errors.append(ValidationError(
|
|
195
|
+
file=file, field="categories[].id",
|
|
196
|
+
message="分类缺少 id 字段", severity="error"
|
|
197
|
+
))
|
|
198
|
+
continue
|
|
199
|
+
if cat_id in category_ids:
|
|
200
|
+
self.errors.append(ValidationError(
|
|
201
|
+
file=file, field="categories[].id",
|
|
202
|
+
message=f"分类 ID 重复:{cat_id}", severity="error"
|
|
203
|
+
))
|
|
204
|
+
category_ids.add(cat_id)
|
|
205
|
+
|
|
206
|
+
# 验证 defects
|
|
207
|
+
defects = config.get('defects', [])
|
|
208
|
+
defect_ids = set()
|
|
209
|
+
for defect in defects:
|
|
210
|
+
defect_id = defect.get('id')
|
|
211
|
+
if not defect_id:
|
|
212
|
+
self.errors.append(ValidationError(
|
|
213
|
+
file=file, field="defects[].id",
|
|
214
|
+
message="缺陷缺少 id 字段", severity="error"
|
|
215
|
+
))
|
|
216
|
+
continue
|
|
217
|
+
if defect_id in defect_ids:
|
|
218
|
+
self.errors.append(ValidationError(
|
|
219
|
+
file=file, field="defects[].id",
|
|
220
|
+
message=f"缺陷 ID 重复:{defect_id}", severity="error"
|
|
221
|
+
))
|
|
222
|
+
defect_ids.add(defect_id)
|
|
223
|
+
|
|
224
|
+
# 验证 category 引用
|
|
225
|
+
category = defect.get('category')
|
|
226
|
+
if category and category not in category_ids:
|
|
227
|
+
self.errors.append(ValidationError(
|
|
228
|
+
file=file, field=f"defects[{defect_id}].category",
|
|
229
|
+
message=f"引用不存在的分类:{category}", severity="error"
|
|
230
|
+
))
|
|
231
|
+
|
|
232
|
+
# 验证 skill_routing
|
|
233
|
+
skill_routing = defect.get('fix_strategy', {}).get('primary_skill')
|
|
234
|
+
if skill_routing:
|
|
235
|
+
valid_skills = [
|
|
236
|
+
'space-util-fixer', 'float-optimizer', 'consistency-polisher',
|
|
237
|
+
'overflow-repair', 'template-migrator', 'visual-inspector', 'writing-polish'
|
|
238
|
+
]
|
|
239
|
+
if skill_routing not in valid_skills:
|
|
240
|
+
self.warnings.append(ValidationError(
|
|
241
|
+
file=file, field=f"defects[{defect_id}].fix_strategy.primary_skill",
|
|
242
|
+
message=f"未知的技能:{skill_routing}", severity="warning"
|
|
243
|
+
))
|
|
244
|
+
|
|
245
|
+
# 验证 skill_routing 一致性
|
|
246
|
+
skill_routing_cfg = config.get('skill_routing', {})
|
|
247
|
+
for skill, defect_list in skill_routing_cfg.items():
|
|
248
|
+
for defect_ref in defect_list:
|
|
249
|
+
if defect_ref not in defect_ids:
|
|
250
|
+
self.errors.append(ValidationError(
|
|
251
|
+
file=file, field=f"skill_routing.{skill}",
|
|
252
|
+
message=f"引用不存在的缺陷 ID: {defect_ref}", severity="error"
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
def _validate_templates(self, file: str, config: Dict) -> None:
|
|
256
|
+
"""验证 templates.yaml"""
|
|
257
|
+
templates = config.get('templates', {})
|
|
258
|
+
for name, template in templates.items():
|
|
259
|
+
if 'columns' in template:
|
|
260
|
+
cols = template['columns']
|
|
261
|
+
if cols not in [1, 2]:
|
|
262
|
+
self.errors.append(ValidationError(
|
|
263
|
+
file=file, field=f"templates.{name}.columns",
|
|
264
|
+
message=f"栏数必须是 1 或 2,当前值:{cols}", severity="error"
|
|
265
|
+
))
|
|
266
|
+
if 'target_pages' in template:
|
|
267
|
+
pages = template['target_pages']
|
|
268
|
+
if pages < 1:
|
|
269
|
+
self.errors.append(ValidationError(
|
|
270
|
+
file=file, field=f"templates.{name}.target_pages",
|
|
271
|
+
message=f"页数必须大于 0,当前值:{pages}", severity="error"
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
def _validate_writing_rules(self, file: str, config: Dict) -> None:
|
|
275
|
+
"""验证 writing_rules.yaml"""
|
|
276
|
+
# 验证写作规则的基本结构
|
|
277
|
+
if 'forbidden_words' in config:
|
|
278
|
+
if not isinstance(config['forbidden_words'], list):
|
|
279
|
+
self.errors.append(ValidationError(
|
|
280
|
+
file=file, field="forbidden_words",
|
|
281
|
+
message="必须是列表类型", severity="error"
|
|
282
|
+
))
|
|
283
|
+
|
|
284
|
+
if 'required_tense' in config:
|
|
285
|
+
tense = config['required_tense']
|
|
286
|
+
valid_tenses = ['present', 'past', 'present_perfect']
|
|
287
|
+
if tense not in valid_tenses:
|
|
288
|
+
self.errors.append(ValidationError(
|
|
289
|
+
file=file, field="required_tense",
|
|
290
|
+
message=f"无效的时态:{tense},有效值:{valid_tenses}",
|
|
291
|
+
severity="error"
|
|
292
|
+
))
|
|
293
|
+
|
|
294
|
+
def _validate_agent_roles(self, file: str, config: Dict) -> None:
|
|
295
|
+
"""验证 agent_roles.yaml"""
|
|
296
|
+
agents = config.get('agents', {})
|
|
297
|
+
for name, role in agents.items():
|
|
298
|
+
if 'description' not in role:
|
|
299
|
+
self.warnings.append(ValidationError(
|
|
300
|
+
file=file, field=f"agents.{name}",
|
|
301
|
+
message="缺少 description 字段", severity="warning"
|
|
302
|
+
))
|
|
303
|
+
if 'tools' not in role:
|
|
304
|
+
self.warnings.append(ValidationError(
|
|
305
|
+
file=file, field=f"agents.{name}",
|
|
306
|
+
message="缺少 tools 字段", severity="warning"
|
|
307
|
+
))
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ============================================================
|
|
311
|
+
# 主函数
|
|
312
|
+
# ============================================================
|
|
313
|
+
|
|
314
|
+
def main():
|
|
315
|
+
"""主函数"""
|
|
316
|
+
import argparse
|
|
317
|
+
|
|
318
|
+
parser = argparse.ArgumentParser(description="PaperFit 配置验证器")
|
|
319
|
+
parser.add_argument('--verbose', '-v', action='store_true',
|
|
320
|
+
help='显示详细输出')
|
|
321
|
+
parser.add_argument('--config-dir', type=str, default=None,
|
|
322
|
+
help='配置文件目录')
|
|
323
|
+
args = parser.parse_args()
|
|
324
|
+
|
|
325
|
+
config_dir = Path(args.config_dir) if args.config_dir else CONFIG_DIR
|
|
326
|
+
|
|
327
|
+
validator = ConfigValidator(config_dir)
|
|
328
|
+
success, errors, warnings = validator.validate_all()
|
|
329
|
+
|
|
330
|
+
if args.verbose:
|
|
331
|
+
print("\n已加载的配置:")
|
|
332
|
+
for file, config in validator.loaded_configs.items():
|
|
333
|
+
print(f" - {file}: {len(str(config))} 字符")
|
|
334
|
+
|
|
335
|
+
sys.exit(0 if success else 1)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
if __name__ == "__main__":
|
|
339
|
+
main()
|