Skip to content

Commit dcd52e1

Browse files
SummerOneTwoclaude
andcommitted
feat: 修复 P1 高优先级问题 (Phase 2)
修复 4 个 P1 问题,统一目录约定和配置单位: ### Breaking Changes - 配置单位变更 - problem_pack_polygon 的 time_limit 参数单位从毫秒改为秒 - problem_pack_polygon 的 memory_limit 参数单位从字节改为 MB - 目录结构变更 - solution_build 保存文件到 solutions/ 子目录 - generator_build 保存文件到 files/ 子目录 - validator_build 保存文件到 files/ 子目录 - checker_build 保存文件到 files/ 子目录 - interactor_build 保存文件到 files/ 子目录 - 所有工具支持向后兼容:优先查找子目录,回退到根目录 ### Bug Fixes - Generator 协议统一 - stress_test_run 新增 generator_args 参数 - 支持完整协议: gen.exe <seed> <type> <n_min> <n_max> <t_min> <t_max> - Verdict 完善 - checker_build 根据 testlib.h 返回码正确区分 AC/WA/PE/TLE - interactor_build 支持 PE 判断 ### Documentation - 更新 README 文件结构说明 - 更新 CHANGELOG.md - 版本号升级到 0.4.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9f4ca1c commit dcd52e1

14 files changed

Lines changed: 196 additions & 62 deletions

File tree

CHANGELOG.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,55 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.4.0] - 2026-04-09
9+
10+
### Breaking Changes
11+
12+
- **配置单位变更**
13+
- `problem_pack_polygon``time_limit` 参数单位从毫秒改为****
14+
- `problem_pack_polygon``memory_limit` 参数单位从字节改为**MB**
15+
-`problem.yaml``ResourceLimit` 保持一致
16+
17+
- **目录结构变更**
18+
- `solution_build` 保存文件到 `solutions/` 子目录
19+
- `generator_build` 保存文件到 `files/` 子目录
20+
- `validator_build` 保存文件到 `files/` 子目录
21+
- `checker_build` 保存文件到 `files/` 子目录
22+
- `interactor_build` 保存文件到 `files/` 子目录
23+
- 所有工具支持向后兼容:优先查找子目录,回退到根目录
24+
25+
### Bug Fixes
26+
27+
- **打包配置修复 (P0)**
28+
-`templates/` 移入 `src/autocode_mcp/templates/`
29+
- 修复 wheel 包不包含模板文件的问题
30+
- 更新 `TEMPLATES_DIR` 路径计算逻辑
31+
32+
- **MCP 协议修复 (P0)**
33+
- `call_tool` 返回类型从 `list[TextContent]` 改为 `CallToolResult`
34+
- 正确设置 `isError` 标记,客户端可区分成功/失败
35+
- 添加 `structuredContent` 字段提供结构化数据
36+
- `get_prompt` 返回类型从 `str` 改为 `GetPromptResult`
37+
- `read_resource` 返回类型从 `str` 改为 `ReadResourceResult`
38+
39+
- **Generator 协议统一 (P1)**
40+
- `stress_test_run` 新增 `generator_args` 参数
41+
- 支持完整协议: `gen.exe <seed> <type> <n_min> <n_max> <t_min> <t_max>`
42+
- 默认保持简单协议: `gen.exe <seed>`
43+
44+
- **Verdict 完善 (P1)**
45+
- `checker_build` 根据 testlib.h 返回码正确区分 AC/WA/PE/TLE
46+
- `interactor_build` 支持 PE 判断
47+
48+
### Tests
49+
50+
- 新增 `tests/test_packaging.py` 验收测试 (7 个测试用例)
51+
- 测试数量从 131 增至 138
52+
53+
### Documentation
54+
55+
- 更新 README 文件结构说明,反映新的目录布局
56+
857
## [0.3.1] - 2026-04-08
958

1059
### Bug Fixes

README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -452,18 +452,22 @@ problem_pack_polygon(
452452

453453
```
454454
problems/your-problem/
455-
├── sol.cpp # Optimal solution
456-
├── brute.cpp # Brute force (for validation)
457-
├── val.cpp # Input validator
458-
├── gen.cpp # Test generator
459-
├── chk.cpp # Output checker (optional)
460-
├── interactor.cpp # Interactor (for interactive problems)
455+
├── files/
456+
│ ├── testlib.h # Competitive programming standard library
457+
│ ├── gen.cpp # Test generator
458+
│ ├── val.cpp # Input validator
459+
│ ├── checker.cpp # Output checker (optional)
460+
│ └── interactor.cpp # Interactor (for interactive problems)
461+
├── solutions/
462+
│ ├── sol.cpp # Optimal solution
463+
│ └── brute.cpp # Brute force (for validation)
461464
├── statements/
462-
│ └── README.md # Problem statement
465+
│ └── README.md # Problem statement
463466
├── tests/
464-
│ ├── input/
465-
│ └── output/
466-
└── config.json # Problem configuration
467+
│ ├── 01.in # Test input
468+
│ ├── 01.ans # Expected output
469+
│ └── ...
470+
└── problem.xml # Polygon configuration
467471
```
468472

469473
## Development

README_CN.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -452,18 +452,22 @@ problem_pack_polygon(
452452

453453
```
454454
problems/your-problem/
455-
├── sol.cpp # 最优解
456-
├── brute.cpp # 暴力解(用于验证)
457-
├── val.cpp # 输入校验器
458-
├── gen.cpp # 测试生成器
459-
├── chk.cpp # 输出检查器(可选)
460-
├── interactor.cpp # 交互器(交互题)
455+
├── files/
456+
│ ├── testlib.h # 竞赛编程标准库
457+
│ ├── gen.cpp # 测试生成器
458+
│ ├── val.cpp # 输入校验器
459+
│ ├── checker.cpp # 输出检查器(可选)
460+
│ └── interactor.cpp # 交互器(交互题)
461+
├── solutions/
462+
│ ├── sol.cpp # 最优解
463+
│ └── brute.cpp # 暴力解(用于验证)
461464
├── statements/
462-
│ └── README.md # 题目描述
465+
│ └── README.md # 题目描述
463466
├── tests/
464-
│ ├── input/
465-
│ └── output/
466-
└── config.json # 题目配置
467+
│ ├── 01.in # 测试输入
468+
│ ├── 01.ans # 期望输出
469+
│ └── ...
470+
└── problem.xml # Polygon 配置
467471
```
468472

469473
## 开发

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "autocode-mcp"
3-
version = "0.1.0"
3+
version = "0.4.0"
44
description = "MCP Server for competitive programming problem creation, based on AutoCode paper"
55
readme = "README.md"
66
requires-python = ">=3.10"

src/autocode_mcp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"""
77
import os
88

9-
__version__ = "0.1.0"
9+
__version__ = "0.4.0"
1010

1111
# 获取 templates 目录路径(包内目录)
1212
_PACKAGE_DIR = os.path.dirname(__file__)

src/autocode_mcp/tools/checker.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,20 @@ async def execute(
8484
"""执行 Checker 构建。"""
8585
os.makedirs(problem_dir, exist_ok=True)
8686

87+
# 保存到 files/ 子目录
88+
files_dir = os.path.join(problem_dir, "files")
89+
os.makedirs(files_dir, exist_ok=True)
90+
8791
# 保存代码
88-
source_path = os.path.join(problem_dir, "checker.cpp")
92+
source_path = os.path.join(files_dir, "checker.cpp")
8993
try:
9094
with open(source_path, "w", encoding="utf-8") as f:
9195
f.write(code)
9296
except Exception as e:
9397
return ToolResult.fail(f"Failed to save code: {str(e)}")
9498

9599
# 编译
96-
binary_path = os.path.join(problem_dir, f"checker{get_exe_extension()}")
100+
binary_path = os.path.join(files_dir, f"checker{get_exe_extension()}")
97101

98102
compile_result = await self.build(source_path, binary_path, compiler=compiler)
99103

@@ -148,8 +152,14 @@ async def execute(
148152
timeout=5,
149153
)
150154

151-
# Checker 返回 0 表示 AC,非 0 表示 WA/PE
152-
actual_verdict = "AC" if run_result.return_code == 0 else "WA"
155+
# Checker 返回码约定 (testlib.h):
156+
# 0 = AC, 1 = WA, 2 = PE, 3+ = Fail (checker error)
157+
verdict_map = {0: "AC", 1: "WA", 2: "PE"}
158+
actual_verdict = verdict_map.get(run_result.return_code, "WA")
159+
160+
# 检查是否超时
161+
if run_result.timed_out:
162+
actual_verdict = "TLE"
153163

154164
is_correct = actual_verdict == expected_verdict
155165
if is_correct:

src/autocode_mcp/tools/generator.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,19 @@ async def execute(
6767
"""执行 Generator 构建。"""
6868
os.makedirs(problem_dir, exist_ok=True)
6969

70-
source_path = os.path.join(problem_dir, "gen.cpp")
70+
# 保存到 files/ 子目录
71+
files_dir = os.path.join(problem_dir, "files")
72+
os.makedirs(files_dir, exist_ok=True)
73+
74+
source_path = os.path.join(files_dir, "gen.cpp")
7175
try:
7276
with open(source_path, "w", encoding="utf-8") as f:
7377
f.write(code)
7478
except Exception as e:
7579
return ToolResult.fail(f"Failed to save code: {str(e)}")
7680

7781
exe_ext = get_exe_extension()
78-
binary_path = os.path.join(problem_dir, f"gen{exe_ext}")
82+
binary_path = os.path.join(files_dir, f"gen{exe_ext}")
7983

8084
compile_result = await self.build(source_path, binary_path, compiler=compiler)
8185

@@ -192,8 +196,11 @@ async def execute(
192196
"""执行数据生成。"""
193197
exe_ext = get_exe_extension()
194198

195-
# 检查 generator
196-
gen_exe = os.path.join(problem_dir, f"gen{exe_ext}")
199+
# 检查 generator - 优先查找 files/ 子目录
200+
gen_exe = os.path.join(problem_dir, "files", f"gen{exe_ext}")
201+
if not os.path.exists(gen_exe):
202+
gen_exe = os.path.join(problem_dir, f"gen{exe_ext}")
203+
197204
if not os.path.exists(gen_exe):
198205
return ToolResult.fail("Generator not found. Run generator_build first.")
199206

src/autocode_mcp/tools/interactor.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,20 @@ async def execute(
7777
"""执行 Interactor 构建。"""
7878
os.makedirs(problem_dir, exist_ok=True)
7979

80+
# 保存到 files/ 子目录
81+
files_dir = os.path.join(problem_dir, "files")
82+
os.makedirs(files_dir, exist_ok=True)
83+
8084
# 保存代码
81-
source_path = os.path.join(problem_dir, "interactor.cpp")
85+
source_path = os.path.join(files_dir, "interactor.cpp")
8286
try:
8387
with open(source_path, "w", encoding="utf-8") as f:
8488
f.write(code)
8589
except Exception as e:
8690
return ToolResult.fail(f"Failed to save code: {str(e)}")
8791

8892
# 编译
89-
binary_path = os.path.join(problem_dir, f"interactor{get_exe_extension()}")
93+
binary_path = os.path.join(files_dir, f"interactor{get_exe_extension()}")
9094

9195
compile_result = await compile_cpp(source_path, binary_path, compiler=compiler)
9296

@@ -308,10 +312,11 @@ async def pipe_data(reader, writer, name: str):
308312
pass
309313

310314
# 根据交互器退出码判断结果
311-
if interactor.returncode == 0:
312-
return {"verdict": "AC", "reason": "Accepted"}
313-
elif interactor.returncode == 1:
314-
return {"verdict": "WA", "reason": "Wrong answer"}
315+
# Interactor 返回码约定 (testlib.h):
316+
# 0 = AC, 1 = WA, 2 = PE, 3+ = Fail
317+
verdict_map = {0: "AC", 1: "WA", 2: "PE"}
318+
if interactor.returncode in verdict_map:
319+
return {"verdict": verdict_map[interactor.returncode], "reason": "..."}
315320
else:
316321
return {
317322
"verdict": "RE",

src/autocode_mcp/tools/problem.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -457,13 +457,13 @@ def input_schema(self) -> dict:
457457
},
458458
"time_limit": {
459459
"type": "integer",
460-
"description": "时间限制(毫秒)",
461-
"default": 1000,
460+
"description": "时间限制()",
461+
"default": 1,
462462
},
463463
"memory_limit": {
464464
"type": "integer",
465-
"description": "内存限制(字节)",
466-
"default": 268435456,
465+
"description": "内存限制(MB)",
466+
"default": 256,
467467
},
468468
},
469469
"required": ["problem_dir"],
@@ -472,13 +472,17 @@ def input_schema(self) -> dict:
472472
async def execute(
473473
self,
474474
problem_dir: str,
475-
time_limit: int = 1000,
476-
memory_limit: int = 268435456,
475+
time_limit: int = 1,
476+
memory_limit: int = 256,
477477
) -> ToolResult:
478478
"""执行 Polygon 打包。"""
479479
if not os.path.exists(problem_dir):
480480
return ToolResult.fail(f"Problem directory not found: {problem_dir}")
481481

482+
# 转换单位:秒 -> 毫秒,MB -> 字节
483+
time_limit_ms = time_limit * 1000
484+
memory_limit_bytes = memory_limit * 1024 * 1024
485+
482486
results = {
483487
"files_copied": [],
484488
"files_removed": [],
@@ -541,8 +545,8 @@ async def execute(
541545
</statements>
542546
<judging>
543547
<testset name="tests">
544-
<time-limit>{time_limit}</time-limit>
545-
<memory-limit>{memory_limit}</memory-limit>
548+
<time-limit>{time_limit_ms}</time-limit>
549+
<memory-limit>{memory_limit_bytes}</memory-limit>
546550
<test-count>{actual_test_count}</test-count>
547551
<input-path-pattern>tests/%02d.in</input-path-pattern>
548552
<answer-path-pattern>tests/%02d.ans</answer-path-pattern>

src/autocode_mcp/tools/solution.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,13 @@ async def execute(
7171
# 确保目录存在
7272
os.makedirs(problem_dir, exist_ok=True)
7373

74+
# 保存到 solutions/ 子目录
75+
solutions_dir = os.path.join(problem_dir, "solutions")
76+
os.makedirs(solutions_dir, exist_ok=True)
77+
7478
# 确定文件名
7579
source_name = f"{solution_type}.cpp"
76-
source_path = os.path.join(problem_dir, source_name)
80+
source_path = os.path.join(solutions_dir, source_name)
7781

7882
# 保存代码
7983
try:
@@ -85,7 +89,7 @@ async def execute(
8589
# 编译
8690
exe_ext = get_exe_extension()
8791
binary_name = f"{solution_type}{exe_ext}"
88-
binary_path = os.path.join(problem_dir, binary_name)
92+
binary_path = os.path.join(solutions_dir, binary_name)
8993

9094
result = await self.build(source_path, binary_path, compiler=compiler)
9195

@@ -161,9 +165,13 @@ async def execute(
161165
timeout: int = 30,
162166
) -> ToolResult:
163167
"""执行解法运行。"""
164-
# 确定二进制文件路径
168+
# 确定二进制文件路径 - 优先查找 solutions/ 子目录
165169
exe_ext = get_exe_extension()
166-
binary_path = os.path.join(problem_dir, f"{solution_type}{exe_ext}")
170+
binary_path = os.path.join(problem_dir, "solutions", f"{solution_type}{exe_ext}")
171+
172+
# 如果子目录没有,检查根目录(向后兼容)
173+
if not os.path.exists(binary_path):
174+
binary_path = os.path.join(problem_dir, f"{solution_type}{exe_ext}")
167175

168176
if not os.path.exists(binary_path):
169177
return ToolResult.fail(

0 commit comments

Comments
 (0)