diff --git a/data/rules/cli.py b/data/rules/cli.py
index 6fc686d..3df57f3 100644
--- a/data/rules/cli.py
+++ b/data/rules/cli.py
@@ -2,6 +2,7 @@ from __future__ import annotations
import argparse
import json
+import sys
from pathlib import Path
from data.majors_catalog.cli import (
@@ -87,6 +88,15 @@ def _build_parser() -> argparse.ArgumentParser:
audit_run.add_argument("--catalog-root", default=str(DEFAULT_CATALOG_ROOT))
audit_run.add_argument("--json", action="store_true")
+ subparsers.add_parser("order", help="delegate to data.orders.cli (orders commands)")
+
+ doctor_parser = subparsers.add_parser(
+ "doctor", help="self-check rules/majors/audit/catalog wiring"
+ )
+ doctor_parser.add_argument("--truth-root", default=str(DEFAULT_RULES_TRUTH_ROOT))
+ doctor_parser.add_argument("--catalog-root", default=str(DEFAULT_CATALOG_ROOT))
+ doctor_parser.add_argument("--json", action="store_true")
+
return parser
@@ -99,8 +109,19 @@ def _emit(payload: dict[str, object], json_output: bool) -> int:
def main(argv: list[str] | None = None) -> int:
+ # `order` is a pure delegation marker. Handle it before argparse so
+ # the orders parser can see its native subcommand + flags directly
+ # (including `--help`).
+ tokens = list(sys.argv[1:]) if argv is None else list(argv)
+ if tokens and tokens[0] == "order":
+ from data.orders.cli import main as orders_main
+
+ # data.orders.cli.main expects subcommands without the `order`
+ # prefix (its parser exposes `create` / `list` / `show` / ...).
+ return orders_main(tokens[1:])
+
parser = _build_parser()
- args = parser.parse_args(argv)
+ args = parser.parse_args(tokens)
if args.command == "rules" and args.rules_command == "status":
loader = RuleLoader.from_truth_root(Path(args.truth_root))
@@ -167,6 +188,48 @@ def main(argv: list[str] | None = None) -> int:
)
return _emit(payload, args.json)
+ if args.command == "doctor":
+ truth_root = Path(args.truth_root)
+ catalog_root = Path(args.catalog_root)
+ rules_section: dict[str, object]
+ rules_ok: bool
+ try:
+ rule_status = RuleLoader.from_truth_root(truth_root).build_status()
+ rules_section = {
+ "province_count": rule_status.province_count,
+ "national_rule_count": rule_status.national_rule_count,
+ }
+ rules_ok = rule_status.province_count > 0
+ except (FileNotFoundError, KeyError, ValueError) as exc:
+ rules_section = {"error": f"{type(exc).__name__}: {exc}"}
+ rules_ok = False
+
+ def _safe_build(builder: object, root: Path) -> tuple[dict[str, object], bool]:
+ try:
+ payload = builder(root) # type: ignore[operator]
+ return dict(payload), bool(payload.get("ok", True)) # type: ignore[union-attr]
+ except (FileNotFoundError, KeyError, ValueError) as exc:
+ return (
+ {"ok": False, "error": f"{type(exc).__name__}: {exc}"},
+ False,
+ )
+
+ majors_status, majors_status_ok = _safe_build(
+ build_majors_status_payload, catalog_root
+ )
+ majors_verify, majors_verify_ok = _safe_build(
+ build_majors_verify_payload, catalog_root
+ )
+ majors_ok = majors_status_ok and majors_verify_ok
+ overall_ok = rules_ok and majors_ok
+ payload = {
+ "ok": overall_ok,
+ "rules": rules_section,
+ "majors": majors_status,
+ "majors_verify": majors_verify,
+ }
+ return _emit(payload, args.json)
+
parser.error("unsupported command")
return 2
diff --git a/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md b/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md
index 151667c..05ea616 100644
--- a/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md
+++ b/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md
@@ -1,5 +1,8 @@
# Batch 1 审计收敛执行板(2026-06-16)
+> **状态**:**已收口(历史快照)**。当前执行真相请看 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` + `docs/CURRENT_STATE.md` §0。
+> 本文件仅作历史审计记录保留,不再作为当前执行口径源。
+
真相源输入:
1. `docs/plans/2026-06-16-optimization-program.md`
diff --git a/docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md b/docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md
new file mode 100644
index 0000000..7d20922
--- /dev/null
+++ b/docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md
@@ -0,0 +1,97 @@
+# ACTIVE_EXECUTION_BOARD_2026-06-17
+
+> 真相源: `docs/CURRENT_STATE.md` §0
+> 设计快照: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`
+> 优化计划: `docs/plans/2026-06-16-optimization-program.md`
+> 状态: 已收口 / 未启动 两段式
+
+---
+
+## 1. 已收口(执行 Phase 1 / 1.5 / 2)
+
+### 1.1 执行 Phase 1 + 1.5 — 规则真相源化
+
+- 已落地:
+ - `rules/_truth/national.yaml`(国家级规则)
+ - `rules/_truth/province/*.yaml`(27 省,2026 版)
+ - `data/rules/loader.py` `RuleLoader.from_truth_root`
+ - `data/rules/audit_engine.py` `AuditEngine.audit_plan`
+ - `data/rules/cli.py` `rules {status,verify}`
+ - `scripts/migrate_province_rules_to_truth.py`(从 `PROVINCE_RULES` 一次性迁移)
+ - `tests/test_rules_truth_phase1.py` + `tests/test_audit_cli_major_validation_phase2.py`
+- 提交链:
+ - `ae4835e` rules truth source loader + rules CLI
+ - `8a61b8f` phase1.5 verify 收口 + 旧 checker 迁移
+- 三仓: gitea / origin / tksea 同步至 `edc5b11`
+
+### 1.2 执行 Phase 2 Batch 1 — 国家级专业目录 MVP
+
+- 已落地:
+ - `data/majors_catalog/models.py` `NationalMajor`
+ - `data/majors_catalog/loader.py` `MajorsCatalogLoader`
+ - `data/majors_catalog/national/2024.json` + `national/latest.json`(13 个 curated subset)
+ - `data/majors_catalog/cli.py` `majors {status,lookup,verify,changes}`
+ - `tests/test_majors_catalog_phase2.py` + `tests/test_majors_cli_phase2.py`
+- 提交: `36ad58a feat(majors): add phase2 national catalog mvp`
+
+### 1.3 执行 Phase 2 Batch 2 — 校级招生目录骨架
+
+- 已落地:
+ - `data/majors_catalog/schools/2026/10001.json`(1 所校级骨架样本)
+ - `data/majors_catalog/cli.py` `majors school-status / school-verify --year`
+ - `tests/test_majors_school_catalog_phase2.py` + `tests/test_majors_school_cli_phase2.py`
+- 提交: `6b1157f feat(majors): add school catalog skeleton and verification`
+
+### 1.4 执行 Phase 2 Batch 3 — audit_engine 接入 major_validation
+
+- 已落地:
+ - `data/rules/audit_engine.py` `_validate_majors` 接入 `audit_plan`
+ - `data/rules/cli.py` `audit run --province --plan --truth-root --catalog-root --json`
+ - 输出口径: `MAJORS.not_found` / `MAJORS.non_active`
+ - `tests/test_audit_engine_major_validation_phase2.py` + `tests/test_audit_cli_major_validation_phase2.py`
+- 提交: `edc5b11 feat(audit): validate majors in structured audit flow`
+
+---
+
+## 2. 未启动(执行 Phase 3 = 统一 CLI 命令面)
+
+> 这是当前唯一未启动且**不依赖外部前置**的 P1 主目标。
+> Lane D(设计)的 Phase 4 = Lane E(执行)的 Phase 3,请勿混淆。
+
+### 2.1 范围
+
+按 6/16 优化计划 Phase 4 / 设计层 Phase 4 收敛:
+
+- `gaokao-cli {order,share,delivery,retention,payment,backup,channel,doctor}` 收纳
+- `gaokao-cli doctor` 最小自检(已有 loader / engine / CLI 调用面,不依赖 `gaokato/`)
+- 旧 `scripts/gaokao-*` wrapper 的 deprecation warning
+- `docs/CLI_API_MAPPING.md` 同步"已落地命令清单"节
+
+### 2.2 暂不进入本轮
+
+- `gaokato/services/*` 与 `gaokato/transport/*`(设计层 Phase 5 = 执行层 Phase 4)
+- `gaokato/capabilities/registry.py`(等 doctor 落地后再用其 JSON 输出作为初版源)
+- 退出码统一改写(沿用各子 CLI 现状,不与 Lane D 退出码收口任务冲突)
+
+### 2.3 不可触发的真源改动
+
+- 不改 `data/rules/loader.py` 字段
+- 不改 `data/majors_catalog/*` 数据契约
+- 不改 `rules/_truth/*` yaml
+- 不重写 `data/orders/cli.py` 状态机
+
+---
+
+## 3. 收口规则(防止 4 条线再漂)
+
+1. 状态查询一律走本文件 + `docs/CURRENT_STATE.md` §0
+2. 设计层 Phase 编号不等于执行层 Phase 编号;引用必须带"执行/设计"前缀
+3. 新写"待做"必须指明属于哪个执行 Phase
+4. 历史评审/历史快照/历史整改计划三类文件不再进入本板追踪
+5. docs-only commit 必须在 commit message 标注"docs-only",避免与代码 commit 混轨
+
+---
+
+## 4. 与本板同步的当前任务文档
+
+- `/home/long/.hermes/docs/tasks/current/TASK-20260617-gaokao-optimization-batch2.md`
diff --git a/docs/CURRENT_RULES_STATE_2026-06-16.md b/docs/CURRENT_RULES_STATE_2026-06-16.md
new file mode 100644
index 0000000..952c54e
--- /dev/null
+++ b/docs/CURRENT_RULES_STATE_2026-06-16.md
@@ -0,0 +1,94 @@
+# CURRENT_RULES_STATE_2026-06-16
+
+最后更新: 2026-06-16
+真相源: `docs/RULES_SOURCE_OF_TRUTH.md`
+执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.1
+
+---
+
+## 1. 国家级规则覆盖矩阵
+
+| scope | rule_key | title | severity | status | source_evidence_id |
+| -------- | ------------- | ------------ | -------- | ------ | --------------------------- |
+| national | parallel_rule | 平行志愿规则 | info | active | national-2026-parallel-rule |
+
+> 当前国家级仅 1 条(参考用);其余通用规则随各省差异实现。
+
+---
+
+## 2. 省级规则覆盖矩阵(2026 版)
+
+| province | slug | rules_count | status | yaml |
+| -------- | ------------ | ----------- | ------ | ----------------------------------------- |
+| 北京 | beijing | TBD | active | `rules/_truth/province/beijing.yaml` |
+| 上海 | shanghai | TBD | active | `rules/_truth/province/shanghai.yaml` |
+| 天津 | tianjin | TBD | active | `rules/_truth/province/tianjin.yaml` |
+| 重庆 | chongqing | TBD | active | `rules/_truth/province/chongqing.yaml` |
+| 河北 | hebei | TBD | active | `rules/_truth/province/hebei.yaml` |
+| 山西 | shanxi | TBD | active | `rules/_truth/province/shanxi.yaml` |
+| 内蒙古 | (待补) | — | — | — |
+| 辽宁 | liaoning | TBD | active | `rules/_truth/province/liaoning.yaml` |
+| 吉林 | jilin | TBD | active | `rules/_truth/province/jilin.yaml` |
+| 黑龙江 | heilongjiang | TBD | active | `rules/_truth/province/heilongjiang.yaml` |
+| 江苏 | jiangsu | TBD | active | `rules/_truth/province/jiangsu.yaml` |
+| 浙江 | zhejiang | TBD | active | `rules/_truth/province/zhejiang.yaml` |
+| 安徽 | anhui | TBD | active | `rules/_truth/province/anhui.yaml` |
+| 福建 | fujian | TBD | active | `rules/_truth/province/fujian.yaml` |
+| 江西 | jiangxi | TBD | active | `rules/_truth/province/jiangxi.yaml` |
+| 山东 | shandong | TBD | active | `rules/_truth/province/shandong.yaml` |
+| 河南 | henan | TBD | active | `rules/_truth/province/henan.yaml` |
+| 湖北 | hubei | TBD | active | `rules/_truth/province/hubei.yaml` |
+| 湖南 | hunan | TBD | active | `rules/_truth/province/hunan.yaml` |
+| 广东 | guangdong | TBD | active | `rules/_truth/province/guangdong.yaml` |
+| 广西 | guangxi | TBD | active | `rules/_truth/province/guangxi.yaml` |
+| 海南 | hainan | TBD | active | `rules/_truth/province/hainan.yaml` |
+| 四川 | sichuan | TBD | active | `rules/_truth/province/sichuan.yaml` |
+| 贵州 | guizhou | TBD | active | `rules/_truth/province/guizhou.yaml` |
+| 云南 | yunnan | TBD | active | `rules/_truth/province/yunnan.yaml` |
+| 西藏 | xizang | TBD | active | `rules/_truth/province/xizang.yaml` |
+| 陕西 | (待补) | — | — | — |
+| 甘肃 | gansu | TBD | active | `rules/_truth/province/gansu.yaml` |
+| 青海 | qinghai | TBD | active | `rules/_truth/province/qinghai.yaml` |
+| 宁夏 | (待补) | — | — | — |
+| 新疆 | xinjiang | TBD | active | `rules/_truth/province/xinjiang.yaml` |
+
+> 当前已落地 27 省 yaml(不含内蒙古/陕西/宁夏,3 个待补)。每省 `rules_count` 由本轮 Batch 4 候选任务回填。
+
+---
+
+## 3. 证据链覆盖矩阵
+
+| 类别 | 已落地 | 备注 |
+| -------- | ------ | ------------------------------------------------------- |
+| 国家级 | ⚠ 占位 | `national-2026-parallel-rule` 是占位 ID,未指向真实文件 |
+| 27 省 | ❌ 0 | `rules/_evidence/` 目录尚未建 |
+| 全国通用 | ❌ 0 | 需 `rules/_evidence/national/` 子目录 |
+
+---
+
+## 4. 当前能力面
+
+- ✅ `gaokao-cli rules status --json` 报告 27 省在场
+- ✅ `gaokao-cli rules verify --json` 校验 `national.yaml` + `province/`
+- ✅ `gaokao-cli audit run --province
--plan --json` 跑结构化审计
+- ❌ `gaokao-cli rules explain ` 未实现
+- ❌ "最近验证时间 > 90 天"自动告警未实现
+
+---
+
+## 5. 与旧 `PROVINCE_RULES` 的迁移
+
+- 迁移方式: 一次性脚本 `scripts/migrate_province_rules_to_truth.py`
+- 旧字典保留作为过渡期镜像,新代码只读 `_truth`
+- 旧 `gaokao-checker` 内部已委托给 `audit_engine`
+- 迁移完整性由 `tests/test_rules_truth_phase1.py` 锁定
+
+---
+
+## 6. 下一阶段(Batch 4 候选)
+
+1. `rules/_evidence//-.md` 补建
+2. 逐省 `rules_count` 实际数回填
+3. `gaokao-cli rules explain ` 命令
+4. "最近验证时间 > 90 天"自动告警
+5. 内蒙古/陕西/宁夏 3 省 yaml 补齐
diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md
index 0ce6812..4506e3c 100644
--- a/docs/CURRENT_STATE.md
+++ b/docs/CURRENT_STATE.md
@@ -1,24 +1,41 @@
# CURRENT_STATE
最后更新: 2026-06-17
-状态词: 本地验证完成(Phase2 major_validation 已接入 + 目录/审核闭环已收口)
+状态词: 本地验证完成(执行 Phase 1 规则真相源化 + Phase 1.5 verify 收口 + Phase 2 专业目录三 Batch 全部收口 + 下一阶段 = 执行 Phase 3 统一 CLI 命令面,未启动)
+
真相源优先级:
1. 本文件
-2. `docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md`(当前 Phase2 专业目录执行计划)
-3. `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`(专业目录/CLI 总设计,当前被 Phase2 计划补充)
-4. `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`(规划/实现漂移审计)
-5. `docs/RULES_SOURCE_OF_TRUTH.md`(规则真相源索引)
-6. `docs/MAJOR_DATA_SOURCE_OF_TRUTH.md`(专业目录真相源索引)
-7. `docs/CLI_API_MAPPING.md`(CLI/API 映射索引)
+2. `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md`(6/17 当前执行板,docs-only 收口任务之一)
+3. `docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md`(Phase 2 已收口快照)
+4. `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`(设计快照;Phase 编号需与执行口径消歧)
+5. `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`(历史规划/实现漂移审计)
+6. `docs/RULES_SOURCE_OF_TRUTH.md`(规则真相源索引;待补建)
+7. `docs/MAJOR_DATA_SOURCE_OF_TRUTH.md`(专业目录真相源索引)
+8. `docs/CLI_API_MAPPING.md`(CLI/API 映射索引)
+9. `docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md`(历史整改板;§4 卡片以 §2.1 状态归一为准)
+10. `docs/FRONTEND_UI_AUDIT_2026-06-16.md` / `docs/FRONTEND_UI_EXECUTION_BOARD_2026-06-16.md`
+11. `docs/ACTIVE_REMEDIATION_2026-06-13.md`
+12. `product/PRD.md` / `product/ROADMAP.md` / `docs/IMPLEMENTATION_PLAN_v2.md`
+13. `docs/PRODUCTION_DEPLOYMENT_CHECKLIST_2026-06-15.md`
+14. `reports/PRODUCT_PLANNING_TECH_ALIGNMENT_REVIEW_2026-06-13.md`(历史评审快照)
-8. docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md(当前整改板;2026-06-15 已做状态归一)
-9. docs/FRONTEND_UI_AUDIT_2026-06-16.md
-10. docs/FRONTEND_UI_EXECUTION_BOARD_2026-06-16.md
-11. docs/ACTIVE_REMEDIATION_2026-06-13.md
-12. product/PRD.md / product/ROADMAP.md / docs/IMPLEMENTATION_PLAN_v2.md
-13. docs/PRODUCTION_DEPLOYMENT_CHECKLIST_2026-06-15.md
-14. reports/PRODUCT_PLANNING_TECH_ALIGNMENT_REVIEW_2026-06-13.md(历史评审快照)
+---
+
+## 0. 单一执行口径(2026-06-17)
+
+为消除设计 Phase 编号与执行 Phase 编号漂移,本节是当前唯一口径:
+
+| 设计层 Phase(DESIGN_RULES_TRUSTED_CLI_2026-06-16.md) | 执行层 Phase(当前真相) | 状态 |
+| ------------------------------------------------------ | ------------------------------------------ | ---------------------------------------------- |
+| Phase 1 规则真相源化 | 执行 Phase 1 | ✅ 已收口(`ae4835e` + `8a61b8f`,三仓同步) |
+| Phase 2 统一审计引擎最小可用 | 执行 Phase 1.5(verify + 旧 checker 迁移) | ✅ 已收口(同上) |
+| Phase 3 专业目录接入 MVP | 执行 Phase 2 | ✅ 已收口(`36ad58a` + `6b1157f` + `edc5b11`) |
+| Phase 4 统一 CLI 命令面 | 执行 Phase 3 | ❌ 未启动 → 下一阶段 |
+| Phase 5 智能体调度 + admin 整合 | 执行 Phase 4 | ❌ 未启动 → 等 Phase 3 收口 |
+| Phase 6 文档/真相源索引收口 | 长期维护 | ⚠ 进行中(docs-only 收口本轮同步) |
+
+> 设计层 Phase ≠ 执行层 Phase。状态查询一律以本表 + 6/17 执行板为准。
---
diff --git a/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md b/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md
index cec3ce2..6b7091c 100644
--- a/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md
+++ b/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md
@@ -1,9 +1,10 @@
# MAJOR_DATA_SOURCE_OF_TRUTH
-最后更新: 2026-06-16
+最后更新: 2026-06-17
真相源: 本文件是"专业目录"维度的入口索引。
审计上下文: `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md` §2.3-2.4
设计上下文: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §4
+执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.2-§1.4
---
@@ -28,20 +29,24 @@
### 2.1 当前已落地范围(2026-06-17)
-- 已落地 `data/majors_catalog/` 包与 national loader/CLI
-- 已落地 `national/2024.json` + `national/latest.json`
-- 当前 `coverage_mode = mvp_subset`
-- 当前 curated subset = 13 个国家级本科专业样本
-- 已可通过:
- - `python scripts/gaokao-cli majors status --json`
- - `python scripts/gaokao-cli majors lookup 经济学 --json`
- - `python scripts/gaokao-cli majors verify --json`
-- 已新增:
- - `schools/2026/10001.json` 校级招生目录骨架样本
-- 尚未落地:
- - 更多学校 `schools//` 批量目录
- - `changes/2024-2026.md`
- - `major_validation` 接入 `audit_engine`
+执行 Phase 2 三个 Batch 全部收口:
+
+- 已落地 `data/majors_catalog/` 包与 national loader/CLI(Batch 1)
+- 已落地 `national/2024.json` + `national/latest.json`(13 个 curated subset, `coverage_mode=mvp_subset`)(Batch 1)
+- 已新增 `schools/2026/10001.json` 校级招生目录骨架样本(**当前仅 1 所样本**,未扩面)(Batch 2)
+- 已落地 `major_validation` 接入 `audit_engine`(Batch 3)
+- 已新增 CLI 子命令:
+ - `gaokao-cli majors {status,lookup,verify,changes}`(Batch 1)
+ - `gaokao-cli majors {school-status,school-verify --year}`(Batch 2)
+ - `gaokao-cli audit run --province --plan --truth-root --catalog-root --json` 命中 `MAJORS.not_found` / `MAJORS.non_active`(Batch 3)
+- 提交链:`36ad58a` → `6b1157f` → `edc5b11`,三仓同步
+
+### 2.2 尚未落地
+
+- 更多学校 `schools//` 批量目录(仅 1 所样本)
+- `changes/2024-2026.md`
+- 国家层第二批扩面(>13 个专业)
+- 国家层 2026 版(当前是 2024 版快照)
---
@@ -84,6 +89,8 @@ SchoolMajorOffering:
last_verified_at: datetime
```
+> **当前落地情况**:校级骨架 `schools/2026/10001.json` 1 所样本;其余待 Batch 4 扩面。
+
---
## 4. 接入策略
@@ -93,52 +100,36 @@ SchoolMajorOffering:
- **首选源**: 教育部 2024 年《普通高等学校本科专业目录》
- **兜底源**: 阳光高考/学位中心
- **抓取方式**: 不自动爬取,使用人工收录 + 摘录
-- **校验**: `python3 -m data.majors_catalog.cli verify national`
+- **校验**: `python3 scripts/gaokao-cli majors verify --json`
### 4.2 校级
- **首选源**: 各高校 2025/2026 招生章程
- **覆盖范围**: 首批 5-10 所重点高校
- **接入节奏**: 每所高校每年一次,5 月前完成
-- **校验**: `python3 -m data.majors_catalog.cli verify school `
+- **校验**: `python3 scripts/gaokao-cli majors school-verify --year --json`
---
## 5. 审计引擎集成
-`audit_engine.run` 接收 plan,新增一步:
+`AuditEngine.audit_plan` 新增 `major_validation` 一步:
-```python
-def _validate_majors(plan: VolunteerPlan) -> list[AuditIssue]:
- issues = []
- for item in plan.items:
- for major_name in item.major_names:
- major = majors_catalog.lookup(major_name)
- if major is None:
- issues.append(AuditIssue(
- rule_id="MAJORS.not_found",
- severity=RuleSeverity.WARNING,
- title=f"专业未在国家级目录中找到: {major_name}",
- suggestion="请人工核对",
- ))
- elif major.status != "active":
- issues.append(AuditIssue(
- rule_id="MAJORS.deprecated",
- severity=RuleSeverity.CRITICAL,
- title=f"专业已撤销/合并/改名: {major.name}",
- evidence_quote=f"{major.year_removed} 年已 {major.status}",
- ))
- return issues
-```
+- 命中 `MAJORS.not_found`(专业未在国家级目录中找到)
+- 命中 `MAJORS.non_active`(专业已撤销/合并/改名)
+
+详细见 `tests/test_audit_engine_major_validation_phase2.py`。
---
-## 6. Phase 3 必须收口
+## 6. 下一阶段(执行 Phase 2 收口后的 Batch 4 候选清单)
-- `data/majors_catalog/national/2024.json` 落地,500+ 专业覆盖
-- 5+ 重点高校 2025/2026 招生目录落地
-- `gaokao-cli majors lookup/validate` 可用
-- 1+ 真实 e2e:大厂AI方案跑 audit run 命中 major 验证
+> 已在 Batch 1/2/3 中**完成**的事项不再列入"必须收口"清单。
+
+- 国家层扩面(>13 个专业)— **Batch 4 候选**
+- 校级扩面(>1 所学校)— **Batch 4 候选**
+- `changes/2024-2026.md` — **Batch 4 候选**
+- 国家层 2026 版快照(当前为 2024 版)— **Batch 4 候选**
---
@@ -147,7 +138,4 @@ def _validate_majors(plan: VolunteerPlan) -> list[AuditIssue]:
- 国家级目录与校级目录语义混淆
- 教育部新设专业不进入旧目录(2024 之后新设)
- 跨年招生章程口径不一致
-
----
-
-**下一阶段**: Phase 3 实施,见 `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §11
+- 当前 13 个 curated subset 不应被误读为完整教育部目录
diff --git a/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md b/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md
index e489e24..9cbbd02 100644
--- a/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md
+++ b/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md
@@ -136,7 +136,8 @@
## 4. 详细整改任务板(历史执行快照,已不再作为当前状态真相)
-> 说明:下方逐项卡片保留的是 2026-06-14 制定整改时的任务模板,很多条目仍显示 `状态: pending`。
+> ⚠️ **重要:以下卡片保留的是 2026-06-14 制定整改时的任务模板,绝大多数条目仍显示 `状态: pending`,但实际状态已在 §2.1 中归一为"已完成 / 已转入 T12 / 并入 T12"三类。本节仅作历史记录,不应再被引用为"当前未完成项"。**
+> 当前状态请以 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` 与 `docs/CURRENT_STATE.md` §5-§6 为准。
> 当前状态请以上一节“2026-06-15 状态归一”为准;不要再用这里的旧状态判断项目是否全部完成。
### P1-1 支付回调双写裂缝
diff --git a/docs/RULES_SOURCE_OF_TRUTH.md b/docs/RULES_SOURCE_OF_TRUTH.md
index 34d9dde..b1b56a7 100644
--- a/docs/RULES_SOURCE_OF_TRUTH.md
+++ b/docs/RULES_SOURCE_OF_TRUTH.md
@@ -1,122 +1,129 @@
# RULES_SOURCE_OF_TRUTH
-最后更新: 2026-06-16
+最后更新: 2026-06-17
真相源: 本文件是"规则"维度的入口索引。
-审计上下文: `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`
设计上下文: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §2
+执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.1
---
## 1. 范围
-本文件收敛以下三类信息:
+本文件收敛以下四类信息:
-- 各省高考 2026 规则
-- 全国通用规则
-- 错误模式库
+- 国家级规则
+- 省级规则
+- 规则证据链
+- 统一审计入口
---
-## 2. 当前真相源路径(Phase 1 之后)
+## 2. 当前真相源路径
-| 类别 | 路径 | 格式 | 写入方式 |
-| ------------ | ----------------------------------- | --------- | ---------------------------------- |
-| 全国通用规则 | `rules/_truth/national.yaml` | YAML | 人工 PR + 自动校验 |
-| 省级规则 | `rules/_truth/province/.yaml` | YAML | 人工 PR + 自动校验 |
-| 规则证据原文 | `rules/_evidence//` | 抓取/摘录 | 人工收录 |
-| 错误模式库 | `rules/errors/ERRORS.md` | Markdown | 人工维护(短期保留) |
-| 跨省索引 | `rules/provinces.md` | Markdown | 由 `data/rules/loader.py` 自动生成 |
+| 类别 | 路径 | 格式 | 写入方式 |
+| ---------- | ------------------------------------------ | -------- | ------------------------------- |
+| 国家级规则 | `rules/_truth/national.yaml` | YAML | 人工摘录 + 官方文件校对 |
+| 省级规则 | `rules/_truth/province/.yaml` | YAML | 人工摘录 + 招生考试院校对 |
+| 规则证据层 | `rules/_evidence//-.md` | Markdown | 待补建(设计层 Phase 1.5 要求) |
+| 旧规则字典 | `scripts/gaokao-checker` `PROVINCE_RULES` | Python | 过渡期镜像,已迁移到 \_truth |
+| 错误码字典 | `rules/errors/ERRORS.md` | Markdown | 人工维护 |
+
+### 2.1 当前已落地范围(2026-06-17)
+
+- 已落地 `rules/_truth/national.yaml`(国家级)
+- 已落地 `rules/_truth/province/.yaml`(27 省,2026 版)
+- 已落地 `data/rules/loader.py` `RuleLoader.from_truth_root`
+- 已落地 `data/rules/audit_engine.py` `AuditEngine`
+- 已落地 `data/rules/cli.py` `rules {status,verify}`
+- 已落地 `scripts/migrate_province_rules_to_truth.py`(从 `PROVINCE_RULES` 一次性迁移)
+- 已落地 `tests/test_rules_truth_phase1.py` + `tests/test_rules_cli_phase1.py`(部分)
+
+### 2.2 尚未落地
+
+- `rules/_evidence//-.md`(证据层)— 设计层 Phase 1.5 要求;当前**未建**
+- 逐省证据链审计报告 — 本轮 docs-only 收口后,下一阶段再启动
+- 规则"最近验证时间 > 90 天"自动告警(设计层 §2.5)
---
-## 3. 数据模型最小契约
+## 3. 数据模型
+
+### 3.1 Rule(`data/rules/models.py`)
```python
Rule:
- rule_id: str # e.g. HUNAN.max_volunteers
+ rule_id: str # e.g. "HUNAN.max_volunteers"
scope: "national" | "province"
- province: str | None
- year: int
+ province: str | None # 当 scope=province 时
+ year: int # 2026
title: str
description: str
severity: "fatal" | "critical" | "warning" | "info"
- value: dict # 机读字段,如 {"max_volunteers": 45}
- source_evidence_id: str # 指向 rules/_evidence/.md
+ value: dict # 规则可机读形式
+ source_evidence_id: str # 证据链引用
effective_date: date
last_verified_at: datetime
- version: str
+ version: str # "2026.1"
status: "active" | "draft" | "deprecated"
```
----
+### 3.2 AuditIssue(`data/rules/models.py`)
-## 4. 当前覆盖矩阵(写入本文件时为准)
-
-| 省份 | 模式 | 文档宣称 | checker 实际 | 备注 |
-| ------ | ---------- | --------------- | -------------- | ------------------------------ |
-| 湖南 | 院校专业组 | ✅ | ✅ | 详细规则见 province/hunan.yaml |
-| 广东 | 院校专业组 | ✅ | ✅ | |
-| 湖北 | 院校专业组 | ✅ | ✅ | |
-| 安徽 | 院校专业组 | ✅ | ✅ | |
-| 江西 | 院校专业组 | ✅ | ✅ | |
-| 甘肃 | 院校专业组 | ✅ | ✅ | |
-| 黑龙江 | 院校专业组 | ✅ | ✅ | |
-| 江苏 | 院校专业组 | ✅ | ✅ | |
-| 福建 | 院校专业组 | ✅ | ✅ | |
-| 广西 | 院校专业组 | ✅ | ✅ | |
-| 北京 | 院校专业组 | ✅ | ✅ | |
-| 上海 | 院校专业组 | ✅ | ✅ | |
-| 天津 | 院校专业组 | ✅ | ✅ | |
-| 海南 | 院校专业组 | ✅ | ✅ | |
-| 浙江 | 专业+学校 | ✅ | ✅ | |
-| 山东 | 专业+学校 | ✅ | ✅ | |
-| 河北 | 专业+学校 | ✅ | ✅ | |
-| 重庆 | 专业+学校 | ✅ | ✅ | |
-| 辽宁 | 专业+学校 | ✅ | ✅ | |
-| 贵州 | 专业+学校 | ✅ | ✅ | |
-| 青海 | 专业+学校 | ✅ | ✅ | |
-| 吉林 | 专业+学校 | ✅ | ✅ | |
-| 新疆 | 传统 | ✅ | ✅ | |
-| 西藏 | 传统 | ✅ | ✅ | |
-| 河南 | 传统 | ✅ | ✅ | |
-| 四川 | 传统 | ✅ | ✅ | |
-| 云南 | 传统 | ✅ | ✅ | |
-| 山西 | — | ❌(SKILL.md 漏) | ❌(checker 无) | SKILL.md frontmatter 漂移待修 |
-| 陕西 | — | ❌(SKILL.md 含) | ❌(checker 无) | SKILL.md / checker 漂移待修 |
-
----
-
-## 5. 文档漂移清单(已识别)
-
-- D1: `rules/provinces.md` 与 `scripts/gaokao-checker` 口径一致为 27 省
-- D2: `skills/gaokao-spec-checker/SKILL.md` frontmatter 与实际 checker 省份集仍有漂移
-- D3: 传统模式数 provinces.md 写 5,SKILL.md 写 6
-- D4: 文档与代码都没有 source_evidence_id 链
-
----
-
-## 6. Phase 1 必须收口
-
-- 27 省全部有 `province/.yaml`
-- 至少 1 条全国通用规则有 `national.yaml`
-- 1-2 个规则有真实 `source_evidence_id` 链路(湖南优先)
-- `gaokao-cli rules status` 可用
-
----
-
-## 7. 验证脚本
-
-```bash
-# 规则台账
-gaokao-cli rules status
-
-# 单条规则解释
-gaokao-cli rules explain --province 湖南 --rule-id HUNAN.max_volunteers
-
-# 一致性自检
-python3 -m data.rules.cli verify
+```python
+AuditIssue:
+ rule_id: str
+ severity: RuleSeverity
+ title: str
+ message: str
+ evidence_quote: str | None
+ suggestion: str | None
```
---
-**下一阶段**: Phase 1 实施,见 `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §11
+## 4. 接入策略
+
+### 4.1 国家级
+
+- **首选源**: 教育部 / 教育考试院公开规则文件
+- **覆盖范围**: 平行志愿、一次投档、退档机制、体检要求等
+- **接入节奏**: 年度全量 + 差异更新
+
+### 4.2 省级
+
+- **首选源**: 各省招生考试院/教育考试院 2026 招生工作通知
+- **覆盖范围**: 27 省(与 `PROVINCE_RULES` 对齐)
+- **接入节奏**: 月度差异更新
+
+### 4.3 证据层
+
+- **首选源**: 上述官方文件原文
+- **存储格式**: Markdown 摘录 + 官方文件 URL
+- **校验**: 每条规则必须能引用至少一个证据条目
+
+---
+
+## 5. 统一审计入口
+
+- `gaokao-cli rules status --json` — 27 省 + national 状态摘要
+- `gaokao-cli rules verify --json` — `national.yaml` + `province/` 完整性
+- `gaokao-cli audit run --province --plan --truth-root --catalog-root --json` — 完整审计
+
+---
+
+## 6. 下一阶段(执行 Phase 1 收口后的 Batch 候选清单)
+
+> 已在 Phase 1 / 1.5 中**完成**的事项不再列入"必须收口"清单。
+
+- `rules/_evidence//-.md` 证据层补建 — **Batch 4 候选**
+- 逐省证据链审计报告 — **Batch 4 候选**
+- `gaokao-cli rules explain ` 命令 — **Batch 4 候选**
+- "最近验证时间 > 90 天"自动告警 — **Batch 4 候选**
+
+---
+
+## 7. 风险
+
+- 证据层未建导致规则可信度依赖 yaml 自描述
+- 旧 `PROVINCE_RULES` 字典与新 `_truth` 字典需要保持同步过渡
+- 月度刷新流程目前依赖人工 cron,未与 `gaokao-cli rules status` 联动
diff --git a/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md b/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md
index 4d9dcfe..0e91287 100644
--- a/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md
+++ b/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md
@@ -1,5 +1,9 @@
# Phase2 专业目录真相源 Implementation Plan
+> **状态**:**已收口(历史快照)**。三个 Batch 全部完成,三仓同步至 `edc5b11`。
+> 实际执行进度见 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.2-§1.4 与 `docs/CURRENT_STATE.md` §0。
+> 本文件保留作为 2026-06-17 制定计划时的原始目标范围,不再作为当前待办清单。
+
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task when tasks become independent enough; for the first batch, controller can implement directly because the repo currently has zero `data/majors*` code and the architecture needs one coherent landing.
**Goal:** 为 gaokao-volunteer-system 落地“2026 官方专业目录”最小可用真相源,使系统首次具备国家级专业目录的结构化读取、查询、校验与 CLI 能力,并为后续 school offering / audit integration 铺路。
diff --git a/tests/test_cli_doctor_phase3.py b/tests/test_cli_doctor_phase3.py
new file mode 100644
index 0000000..f6b380d
--- /dev/null
+++ b/tests/test_cli_doctor_phase3.py
@@ -0,0 +1,87 @@
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+PROJECT_ROOT = Path(__file__).resolve().parents[1]
+CLI = PROJECT_ROOT / "scripts" / "gaokao-cli"
+
+
+def _run_cli(*args: str) -> subprocess.CompletedProcess[str]:
+ return subprocess.run(
+ [sys.executable, str(CLI), *args],
+ check=False,
+ capture_output=True,
+ text=True,
+ cwd=PROJECT_ROOT,
+ )
+
+
+def test_cli_help_lists_order_and_doctor() -> None:
+ result = _run_cli("--help")
+ assert result.returncode == 0
+ assert "order" in result.stdout
+ assert "doctor" in result.stdout
+ assert "rules" in result.stdout
+ assert "majors" in result.stdout
+ assert "audit" in result.stdout
+
+
+def test_cli_order_delegates_list_subcommand() -> None:
+ # The orders CLI exposes a `list` subcommand. Asking for its --help
+ # proves the `gaokao-cli order ...` argv successfully reaches the
+ # orders parser instead of being swallowed by the top-level parser.
+ result = _run_cli("order", "list", "--help")
+ assert result.returncode == 0, result.stderr
+ # The orders parser's list subcommand should expose --status/--source/--limit;
+ # presence proves the delegation wired up correctly.
+ assert "--status" in result.stdout
+ assert "--limit" in result.stdout
+
+
+def test_cli_doctor_returns_ok_json(tmp_path: Path) -> None:
+ result = _run_cli("doctor", "--json")
+ assert result.returncode == 0
+ payload = json.loads(result.stdout)
+ assert payload["ok"] is True
+ assert payload["rules"]["province_count"] >= 27
+ assert payload["rules"]["national_rule_count"] >= 0
+ assert payload["majors"]["ok"] is True
+ assert payload["majors_verify"]["ok"] is True
+
+
+def test_cli_doctor_fails_on_empty_truth_root(tmp_path: Path) -> None:
+ empty_truth = tmp_path / "truth"
+ empty_truth.mkdir()
+ empty_catalog = tmp_path / "catalog"
+ empty_catalog.mkdir()
+ (empty_catalog / "national").mkdir()
+ (empty_catalog / "schools").mkdir()
+ (empty_catalog / "national" / "2024.json").write_text(
+ json.dumps({"year": 2024, "majors": []}),
+ encoding="utf-8",
+ )
+ (empty_catalog / "national" / "latest.json").write_text(
+ json.dumps({"year": 2024, "majors": []}),
+ encoding="utf-8",
+ )
+ result = _run_cli(
+ "doctor",
+ "--truth-root",
+ str(empty_truth),
+ "--catalog-root",
+ str(empty_catalog),
+ "--json",
+ )
+ assert result.returncode == 1
+ payload = json.loads(result.stdout)
+ assert payload["ok"] is False
+ assert payload["rules"].get("province_count", 0) == 0
+ # When the truth_root is incomplete the loader raises before any
+ # province_count key is populated, so a structured error key replaces
+ # the missing province_count.
+ assert "error" in payload["rules"] or payload["rules"].get("province_count", 0) == 0