Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

## [Unreleased]

## [0.6.5] - 2026-08-04

- Schema v1 题面—Validator 约束对账新增多组数据累计约束检查:保守识别 LaTeX 求和、中文“所有测试用例之和”表述,以及 Validator 中直接 `+=` / `acc = acc + term` 累加和后续 `ensuref` 上限;统一规范化为 `sum:n`、`sum:len:s`、`sum:n+m` 等主体。
- lint 对累计约束的题面缺失、Validator 缺失和确定数值不一致给出非阻断 warning;动态边界与“检测到多测和单组规模但未发现累计上限”保留结构化人工复核信息,不自动推导正确上限,也不把启发式结果当作证明。
- `probhub report` 的 JSON、终端和 Markdown 输出新增累计约束状态与 matched / statement-only / Validator-only / dynamic 计数;新能力仅进入 Workspace Schema v1,Legacy 保持冻结兼容。
- Agent Skill 新增多组数据上限推导契约:`T_max` 在 5 至 100000 间按同文件测试需求、逐组固定成本和 I/O 选择;高测试需求且满足复杂度条件时以 `sum(n_i) <= 10N` 为候选,再用联合最坏数据和三倍 TL 余量校准。题面累计上限未在 Validator 匹配、数值冲突或动态不可核对时阻断封题;即使自动匹配,也要求人工确认累加器宽度、初始化、逐组一次累计和最终拒绝逻辑。

## [0.6.3] - 2026-08-02

- Unix 内存限制 helper 改为隔离 Python 的内联 `-c` 启动,保留 `RLIMIT_AS`、状态管道、信号复位、session/process group 与 fail-closed 语义,同时避免 WSL 从 Windows 挂载目录逐次读取 helper 文件的冷启动开销。
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ProbHub 会在这条流程中提供:
- standard、custom checker、浮点比较和 interactive 四类常见评测场景;
- AC、WA、TLE、MLE、OLE、RE、FAIL 等结果和完整进程树清理;
- 可复现的数据生成、差分测试、反例重放和错解击杀矩阵;
- 题面与 Validator 的范围对账、多组数据累计总量的静态复核提示,以及按复杂度、测试需求和资源余量推导 `T` 与累计上限的 Agent 指引;
- Typst 全卷排版、单题 PDF、DOMjudge ZIP 和交付前验包;
- Windows 与 Ubuntu 双平台 CI 验证。

Expand Down Expand Up @@ -168,7 +169,8 @@ Agent 和 WebUI 都会调用同一套 Core。只有需要手动排查或编排
|---|---|
| `probhub doctor` | 检查安装环境 |
| `probhub ui` | 启动 WebUI |
| `probhub lint L01` | 检查目录、配置和题面结构 |
| `probhub lint L01` | 检查目录、配置、题面结构和约束对账 |
| `probhub report L01` | 查看数据画像、错解击杀和累计约束状态 |
| `probhub judge L01` | 编译并运行 Validator、标程、暴力和错解 |
| `probhub stress L01 --rounds 1000 --seed 12345` | 用随机小数据对拍 |
| `probhub seal L01 --no-cache` | 验证并冻结当前题目版本 |
Expand Down
9 changes: 7 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,17 @@ probhub build L01 --skip-judge

1. 读取 `.probhub/workspace.yaml`,确认稳定 ID、目录和正式题序。
2. 读取所选题目的 `probhub.yaml`、`problem.md`、`code/` 与 `data/`。
3. 只修改规范源文件;不要修改生成物来“修复”结果。
3. 只修改规范源文件;不要修改生成物来“修复”结果。创作、修改或审查含 `T` 的多组数据题时,先读取 `references/aggregate-limit-derivation.md`,在定稿题面和 Validator 前推导并记录 `T_max`、累计规模代理与资源校准结论。
4. 修改后执行:

```powershell
probhub lint <ID>
```

多组数据题还必须读取 `probhub --json lint <ID>` 的 `constraint_reconciliation.aggregate_constraints`。题面声明累计上限时,任何对应的 `statement_only`、`aggregate_constraint_mismatch` 或 `dynamic` 都是 Agent 封题阻断,不能因为 Core 仍返回非阻断 warning 就忽略。直接写法应达到 `state: matched`;若 Validator 使用函数封装、宏或其他静态分析不支持的等价实现,必须人工读代码确认并在验证记录中说明,不能把未识别状态冒充自动通过。

即使结果为 `matched`,也要人工确认 Validator 使用足够宽的累加类型、在多测循环前初始化、对每组目标量恰好累计一次,并在读取全部相关输入后用 `ensuref` 或等价检查限制为题面同一上限。只看到变量名或同一常量不算“确实限制”。发现题面有总量承诺但 Validator 没有实际执行这些步骤时,先修 Validator 并重跑 lint/Judge,再进入 seal。

5. 开发代码或数据时执行:

```powershell
Expand Down Expand Up @@ -202,6 +206,7 @@ probhub build L01 --skip-judge

- 现有题面来源不得擅自改意,只修正格式;Idea 题应自行完成约束、算法与简洁题面。
- 输入格式中的数据范围使用中文括号,紧跟变量第一次出现处,例如:`输入一个整数 $T$($1\le T\le 100$)。`
- 多组数据题除单组范围外,必须按 `references/aggregate-limit-derivation.md` 推导 `T_max` 与累计规模:`T_max` 候选限制在 `5..100000`;测试需求较大、算法至少线性、各组独立且工作量条件成立时,以 `sum(n_i) <= 10*N` 为默认候选,再用联合最坏数据和 `accepted_max_time * 3 <= TL` 校准。若算法复杂度需要累计输入受限,应在题面明确写出总点数、总边数或字符串总长度上界,并在 Validator 中使用足够宽的累加类型逐组累计后校验。题面已有累计上限时,Validator 中的实际累计和拒绝逻辑是封题必查项,不得只依赖 lint 退出码或变量名匹配。
- 题面写法守则:
- 任务目标必须在题目描述阶段即可读懂,不得推迟到输入输出格式甚至样例才首次出现;关键定义、对象、操作在就近位置解释。
- 数据范围必须覆盖输入中每个量的完整前提:下界、字符集、互异性、是否保证有解、是否保证成树/连通等;浮点输出题写明误差判定标准,而不是只写"保留若干位小数"。
Expand All @@ -217,7 +222,7 @@ probhub build L01 --skip-judge
- Checker/Interactor 必须使用附带的 DOMjudge/testlib 协议;交互题按需设置 `judge.interactive.idle_limit` 和 `transcript_limit`。Core 负责本地编译以及生成 `output_validators/validate/`,不得手工维护该生成目录。
- 数据严格放在 `data/sample` 和 `data/secret`,每个 `.in` 必须有同名 `.ans`。
- 样例 `.ans` 必须由配置顺序中的首个 accepted 精确复现;只归一 CRLF/CR 为 LF,尾空格、缺少尾换行和其他字节差异仍失败。Custom Checker 的非唯一输出语义不能替代这条样例不变量;交互题明确不适用。
- 题面只能有一个 H1,必需 H2 依次为题目描述、输入格式、输出格式且内容非空;提示位于输出之后,样例输入/输出只来自 `data/sample`。lint 的约束对账始终是 `analysis_state: partial` 的人工复核报告,启发式 mismatch 只能 warning,不能作为自动正确性证明
- 题面只能有一个 H1,必需 H2 依次为题目描述、输入格式、输出格式且内容非空;提示位于输出之后,样例输入/输出只来自 `data/sample`。lint 的约束对账会保守识别直接 LaTeX/中文累计上限与 Validator 直接累加器,并在多测但未发现累计上限时提示复核;结果始终是 `analysis_state: partial`,启发式 mismatch 只能 warning,不能替代复杂度分析或正确性证明
- secret 数据优先通过 `data.recipes` 配方生成(`probhub gen`):生成器 + 精确 args 可复现同一字节,手工数据显式 `manual: true`;没有配方的测试点 lint 会给 warning。配方格式见 `references/workspace-schema-v1.md`。
- 为定向卡错解和复杂度数据配置 `data.groups` 与结构化 `solutions.*[].expected`;实现或审查时读取 `references/data-groups-expectations.md`。要求错解必须 WA 时显式写 `status: WA`,不得用偶然 RE/TLE 代替。
- 慢参考解可在第二及后续 accepted 上配置 `run_on: [groups]`;多个组取并集,sample 始终执行。首个 accepted 禁止缩域;局部 accepted 必须显式写 `expected.groups`,且期望和 target 覆盖不得超出运行域。该字段只影响本地 Judge,不影响 stress 或 DOMjudge 包。
Expand Down
4 changes: 2 additions & 2 deletions compat/probhub-skill/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "probhub-skill",
"version": "0.6.3",
"version": "0.6.5",
"description": "Lightweight ProbHub command package that delegates to the complete probhub package.",
"main": "bin/init.js",
"bin": {
Expand Down Expand Up @@ -32,7 +32,7 @@
"node": ">=18"
},
"dependencies": {
"probhub": "0.6.3"
"probhub": "0.6.5"
},
"files": [
"bin/*.js",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "probhub",
"version": "0.6.3",
"version": "0.6.5",
"description": "基于大语言模型 (LLM Agent) 和现代排版框架构建的 ACM/ICPC 自动化出题工作流。",
"main": "bin/init.js",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion probhub/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""ProbHub deterministic contest-problem build core."""

__version__ = "0.6.3"
__version__ = "0.6.5"
30 changes: 30 additions & 0 deletions probhub/reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,23 @@ def _problem_report(root, workspace, entry, position, lint_result):
"ungrouped_secret_cases": ungrouped_secret,
"targeted_secret_cases": len(targeted_secret_cases),
"recipes": recipes,
"aggregate_constraints": constraint_report.get("aggregate_constraints") or {
"analysis_state": "partial",
"multi_case_detected": False,
"state": "not_detected",
"matched": [],
"statement_only": [],
"validator_only": [],
"dynamic": [],
"statement_constraints": [],
"validator_constraints": [],
"summary": {
"matched": 0,
"statement_only": 0,
"validator_only": 0,
"dynamic": 0,
},
},
"calibration": _calibration_profile(calibration),
"solution_verification": (lint_result or {}).get("solution_verification") or {},
"kill_matrix": _kill_matrix(config, groups, cases, calibration),
Expand Down Expand Up @@ -619,6 +636,11 @@ def render_markdown_report(report):
f"targeted {recipes['targeted']}/{recipes['total']};near-boundary {recipes['near_boundary']}/{recipes['total']}",
f"- 校准:{problem['calibration']['state']};primary accepted TL 余量 "
f"{_format_headroom(problem['calibration']['primary_headroom'])};`target_guarantee: false`",
f"- 累计约束:{problem['aggregate_constraints']['state']};"
f"matched {problem['aggregate_constraints']['summary']['matched']};"
f"statement-only {problem['aggregate_constraints']['summary']['statement_only']};"
f"Validator-only {problem['aggregate_constraints']['summary']['validator_only']};"
f"dynamic {problem['aggregate_constraints']['summary']['dynamic']}",
"",
])
if problem["groups"]:
Expand Down Expand Up @@ -692,6 +714,14 @@ def render_text_report(report):
f"near-boundary={recipes['near_boundary']}/{recipes['total']} "
f"analysis={recipes['analysis_state']}"
)
aggregate = problem["aggregate_constraints"]
lines.append(
f" 累计约束: {aggregate['state']} "
f"matched={aggregate['summary']['matched']} "
f"statement-only={aggregate['summary']['statement_only']} "
f"validator-only={aggregate['summary']['validator_only']} "
f"dynamic={aggregate['summary']['dynamic']}"
)
if problem["groups"]:
lines.append(" 数据组:")
for group in problem["groups"]:
Expand Down
Loading
Loading