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
4 changes: 4 additions & 0 deletions .harness/config/checks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,7 @@ requires = "macos"
[reports]
# 自动合并用的批准 App 的机器人账号(周报把它合并或批准的 PR 算作自动合并)。
bots = ["snowsonz-auto-merge"]

[metrics.baseline]
label = "v0.8.0"
values = { "评审轮次" = 5, "声称已修但代码未变" = 3, "自述与事实不符" = 5, "临时探针 CI 运行" = 32, "首轮修复净增测试" = "16 项修复 / +1" }
4 changes: 2 additions & 2 deletions .harness/engine.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"engine": "delivery-harness",
"version": "0.1.0",
"commit": "95d7f67b0d325af53b0bd0e6ec3ab5e13a2f90dc",
"tree": "sha256:10e1808e6f2aa91f3d86be1218a641154e3e42594b3c1feacf8080eeb03041f3"
"commit": "c22ddedd6c98c55e9a25025663bce72807ef81d3",
"tree": "sha256:a33b78ba880f14f5efdf3b158843291768e228f21764f50b6e5c85d0a642953d"
}
2 changes: 2 additions & 0 deletions .harness/engine/checks/base_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def _restore_base(judged: str, base: str, worktree: Path, cwd: Path) -> None:

def run(base: str, head: str = "HEAD", cwd: Path = ROOT) -> tuple[bool, bool, str]:
"""返回 (base 测试是否通过, 是否强制要求通过, 摘要)。测试框架被篡改时强制要求通过。"""
if not git("ls-tree", "-r", "--name-only", base, "--", "tests", cwd=cwd).strip():
return True, False, "base 没有 tests/,无已有测试可回放"
intended = [flag for flag in risk.classify(base, head, cwd).flags if flag.startswith(INTENDED)]
temp = Path(tempfile.mkdtemp(prefix="base-tests-"))
worktree = temp / "wt"
Expand Down
41 changes: 40 additions & 1 deletion .harness/engine/core/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from __future__ import annotations

import argparse
import json
import re
import shutil
import subprocess
from pathlib import Path
Expand Down Expand Up @@ -39,6 +41,35 @@ def engine_commit(allow_dirty: bool) -> str:
return head.stdout.strip() + ("-dirty" if dirty else "")


def not_on_origin_main() -> str:
"""引擎仓库当前提交不在本地 origin/main 上时返回原因(守卫的可信来源是合并到 main 的代码);否则空串。"""
repo = ENGINE_DIR.parent
if _git("rev-parse", "--verify", "-q", "origin/main", cwd=repo).returncode != 0:
return "引擎仓库没有 origin/main 引用,无法确认提交已合并"
if _git("merge-base", "--is-ancestor", "HEAD", "origin/main", cwd=repo).returncode != 0:
return "引擎仓库当前提交不在 origin/main 上:先 `git checkout main && git pull`"
return ""


def _version_tuple(text: str) -> tuple[int, ...]:
return tuple(int(part) for part in re.findall(r"\d+", text)[:3])


def migration_notes(previous_version: str) -> list[str]:
"""CHANGELOG 中晚于 previous_version 的版本段(含 Unreleased)里带 `**Migration:**` 的条目。previous_version 为空时不提示。"""
changelog = ENGINE_DIR.parent / "CHANGELOG.md"
if not previous_version or not changelog.exists():
return []
notes, applies = [], False
for line in changelog.read_text(encoding="utf-8").splitlines():
if line.startswith("## "):
version = re.search(r"\d+\.\d+\.\d+", line)
applies = "Unreleased" in line or (version is not None and _version_tuple(version.group()) > _version_tuple(previous_version))
elif applies and "**Migration:**" in line:
notes.append(line.lstrip("- ").strip())
return notes


def copy_engine(target: Path) -> Path:
destination = target / ".harness" / "engine"
if destination.resolve() == ENGINE_DIR.resolve():
Expand Down Expand Up @@ -70,6 +101,8 @@ def copy_templates(target: Path) -> tuple[list[str], list[str]]:
def install(target: Path, templates: bool, allow_dirty: bool) -> int:
if not (target / ".git").exists():
raise SystemExit(f"{target} 不是 git 仓库的根目录")
if not allow_dirty and (reason := not_on_origin_main()):
raise SystemExit(reason + "(开发调试可加 --allow-dirty)")
lock = target / ".harness" / "engine.lock"
previous = lock.read_text(encoding="utf-8") if lock.exists() else ""
commit = engine_commit(allow_dirty)
Expand All @@ -78,6 +111,12 @@ def install(target: Path, templates: bool, allow_dirty: bool) -> int:
print(f"引擎 {__version__}({commit[:12]})已装到 {destination.relative_to(target)},锁文件已写入")
if previous:
print(" 升级前的锁文件:" + " ".join(previous.split()))
notes = migration_notes(json.loads(previous).get("version", ""))
if notes:
print("升级后需要你手工处理的迁移项(CHANGELOG):")
for note in notes:
print(f" ! {note}")
print("升级后:bin/verify --full,经 PR 合并(R3,由用户批准);流程见引擎仓库 docs/upgrading.md")
if templates:
written, skipped = copy_templates(target)
for rel in written:
Expand All @@ -94,7 +133,7 @@ def install(target: Path, templates: bool, allow_dirty: bool) -> int:
def main(argv: list[str] | None = None, command: str = "install") -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--target", required=True, help="业务仓库根目录")
parser.add_argument("--allow-dirty", action="store_true", help="允许引擎仓库有未提交改动(仅限开发调试,锁文件会标 -dirty)")
parser.add_argument("--allow-dirty", action="store_true", help="允许引擎仓库有未提交改动、提交不在 origin/main 上(仅限开发调试,锁文件会标 -dirty)")
args = parser.parse_args(argv)
return install(Path(args.target).resolve(), templates=command == "install", allow_dirty=args.allow_dirty)

Expand Down
23 changes: 12 additions & 11 deletions .harness/engine/reports/metrics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""交付度量:统计一次交付(base..head)中机器可数的指标,并与 v0.8.0 基线并列。
"""交付度量:统计一次交付(base..head)中机器可数的指标,并与项目登记的基线(checks.toml [metrics.baseline],可选)并列。

bin/harness metrics --base origin/main [--head HEAD] [--github] [--json]

Expand All @@ -18,17 +18,17 @@

from engine.checks import evidence
from engine.core.cases import load_project_cases
from engine.core.common import ROOT, added_lines, git
from engine.core.common import ROOT, added_lines, git, setting
from engine.routing import risk, run_check

# docs/review/2026-09-25-harness-baseline.md §2
V080_BASELINE = {
"评审轮次": 5,
"声称已修但代码未变": 3,
"自述与事实不符": 5,
"临时探针 CI 运行": 32,
"首轮修复净增测试": "16 项修复 / +1",
}

def baseline_line() -> str:
"""checks.toml [metrics.baseline] 的 label 与 values 拼成一行;未登记则为空(不并列任何基线)。"""
values = setting("metrics.baseline", "values", {})
if not values:
return ""
label = setting("metrics.baseline", "label", "基线")
return f"{label} 基线:" + ";".join(f"{key} {value}" for key, value in values.items()) + "。"


def added_test_functions(base: str, head: str, cwd: Path = ROOT) -> int:
Expand Down Expand Up @@ -104,7 +104,8 @@ def render(data: dict) -> str:
continue
shown = "、".join(value) if isinstance(value, list) else value
lines.append(f"| {key} | {shown if shown != '' else '—'} |")
lines += ["", "v0.8.0 基线:" + ";".join(f"{key} {value}" for key, value in V080_BASELINE.items()) + "。"]
if baseline_line():
lines += ["", baseline_line()]
return "\n".join(lines) + "\n"


Expand Down
4 changes: 2 additions & 2 deletions .harness/engine/reports/weekly.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ def render(week: Week, data: dict, flagged: list[str], budgets: list[str], trial
lines += ["### 误差预算", "", "| 类别 | 窗口 | 消耗 | 状态 |", "|---|---|---|---|"] + (budgets or ["| — | — | — | — |"])
lines += ["", "### 试跑记录(自动汇总)", "", "| 任务 | PR | 人工评审 | 要求修改 | 升级 | 逃逸 |",
"|---|---|---|---|---|---|"] + (trials or ["| — | — | — | — | — | — |"])
lines += ["", "v0.8.0 基线:" + ";".join(f"{key} {value}" for key, value in metrics.V080_BASELINE.items()) + "。",
"", "错误分析:由不是本周主要设计方的评审方按设计 11.1 做,结论存 `docs/review/weekly/<年-周>.md`。", ""]
lines += [""] + ([metrics.baseline_line(), ""] if metrics.baseline_line() else [])
lines += ["错误分析:由不是本周主要设计方的评审方按设计 11.1 做,结论存 `docs/review/weekly/<年-周>.md`。", ""]
lines += [f"<!-- weekly-data {json.dumps({'week': week.key, **data['_signals']}, ensure_ascii=False)} -->"]
return "\n".join(lines) + "\n"

Expand Down
20 changes: 19 additions & 1 deletion .harness/engine/routing/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from pathlib import Path

from engine.checks import r1_checks, taskbook
from engine.core.common import ROOT, commit_field, git, path_matches, setting
from engine.core.common import ROOT, ConfigError, commit_field, git, path_matches, setting
from engine.routing import risk, run_check

CONTRACT_PATTERNS = ["docs/plans/task-*.md", "docs/templates/**", "docs/plans/backlog.md", "docs/specs/**"]
Expand All @@ -41,6 +41,23 @@ def ui_patterns() -> list[str]:
return list(setting("sources", "ui", []))


APPROVAL_MODES = ("app", "none")


def platform_outputs() -> dict[str, str]:
"""checks.toml [platform]:自动合并工作流的批准方式与 App 变量名。approval = "app"(默认,两个账号 + 批准 App)
或 "none"(单账号:ruleset 不要求批准,工作流用 GITHUB_TOKEN 合并,风险见 SECURITY.md)。"""
approval = setting("platform", "approval", "app")
if approval not in APPROVAL_MODES:
raise ConfigError(f".harness/config/checks.toml 的 [platform] approval 只能是 {' 或 '.join(APPROVAL_MODES)},得到 {approval!r}")
return {
"approval": approval,
"app_client_id_var": setting("platform", "app_client_id_var", "HARNESS_APP_CLIENT_ID"),
"app_private_key_secret": setting("platform", "app_private_key_secret", "HARNESS_APP_PRIVATE_KEY"),
"environment": setting("platform", "environment", "harness-auto-merge"),
}


BUDGET_LABEL = "budget-exceeded"
ESCAPE_LABEL = "escape"

Expand Down Expand Up @@ -249,6 +266,7 @@ def main(argv: list[str] | None = None) -> int:
f"risk={facts.risk.label}\nclass={facts.machine_class}\n"
f"auto_merge={'true' if auto else 'false'}\naudit={'true' if audit else 'false'}\n"
)
handle.writelines(f"{key}={value}\n" for key, value in platform_outputs().items())
return 0


Expand Down
Loading