Skip to content

Commit d48c121

Browse files
committed
feat: v0.6.0 - source_path param, stress_test error detail, docs
- Add source_path parameter to all build tools (solution_build, generator_build, validator_build, checker_build, interactor_build) as alternative to passing full source code via code parameter - Add encoding fallback (UTF-8 -> latin-1) for source_path files - Add include_dirs support so relative includes work with source_path - Enhance stress_test_run error messages with seed, cmd_args, stdout, stderr, last_input for easier debugging - Distinguish generator failure modes (timeout/empty output/crash) with mode-specific hints - Document generator_args protocol and n_max parameter relationship - Add effective_n_max to stress_test_run success result - Add problem directory structure docs to CLAUDE.md - Bump version to 0.6.0
1 parent e338ad1 commit d48c121

15 files changed

Lines changed: 257 additions & 39 deletions

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "autocode",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"description": "Claude Code plugin for competitive programming problem-setting workflows.",
55
"author": {
66
"name": "SummerOneTwo",

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ 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.6.0] - 2026-04-25
9+
10+
### Features
11+
12+
- **source_path 参数**: 所有构建工具(solution_build, generator_build, validator_build, checker_build, interactor_build)新增 `source_path` 参数,可直接指定源文件路径,无需传入完整源码字符串。`code` 参数不再为必填,与 `source_path` 二选一。
13+
- **source_path 编码回退**: 自动处理非 UTF-8 编码的源文件(如 GBK),先尝试 UTF-8 读取,失败后回退到 latin-1。
14+
- **source_path 相对 include 支持**: 当 `source_path` 指向外部文件时,自动将源文件父目录加入编译 include 路径,确保 `#include "helper.h"` 等相对引用正常工作。
15+
16+
### Improvements
17+
18+
- **stress_test_run 错误信息增强**: Generator 失败时现在包含 `seed``cmd_args``stdout``stderr``last_input`(上一次成功生成的输入数据),便于调试。
19+
- **stress_test_run 失败模式区分**: 超时、空输出、崩溃三种失败模式现在给出不同的提示信息,不再统一附加 "Check that the generator accepts command-line arguments"。
20+
- **generator_args 文档完善**: `stress_test_run``generator_args` 参数现在明确说明调用协议 `gen.exe <seed> <type> <n_min> <n_max> <t_min> <t_max>`,以及各字段的含义和可选值。
21+
- **n_max 参数关系澄清**: 顶层 `n_max` 参数说明中注明其同时作为 `generator_args.n_max` 的默认值,成功结果中新增 `effective_n_max` 字段。
22+
- **题目目录结构文档**: CLAUDE.md 新增题目目录结构说明,明确 `solutions/``files/``statements/``tests/` 的用途和文件命名。
23+
824
## [0.5.0] - 2026-04-24
925

1026
### Features

CLAUDE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,32 @@ AutoCode/
6666
| stress_test_run | 压力测试 |
6767
| problem_create | 初始化题目 |
6868
| problem_generate_tests | 生成测试数据 |
69+
| problem_validate | 验证题面样例 |
6970
| problem_pack_polygon | 打包为 Polygon 格式 |
7071

72+
## 题目目录结构
73+
74+
`problem_create` 初始化后的目录布局:
75+
76+
```
77+
<problem_dir>/
78+
├── solutions/ # 解法
79+
│ ├── sol.cpp # 标准解
80+
│ └── brute.cpp # 暴力解
81+
├── files/ # 辅助程序
82+
│ ├── gen.cpp # 生成器
83+
│ ├── val.cpp # 校验器
84+
│ ├── checker.cpp # 检查器(可选)
85+
│ ├── interactor.cpp # 交互器(可选)
86+
│ └── testlib.h # testlib 头文件
87+
├── statements/ # 题面
88+
│ └── README.md
89+
└── tests/ # 生成的测试数据
90+
├── 01.in
91+
├── 01.ans
92+
└── ...
93+
```
94+
7195
## 出题工作流程
7296

7397
1. 初始化题目目录 (`problem_create`)

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.5.0"
3+
version = "0.6.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.5.0"
9+
__version__ = "0.6.0"
1010

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

src/autocode_mcp/tools/checker.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ def input_schema(self) -> dict:
4646
},
4747
"code": {
4848
"type": "string",
49-
"description": "Checker C++ 代码(基于 testlib.h)",
49+
"description": "C++ 源代码(与 source_path 二选一)",
50+
},
51+
"source_path": {
52+
"type": "string",
53+
"description": "源文件路径,相对于 problem_dir 或绝对路径。与 code 二选一,优先级高于 code",
5054
},
5155
"test_scenarios": {
5256
"type": "array",
@@ -71,17 +75,38 @@ def input_schema(self) -> dict:
7175
"default": "g++",
7276
},
7377
},
74-
"required": ["problem_dir", "code"],
78+
"required": ["problem_dir"],
7579
}
7680

7781
async def execute(
7882
self,
7983
problem_dir: str,
80-
code: str,
84+
code: str | None = None,
85+
source_path: str | None = None,
8186
test_scenarios: list[dict] | None = None,
8287
compiler: str = "g++",
8388
) -> ToolResult:
8489
"""执行 Checker 构建。"""
90+
# 解析源代码:source_path 优先于 code
91+
source_dir = None
92+
if source_path:
93+
if not os.path.isabs(source_path):
94+
source_path = os.path.join(problem_dir, source_path)
95+
if not os.path.exists(source_path):
96+
return ToolResult.fail(f"Source file not found: {source_path}")
97+
try:
98+
with open(source_path, encoding="utf-8") as f:
99+
code = f.read()
100+
except UnicodeDecodeError:
101+
try:
102+
with open(source_path, encoding="latin-1") as f:
103+
code = f.read()
104+
except Exception as e:
105+
return ToolResult.fail(f"Failed to read source file: {e}")
106+
source_dir = os.path.dirname(os.path.abspath(source_path))
107+
elif code is None:
108+
return ToolResult.fail("Either 'code' or 'source_path' must be provided")
109+
85110
os.makedirs(problem_dir, exist_ok=True)
86111

87112
# 保存到 files/ 子目录
@@ -99,7 +124,8 @@ async def execute(
99124
# 编译
100125
binary_path = os.path.join(files_dir, f"checker{get_exe_extension()}")
101126

102-
compile_result = await self.build(source_path, binary_path, compiler=compiler)
127+
include_dirs = [source_dir] if source_dir else None
128+
compile_result = await self.build(source_path, binary_path, compiler=compiler, include_dirs=include_dirs)
103129

104130
if not compile_result.success:
105131
return ToolResult.fail(

src/autocode_mcp/tools/generator.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,49 @@ def input_schema(self) -> dict:
4747
},
4848
"code": {
4949
"type": "string",
50-
"description": "Generator C++ 代码(基于 testlib.h)",
50+
"description": "C++ 源代码(与 source_path 二选一)",
51+
},
52+
"source_path": {
53+
"type": "string",
54+
"description": "源文件路径,相对于 problem_dir 或绝对路径。与 code 二选一,优先级高于 code",
5155
},
5256
"compiler": {
5357
"type": "string",
5458
"description": "编译器名称",
5559
"default": "g++",
5660
},
5761
},
58-
"required": ["problem_dir", "code"],
62+
"required": ["problem_dir"],
5963
}
6064

6165
async def execute(
6266
self,
6367
problem_dir: str,
64-
code: str,
68+
code: str | None = None,
69+
source_path: str | None = None,
6570
compiler: str = "g++",
6671
) -> ToolResult:
6772
"""执行 Generator 构建。"""
73+
# 解析源代码:source_path 优先于 code
74+
source_dir = None
75+
if source_path:
76+
if not os.path.isabs(source_path):
77+
source_path = os.path.join(problem_dir, source_path)
78+
if not os.path.exists(source_path):
79+
return ToolResult.fail(f"Source file not found: {source_path}")
80+
try:
81+
with open(source_path, encoding="utf-8") as f:
82+
code = f.read()
83+
except UnicodeDecodeError:
84+
try:
85+
with open(source_path, encoding="latin-1") as f:
86+
code = f.read()
87+
except Exception as e:
88+
return ToolResult.fail(f"Failed to read source file: {e}")
89+
source_dir = os.path.dirname(os.path.abspath(source_path))
90+
elif code is None:
91+
return ToolResult.fail("Either 'code' or 'source_path' must be provided")
92+
6893
os.makedirs(problem_dir, exist_ok=True)
6994

7095
# 保存到 files/ 子目录
@@ -81,7 +106,8 @@ async def execute(
81106
exe_ext = get_exe_extension()
82107
binary_path = os.path.join(files_dir, f"gen{exe_ext}")
83108

84-
compile_result = await self.build(source_path, binary_path, compiler=compiler)
109+
include_dirs = [source_dir] if source_dir else None
110+
compile_result = await self.build(source_path, binary_path, compiler=compiler, include_dirs=include_dirs)
85111

86112
if not compile_result.success:
87113
return ToolResult.fail(

src/autocode_mcp/tools/interactor.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ def input_schema(self) -> dict:
4646
},
4747
"code": {
4848
"type": "string",
49-
"description": "Interactor C++ 代码(基于 testlib.h)",
49+
"description": "C++ 源代码(与 source_path 二选一)",
50+
},
51+
"source_path": {
52+
"type": "string",
53+
"description": "源文件路径,相对于 problem_dir 或绝对路径。与 code 二选一,优先级高于 code",
5054
},
5155
"reference_solution_path": {
5256
"type": "string",
@@ -63,18 +67,39 @@ def input_schema(self) -> dict:
6367
"default": "g++",
6468
},
6569
},
66-
"required": ["problem_dir", "code"],
70+
"required": ["problem_dir"],
6771
}
6872

6973
async def execute(
7074
self,
7175
problem_dir: str,
72-
code: str,
76+
code: str | None = None,
77+
source_path: str | None = None,
7378
reference_solution_path: str | None = None,
7479
mutant_solutions: list[str] | None = None,
7580
compiler: str = "g++",
7681
) -> ToolResult:
7782
"""执行 Interactor 构建。"""
83+
# 解析源代码:source_path 优先于 code
84+
source_dir = None
85+
if source_path:
86+
if not os.path.isabs(source_path):
87+
source_path = os.path.join(problem_dir, source_path)
88+
if not os.path.exists(source_path):
89+
return ToolResult.fail(f"Source file not found: {source_path}")
90+
try:
91+
with open(source_path, encoding="utf-8") as f:
92+
code = f.read()
93+
except UnicodeDecodeError:
94+
try:
95+
with open(source_path, encoding="latin-1") as f:
96+
code = f.read()
97+
except Exception as e:
98+
return ToolResult.fail(f"Failed to read source file: {e}")
99+
source_dir = os.path.dirname(os.path.abspath(source_path))
100+
elif code is None:
101+
return ToolResult.fail("Either 'code' or 'source_path' must be provided")
102+
78103
os.makedirs(problem_dir, exist_ok=True)
79104

80105
# 保存到 files/ 子目录
@@ -92,7 +117,8 @@ async def execute(
92117
# 编译
93118
binary_path = os.path.join(files_dir, f"interactor{get_exe_extension()}")
94119

95-
compile_result = await compile_cpp(source_path, binary_path, compiler=compiler)
120+
include_dirs = [source_dir] if source_dir else None
121+
compile_result = await compile_cpp(source_path, binary_path, compiler=compiler, include_dirs=include_dirs)
96122

97123
if not compile_result.success:
98124
return ToolResult.fail(

src/autocode_mcp/tools/mixins.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ async def build(
2323
std: str = "c++20",
2424
opt_level: str = "O2",
2525
timeout: int = 30,
26+
include_dirs: list[str] | None = None,
2627
) -> CompileResult:
2728
return await compile_cpp(
2829
source_path,
@@ -31,6 +32,7 @@ async def build(
3132
compiler=compiler,
3233
std=std,
3334
opt_level=opt_level,
35+
include_dirs=include_dirs,
3436
)
3537

3638

src/autocode_mcp/tools/solution.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,25 +49,50 @@ def input_schema(self) -> dict:
4949
},
5050
"code": {
5151
"type": "string",
52-
"description": "解法的 C++ 代码",
52+
"description": "C++ 源代码(与 source_path 二选一)",
53+
},
54+
"source_path": {
55+
"type": "string",
56+
"description": "源文件路径,相对于 problem_dir 或绝对路径。与 code 二选一,优先级高于 code",
5357
},
5458
"compiler": {
5559
"type": "string",
5660
"description": "编译器名称",
5761
"default": "g++",
5862
},
5963
},
60-
"required": ["problem_dir", "solution_type", "code"],
64+
"required": ["problem_dir", "solution_type"],
6165
}
6266

6367
async def execute(
6468
self,
6569
problem_dir: str,
6670
solution_type: Literal["sol", "brute"],
67-
code: str,
71+
code: str | None = None,
72+
source_path: str | None = None,
6873
compiler: str = "g++",
6974
) -> ToolResult:
7075
"""执行解法构建。"""
76+
# 解析源代码:source_path 优先于 code
77+
source_dir = None
78+
if source_path:
79+
if not os.path.isabs(source_path):
80+
source_path = os.path.join(problem_dir, source_path)
81+
if not os.path.exists(source_path):
82+
return ToolResult.fail(f"Source file not found: {source_path}")
83+
try:
84+
with open(source_path, encoding="utf-8") as f:
85+
code = f.read()
86+
except UnicodeDecodeError:
87+
try:
88+
with open(source_path, encoding="latin-1") as f:
89+
code = f.read()
90+
except Exception as e:
91+
return ToolResult.fail(f"Failed to read source file: {e}")
92+
source_dir = os.path.dirname(os.path.abspath(source_path))
93+
elif code is None:
94+
return ToolResult.fail("Either 'code' or 'source_path' must be provided")
95+
7196
# 确保目录存在
7297
os.makedirs(problem_dir, exist_ok=True)
7398

@@ -91,7 +116,8 @@ async def execute(
91116
binary_name = f"{solution_type}{exe_ext}"
92117
binary_path = os.path.join(solutions_dir, binary_name)
93118

94-
result = await self.build(source_path, binary_path, compiler=compiler)
119+
include_dirs = [source_dir] if source_dir else None
120+
result = await self.build(source_path, binary_path, compiler=compiler, include_dirs=include_dirs)
95121

96122
if not result.success:
97123
return ToolResult.fail(

0 commit comments

Comments
 (0)