agent-exec transport 92d779408cc72a3fdf9fdf6f005e9b5e/09f250401f29

This commit is contained in:
agent-exec-transport
2026-10-18 02:37:50 +00:00
parent b7f6a8bb9c
commit fb4c89d9dc
6 changed files with 323 additions and 4 deletions
+5 -3
View File
@@ -6,7 +6,7 @@
轮询 Gitea 合并状态 GET {api}/repos/{owner}/{repo}/commits/{sha}/status:
- 任一状态为 failure/error:逐条打印 `[ci-wait] FAIL <context> <target_url>`,立即退出 1;
- 成功须同时满足:至少一条状态、全部 success、每个 --contexts 名称是某个 context 的子串,
- 成功须同时满足:至少一条状态、全部 success(skipped/warning 视同 success,不算失败)、每个 --contexts 名称是某个 context 的子串,
且连续两次轮询观察到同一全绿集合(防止迟注册的 job 被提前判绿);
- 超时:打印最后观察到的状态,退出 2;
- 远端/API/token 问题:`[verdict] REMOTE_UNVERIFIED`,退出 6(瞬时错误在超时内重试,
@@ -30,6 +30,8 @@ EXIT_TIMEOUT = 2
EXIT_USAGE = 2
SHA_RE = re.compile(r"^[0-9a-f]{40}$")
FAILING = ("failure", "error")
# skipped:分级门禁下 base 非 main 的 PR 跳过全量作业;与 warning 一样是非失败终态。
DONE_OK = ("success", "skipped", "warning")
FATAL_HTTP = (400, 401, 403, 404, 410)
@@ -79,8 +81,8 @@ def describe(rows):
def green_key(rows, contexts):
"""满足全绿与 --contexts 时返回可比较的集合键,否则 None。"""
if not rows or any(state != "success" for _, state, _ in rows):
"""满足全绿(success/skipped/warning)与 --contexts 时返回可比较的集合键,否则 None。"""
if not rows or any(state not in DONE_OK for _, state, _ in rows):
return None
if any(not any(name in ctx for ctx, _, _ in rows) for name in contexts):
return None
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""按分支改动选择最小直接检查:host 实施中的 make 目标子集(完整套件仍以 CI 为准)。
用法:test_affected.py [--base REF] [--dry-run]
base:--base,否则环境变量 BASE,否则当前分支卡片的 immediate_target(task_pr_check 助手),
再否则 origin/main;裸分支名在 origin/<name> 存在时解析为 origin/<name>。
改动路径 = `git diff <merge-base>...HEAD` ∪ 未提交的已跟踪改动(相对 HEAD)。路径到目标:
scripts/** tests/** .gitea/** policy/** Makefile AGENTS.md AGENT-EXECUTION.md PROJECT.md → test-governance
modules/{core,backend,frontend,ui} → test-{core,backend,frontend,ui};deps/agent-command → test-agentcmd;
deps/agent-{intent,native,squad} → test-libs;gitlink 本身或 .gitmodules → test-pins;
modules/** 或 scripts/lint-boundaries.sh → test-boundaries;其余(.task/**、docs/**、其他 *.md)不选。
选中任一目标时先跑 test-worktree;单次 `make <targets>` 执行,退出码随 make。
退出码:make 的退出码;无目标或 --dry-run 为 0;用法/ref 错误为 2。仅使用标准库。
"""
import argparse
import os
import subprocess
import sys
DEFAULT_BASE = "origin/main"
EXIT_USAGE = 2
GITLINK_MODE = "160000"
ORDER = ("test-governance", "test-boundaries", "test-pins", "test-libs", "test-core",
"test-backend", "test-frontend", "test-ui", "test-agentcmd")
GOVERNANCE_DIRS = ("scripts/", "tests/", ".gitea/", "policy/")
GOVERNANCE_FILES = ("Makefile", "AGENTS.md", "AGENT-EXECUTION.md", "PROJECT.md")
COMPONENTS = {
"modules/core": "test-core",
"modules/backend": "test-backend",
"modules/frontend": "test-frontend",
"modules/ui": "test-ui",
"deps/agent-command": "test-agentcmd",
"deps/agent-intent": "test-libs",
"deps/agent-native": "test-libs",
"deps/agent-squad": "test-libs",
}
class Stop(Exception):
pass
def git(*args):
proc = subprocess.run(["git", *args], capture_output=True, text=True, encoding="utf-8", errors="replace")
if proc.returncode != 0:
raise Stop(f"git {' '.join(args)} failed: {proc.stderr.strip() or proc.returncode}")
return proc.stdout
def ref_exists(ref):
return subprocess.run(["git", "rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}"],
capture_output=True).returncode == 0
def card_target():
"""当前分支卡片的 immediate_target;无分支/无助手/无卡时返回 None。"""
try:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import task_pr_check
meta, text = task_pr_check.branch_meta(task_pr_check.current_branch(), "main")
except Exception:
return None
return meta.get("immediate_target") if text is not None else None
def resolve_base(requested):
name = (requested or os.environ.get("BASE", "")).strip() or card_target() or DEFAULT_BASE
if not name.startswith("origin/") and ref_exists(f"refs/remotes/origin/{name}"):
name = f"origin/{name}"
if not ref_exists(name):
raise Stop(f"base {name} does not resolve to a commit")
return name
def raw_changes(*diff_args):
"""{path: is_gitlink},取自 `git diff --raw -z --no-renames`。"""
out = git("diff", "--raw", "-z", "--no-renames", *diff_args)
fields = out.split("\0")
changes = {}
for meta, path in zip(fields[0::2], fields[1::2]):
modes = meta.lstrip(":").split()[:2]
changes[path] = changes.get(path, False) or GITLINK_MODE in modes
return changes
def changed_paths(base):
merge_base = git("merge-base", base, "HEAD").strip()
changes = raw_changes(merge_base, "HEAD")
for path, gitlink in raw_changes("HEAD").items():
changes[path] = changes.get(path, False) or gitlink
return changes
def targets_for(path, gitlink):
found = set()
if path.startswith(GOVERNANCE_DIRS) or path in GOVERNANCE_FILES:
found.add("test-governance")
for prefix, target in COMPONENTS.items():
if path == prefix or path.startswith(prefix + "/"):
found.add(target)
if gitlink or path == ".gitmodules":
found.add("test-pins")
if path.startswith("modules/") or path == "scripts/lint-boundaries.sh":
found.add("test-boundaries")
return found
def select(changes):
found = set()
for path, gitlink in changes.items():
found |= targets_for(path, gitlink)
return ["test-worktree", *(t for t in ORDER if t in found)] if found else []
def main(argv=None):
parser = argparse.ArgumentParser(prog="test_affected.py", description="按分支改动运行最小直接检查")
parser.add_argument("--base")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(argv)
try:
os.chdir(git("rev-parse", "--show-toplevel").strip())
base = resolve_base(args.base)
changes = changed_paths(base)
except Stop as stop:
print(f"[affected] ERROR {stop}")
return EXIT_USAGE
targets = select(changes)
print(f"[affected] base={base} changed={len(changes)} targets={','.join(targets) or 'none'}", flush=True)
if not targets:
print("[affected] nothing to test")
return 0
if args.dry_run:
print(f"[affected] dry-run: make {' '.join(targets)}")
return 0
return subprocess.run(["make", *targets]).returncode
if __name__ == "__main__":
sys.exit(main())