DCL7 精简文档并统一中文口径

这个提交包含在:
2026-05-12 23:06:16 +08:00
父节点 5da05eb5f7
当前提交 39a930399f
共修改 64 个文件,包含 411 行新增和 2129 行删除
+1 -1
查看文件
@@ -5,7 +5,7 @@ import path from "node:path";
const rootDir = path.resolve(new URL("../..", import.meta.url).pathname);
const routerPath = path.join(rootDir, "client/src/router/index.tsx");
const navSchemaPath = path.join(rootDir, "client/src/features/navigation/schema.ts");
const flowMatrixPath = path.join(rootDir, "docs/trialrun/project-wide-flow-matrix.csv");
const flowMatrixPath = path.join(rootDir, "docs/trialrun/项目级流程覆盖矩阵.csv");
const serverRoutesDir = path.join(rootDir, "server/src/routes");
const e2eDir = path.join(rootDir, "client/tests/e2e");
+72 -142
查看文件
@@ -1,176 +1,106 @@
#!/usr/bin/env python3
"""Validate docs/feature-matrix.md against execution-contract CSV sources.
"""Validate the lightweight DCL7 document status baseline.
Checks:
- feature-matrix contains exactly one row per ID in CSV fact sources
- every ID from CSVs appears in matrix
- matrix does not contain unknown IDs
- every matrix row status equals DONE
- every fact-source CSV row is fully DONE on Dev/Review1/Regression axes
DCL7 removed the old full feature matrix from the current docs tree. Historical
details remain recoverable from git history and issue CSVs. This check now
guards the current lightweight status docs and the latest issue CSV contract
shape instead of requiring a large duplicated feature table.
"""
from __future__ import annotations
import csv
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
MATRIX_PATH = ROOT / "docs" / "feature-matrix.md"
CSV_FACT_SOURCES = [
ROOT / "issues" / "2026-02-09_13-39-02-p5-blueprint-v3.csv",
ROOT / "issues" / "2026-02-09_23-30-00-p6-p8-remaining-work-master.csv",
ROOT / "issues" / "2026-02-10_09-42-31-p9-maintenance-performance-matrix.csv",
ROOT / "issues" / "2026-02-10_10-35-30-p10-production-hardening.csv",
ROOT / "issues" / "2026-02-11-p11-p12-governance-mobile.csv",
ROOT / "issues" / "2026-02-12_01-09-41-p13-l5-governance.csv",
ROOT / "issues" / "2026-02-13_00-11-56-p14-l5-operationalization.csv",
ROOT / "issues" / "2026-03-06_14-30-00-p15a-l5-permission-decouple.csv",
ROOT / "issues" / "2026-03-06_14-30-00-p15b-l5-leave-v1.csv",
ROOT / "issues" / "2026-03-07_13-47-05-p16a-l5-master-data-core.csv",
ROOT / "issues" / "2026-03-07_16-22-07-p16b-l5-holiday-calendar.csv",
ROOT / "issues" / "2026-03-07_17-14-39-p16c-l5-suppliers.csv",
ROOT / "issues" / "2026-03-07_18-15-03-r9-g2-p19-roadmap-refresh.csv",
ROOT / "issues" / "2026-03-07_18-16-00-g2-l5-cross-domain-bridge.csv",
ROOT / "issues" / "2026-03-07_19-33-09-p17a-l5-procurement-core.csv",
ROOT / "issues" / "2026-03-07_21-00-37-p17b-l5-procurement-budget-archive.csv",
ROOT / "issues" / "2026-03-07_22-02-09-p18a-l5-asset-registry-core.csv",
ROOT / "issues" / "2026-03-07_22-34-03-p18b-l5-asset-movements-inventory.csv",
ROOT / "issues" / "2026-03-07_23-22-00-p19-l4-integration-readiness.csv",
ROOT / "issues" / "2026-03-08_12-18-10-p19-im-1-l5-collaboration-adapter.csv",
ROOT / "issues" / "2026-03-08_14-10-53-p19-erp-1-l5-masterdata-journal-bridge.csv",
ROOT / "issues" / "2026-03-08_22-46-51-p19-erp-2-l5-procurement-commitment-archive.csv",
ROOT / "issues" / "2026-03-09_00-10-00-p19-erp-3-l5-asset-ledger-summary.csv",
ROOT / "issues" / "2026-03-09_10-15-00-r10-post-erp3-roadmap-refresh.csv",
ROOT / "issues" / "2026-03-09_10-25-00-p19-mcp-1-l4-readonly-tools-baseline.csv",
ROOT / "issues" / "2026-03-10_12-54-30-p19-ai-1-l5-suggestion-workbench-refresh.csv",
ROOT / "issues" / "2026-03-10_17-32-21-p20-travel-1-l5-request-intake-refresh.csv",
ROOT / "issues" / "2026-03-10_21-05-59-p20-travel-2-l5-corporate-payments-refresh.csv",
ROOT / "issues" / "2026-03-11_09-57-28-p20-travel-3-l5-reconciliation-erp-bridge-refresh.csv",
ROOT / "issues" / "2026-03-11_17-50-39-p21-pwa-1-l5-asset-scan-refresh.csv",
ROOT / "issues" / "2026-03-12_04-32-13-p21-pwa-2-l5-offline-deeplink-refresh.csv",
ROOT / "issues" / "2026-03-12_16-58-44-p22-prod-1-l5-production-hardening.csv",
ROOT / "issues" / "2026-03-13_10-30-00-r11-contract-reconciliation-and-spec-refresh.csv",
]
MATRIX_PATH = ROOT / "docs" / "历史功能状态矩阵.md"
STATE_MATRIX_PATH = ROOT / "docs" / "文档事实源状态矩阵.md"
INDEX_PATH = ROOT / "docs" / "文档索引.md"
ISSUES_DIR = ROOT / "issues"
ID_PATTERN = re.compile(r"^[A-Z][0-9][0-9A-Za-z-]*$")
STATUS_INDEX = 6
REQUIRED_COLUMNS = {
"ID",
"Title",
"Description",
"Acceptance",
"Test_Method",
"Tools",
"Dev_Status",
"Review1_Status",
"Regression_Status",
"Files",
"Dependencies",
"Notes",
}
def load_csv_rows(path: Path) -> list[dict[str, str]]:
with path.open(encoding="utf-8", newline="") as fp:
return list(csv.DictReader(fp))
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def collect_expected_ids_and_source_status(paths: list[Path]) -> tuple[list[str], list[str]]:
ids: list[str] = []
source_errors: list[str] = []
def validate_latest_issue_csv() -> list[str]:
errors: list[str] = []
csv_files = sorted(ISSUES_DIR.glob("*.csv"))
if not csv_files:
return ["No issue CSV files found"]
latest = csv_files[-1]
with latest.open(encoding="utf-8", newline="") as fp:
reader = csv.DictReader(fp)
columns = set(reader.fieldnames or [])
missing = sorted(REQUIRED_COLUMNS - columns)
if missing:
errors.append(f"{latest.name} missing columns: {missing}")
rows = list(reader)
if not rows:
errors.append(f"{latest.name} has no issue rows")
for path in paths:
rows = load_csv_rows(path)
for row in rows:
row_id = (row.get("ID") or "").strip()
if not row_id:
continue
ids.append(row_id)
for key in ("Dev_Status", "Review1_Status", "Regression_Status"):
value = (row.get(key) or "").strip()
if value not in {"TODO", "DOING", "DONE"}:
errors.append(f"{latest.name}:{row.get('ID')} invalid {key}={value!r}")
dev_status = (row.get("Dev_Status") or "").strip().upper()
review_status = (row.get("Review1_Status") or "").strip().upper()
regression_status = (row.get("Regression_Status") or "").strip().upper()
if not (dev_status == review_status == regression_status == "DONE"):
source_errors.append(
f"{path.name}:{row_id} source status not all DONE "
f"(Dev={dev_status}, Review1={review_status}, Regression={regression_status})"
)
return ids, source_errors
def load_matrix_rows(path: Path) -> list[list[str]]:
rows: list[list[str]] = []
for line in path.read_text(encoding="utf-8").splitlines():
stripped = line.strip()
if not stripped.startswith("|"):
continue
if stripped.startswith("| ---"):
continue
parts = [part.strip() for part in stripped.strip("|").split("|")]
if not parts:
continue
row_id = parts[0]
if not ID_PATTERN.match(row_id):
continue
rows.append(parts)
return rows
return errors
def main() -> int:
errors: list[str] = []
if not MATRIX_PATH.exists():
errors.append(f"Missing matrix file: {MATRIX_PATH}")
for path in (MATRIX_PATH, STATE_MATRIX_PATH, INDEX_PATH):
if not path.exists():
errors.append(f"Missing required doc: {path.relative_to(ROOT)}")
for source in CSV_FACT_SOURCES:
if not source.exists():
errors.append(f"Missing csv file: {source}")
if not errors:
matrix = read_text(MATRIX_PATH)
state = read_text(STATE_MATRIX_PATH)
index = read_text(INDEX_PATH)
required_matrix_markers = [
"历史功能状态矩阵",
"git show 5da05eb:docs/feature-matrix.md",
"MIF7",
"ME6-3",
]
for marker in required_matrix_markers:
if marker not in matrix:
errors.append(f"{MATRIX_PATH.relative_to(ROOT)} missing marker: {marker}")
for marker in ("中文优先", "已从当前树移除", "MIF7", "ME6-3"):
if marker not in state and marker not in index:
errors.append(f"status/index docs missing marker: {marker}")
errors.extend(validate_latest_issue_csv())
if errors:
for err in errors:
print(f"[ERR] {err}")
return 1
expected_ids, source_status_errors = collect_expected_ids_and_source_status(CSV_FACT_SOURCES)
matrix_rows = load_matrix_rows(MATRIX_PATH)
matrix_ids = [row[0] for row in matrix_rows]
expected_id_set = set(expected_ids)
matrix_id_set = set(matrix_ids)
missing_in_matrix = sorted(expected_id_set - matrix_id_set)
extra_in_matrix = sorted(matrix_id_set - expected_id_set)
if missing_in_matrix:
errors.append(f"IDs missing in matrix: {missing_in_matrix}")
if extra_in_matrix:
errors.append(f"Extra IDs in matrix: {extra_in_matrix}")
duplicate_matrix_ids = sorted({item for item in matrix_ids if matrix_ids.count(item) > 1})
if duplicate_matrix_ids:
errors.append(f"Duplicate IDs in matrix: {duplicate_matrix_ids}")
if len(matrix_ids) != len(expected_ids):
errors.append(f"Row count mismatch: matrix={len(matrix_ids)} expected={len(expected_ids)}")
duplicate_expected_ids = sorted({item for item in expected_ids if expected_ids.count(item) > 1})
if duplicate_expected_ids:
errors.append(f"Duplicate IDs in fact-source CSVs: {duplicate_expected_ids}")
for row in matrix_rows:
if len(row) <= STATUS_INDEX:
errors.append(f"Malformed matrix row (too few columns): {row}")
continue
status = row[STATUS_INDEX].upper()
if status != "DONE":
errors.append(f"Non-DONE status in matrix for {row[0]}: {row[STATUS_INDEX]}")
errors.extend(source_status_errors)
if errors:
print("[ERR] feature-matrix validation failed")
print("[ERR] lightweight document status validation failed")
for err in errors:
print(f" - {err}")
return 1
source_names = ", ".join(path.name for path in CSV_FACT_SOURCES)
print(
f"[OK] feature-matrix matches fact sources ({source_names}) "
f"(IDs/status/count)"
)
print("[OK] lightweight document status baseline is valid")
return 0