homegraph 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +305 -305
- package/dist/bin/command-supervision.d.ts.map +1 -1
- package/dist/bin/command-supervision.js +7 -4
- package/dist/bin/command-supervision.js.map +1 -1
- package/dist/bin/homegraph.js +9 -9
- package/dist/db/index.js +36 -36
- package/dist/db/migrations.js +37 -37
- package/dist/db/queries.js +156 -156
- package/dist/db/schema.sql +203 -203
- package/dist/directory.js +5 -5
- package/dist/extraction/languages/arkts-viewtree.d.ts +2 -4
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.js +6 -21
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +16 -6
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +174 -21
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/installer/instructions-template.js +9 -9
- package/dist/mcp/liveness-watchdog.d.ts +18 -0
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +185 -73
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/server-instructions.js +47 -47
- package/dist/reasoning/reasoner.js +32 -32
- package/dist/spec/db/commit-node.js +4 -4
- package/dist/spec/db/fragment-node.js +10 -10
- package/dist/spec/db/fts.js +8 -8
- package/dist/spec/db/relations.js +88 -88
- package/dist/spec/db/schema.js +6 -6
- package/dist/spec/db/schema.sql +121 -121
- package/dist/spec/db/spec-node.js +4 -4
- package/dist/spec/llm/prompts.js +53 -53
- package/dist/spec/utils.d.ts +16 -4
- package/dist/spec/utils.d.ts.map +1 -1
- package/dist/spec/utils.js +58 -6
- package/dist/spec/utils.js.map +1 -1
- package/package.json +62 -62
- package/scripts/_tmp-cfwk-resolve.log +0 -0
- package/scripts/_tmp-cfwk-sig.log +0 -0
- package/scripts/_tmp-cfwk-vt.log +0 -0
- package/scripts/add-lang/bench.sh +60 -60
- package/scripts/add-lang/check-grammar.mjs +75 -75
- package/scripts/add-lang/dump-ast.mjs +103 -103
- package/scripts/add-lang/verify-extraction.mjs +70 -70
- package/scripts/agent-eval/ab-adoption.sh +91 -91
- package/scripts/agent-eval/ab-hook.sh +86 -86
- package/scripts/agent-eval/ab-impl.sh +78 -78
- package/scripts/agent-eval/ab-new-vs-baseline.sh +102 -102
- package/scripts/agent-eval/ab-sufficiency.sh +78 -78
- package/scripts/agent-eval/arms-F.sh +21 -21
- package/scripts/agent-eval/arms-matrix.sh +37 -37
- package/scripts/agent-eval/audit.sh +68 -68
- package/scripts/agent-eval/bench-readme.sh +28 -28
- package/scripts/agent-eval/bench-why-repo.sh +22 -22
- package/scripts/agent-eval/block-read-hook.sh +19 -19
- package/scripts/agent-eval/hook-settings.json +15 -15
- package/scripts/agent-eval/itrun.sh +120 -120
- package/scripts/agent-eval/offload-eval-3arm.sh +72 -72
- package/scripts/agent-eval/offload-eval-cost.mjs +133 -133
- package/scripts/agent-eval/offload-eval-effort.mjs +108 -108
- package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -25
- package/scripts/agent-eval/offload-eval-frontload.sh +47 -47
- package/scripts/agent-eval/offload-eval-ground-truth.json +18 -18
- package/scripts/agent-eval/offload-eval-hook.mjs +84 -84
- package/scripts/agent-eval/offload-eval-judge.mjs +103 -103
- package/scripts/agent-eval/offload-eval-matrix.sh +20 -20
- package/scripts/agent-eval/offload-eval-metrics.mjs +94 -94
- package/scripts/agent-eval/offload-eval-refs1.sh +50 -50
- package/scripts/agent-eval/offload-eval-setup.sh +24 -24
- package/scripts/agent-eval/offload-eval-styles.sh +71 -71
- package/scripts/agent-eval/offload-eval-summarize.mjs +68 -68
- package/scripts/agent-eval/offload-eval.md +76 -76
- package/scripts/agent-eval/parse-arms.mjs +116 -116
- package/scripts/agent-eval/parse-bench-readme.mjs +84 -84
- package/scripts/agent-eval/parse-run.mjs +45 -45
- package/scripts/agent-eval/parse-session.mjs +93 -93
- package/scripts/agent-eval/probe-context.mjs +21 -21
- package/scripts/agent-eval/probe-explore.mjs +40 -40
- package/scripts/agent-eval/probe-node.mjs +20 -20
- package/scripts/agent-eval/probe-sweep.mjs +119 -119
- package/scripts/agent-eval/probe-trace.mjs +20 -20
- package/scripts/agent-eval/redirect-read-hook.sh +38 -38
- package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -119
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -125
- package/scripts/agent-eval/run-agent.sh +34 -34
- package/scripts/agent-eval/run-all.sh +75 -75
- package/scripts/agent-eval/run-arms.sh +56 -56
- package/scripts/agent-eval/seq-matrix.mjs +137 -137
- package/scripts/bench-arkts-init-rss.log +0 -0
- package/scripts/build-bundle.sh +123 -123
- package/scripts/exp_boundary_eval/README.md +247 -247
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/win_mcp_launcher.cpython-38.pyc +0 -0
- package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -78
- package/scripts/exp_boundary_eval/_test_stdin.py +8 -8
- package/scripts/exp_boundary_eval/_utils.py +1116 -1116
- package/scripts/exp_boundary_eval/analyze.py +1313 -1313
- package/scripts/exp_boundary_eval/deveco_arm.py +519 -519
- package/scripts/exp_boundary_eval/run_all.py +378 -378
- package/scripts/exp_boundary_eval/run_one.py +165 -165
- package/scripts/exp_boundary_eval/run_session.py +158 -158
- package/scripts/exp_boundary_eval/setup.py +120 -120
- package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -73
- package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -36
- package/scripts/exp_boundary_eval/win_node_launcher.py +24 -24
- package/scripts/extract-release-notes.mjs +130 -130
- package/scripts/local-install.sh +41 -41
- package/scripts/npm-sdk.js +75 -75
- package/scripts/npm-shim.js +275 -275
- package/scripts/ohos-sdk-publish.mjs +133 -133
- package/scripts/pack-npm.sh +119 -119
- package/scripts/prepare-release.mjs +270 -270
- package/scripts/probe-arkts-mem-why-run.log +0 -0
- package/scripts/probe-banner-livecard-ir.log +0 -0
- package/scripts/probe-cfwk-ast.stderr.log +0 -0
- package/scripts/probe-cfwk-ast.stdout.log +0 -0
- package/scripts/probe-cfwk-attr-shape.log +0 -0
- package/scripts/probe-cfwk-cfgdump.stderr.log +0 -0
- package/scripts/probe-cfwk-cfgdump.stdout.log +0 -0
- package/scripts/probe-cfwk-cvc.stderr.log +0 -0
- package/scripts/probe-cfwk-cvc.stdout.log +0 -0
- package/scripts/probe-cfwk-diag2.log +0 -0
- package/scripts/probe-cfwk-diag3.log +0 -0
- package/scripts/probe-cfwk-fedbg.stderr.log +0 -0
- package/scripts/probe-cfwk-fedbg.stdout.log +0 -0
- package/scripts/probe-cfwk-fileresult.log +0 -0
- package/scripts/probe-cfwk-fix.stderr.log +0 -0
- package/scripts/probe-cfwk-fix.stdout.log +0 -0
- package/scripts/probe-cfwk-fix2.stderr.log +0 -0
- package/scripts/probe-cfwk-fix2.stdout.log +0 -0
- package/scripts/probe-cfwk-fix3.stderr.log +0 -0
- package/scripts/probe-cfwk-fix3.stdout.log +0 -0
- package/scripts/probe-cfwk-foreach.log +0 -0
- package/scripts/probe-cfwk-getmethod-throw.log +0 -0
- package/scripts/probe-cfwk-hg-extract.log +0 -0
- package/scripts/probe-cfwk-pr1003-noprior.stderr.log +0 -0
- package/scripts/probe-cfwk-pr1003-noprior.stdout.log +0 -0
- package/scripts/probe-cfwk-pr1003.stderr.log +0 -0
- package/scripts/probe-cfwk-pr1003.stdout.log +0 -0
- package/scripts/probe-cfwk-preroot-noprior.stderr.log +0 -0
- package/scripts/probe-cfwk-preroot-noprior.stdout.log +0 -0
- package/scripts/probe-cfwk-preroot-prior.stderr.log +0 -0
- package/scripts/probe-cfwk-preroot-prior.stdout.log +0 -0
- package/scripts/probe-cfwk-preroot-skipstate.stderr.log +0 -0
- package/scripts/probe-cfwk-preroot-skipstate.stdout.log +0 -0
- package/scripts/probe-cfwk-resolve-sim.log +0 -0
- package/scripts/probe-cfwk-tree-shape.log +0 -0
- package/scripts/probe-cfwk-walk-abort.log +0 -0
- package/scripts/probe-cfwk.log +0 -0
- package/scripts/probe-force-index.log +0 -0
- package/scripts/probe-no-force-index.log +0 -0
- package/scripts/probe-samefile-ir.stderr.log +0 -0
- package/scripts/probe-samefile-ir.stdout.log +224 -0
- package/scripts/probe-sdk-vs-project.log +0 -0
- package/scripts/probe-viewtree-downgrade.log +0 -0
- package/dist/arkts/ohos-api-index.d.ts +0 -15
- package/dist/arkts/ohos-api-index.d.ts.map +0 -1
- package/dist/arkts/ohos-api-index.js +0 -190
- package/dist/arkts/ohos-api-index.js.map +0 -1
- package/dist/arkts/ohos-sdk-input.d.ts +0 -36
- package/dist/arkts/ohos-sdk-input.d.ts.map +0 -1
- package/dist/arkts/ohos-sdk-input.js +0 -214
- package/dist/arkts/ohos-sdk-input.js.map +0 -1
- package/dist/extraction/languages/arkts-state-decorators.d.ts +0 -13
- package/dist/extraction/languages/arkts-state-decorators.d.ts.map +0 -1
- package/dist/extraction/languages/arkts-state-decorators.js +0 -26
- package/dist/extraction/languages/arkts-state-decorators.js.map +0 -1
- package/dist/extraction/languages/ohos-api-consumer.d.ts +0 -34
- package/dist/extraction/languages/ohos-api-consumer.d.ts.map +0 -1
- package/dist/extraction/languages/ohos-api-consumer.js +0 -283
- package/dist/extraction/languages/ohos-api-consumer.js.map +0 -1
- package/dist/spec/build/git-scanner.d.ts +0 -93
- package/dist/spec/build/git-scanner.d.ts.map +0 -1
- package/dist/spec/build/git-scanner.js +0 -254
- package/dist/spec/build/git-scanner.js.map +0 -1
- package/dist/spec/git-utils.d.ts +0 -8
- package/dist/spec/git-utils.d.ts.map +0 -1
- package/dist/spec/git-utils.js +0 -14
- package/dist/spec/git-utils.js.map +0 -1
- package/dist/spec/mine/clusterer.d.ts +0 -63
- package/dist/spec/mine/clusterer.d.ts.map +0 -1
- package/dist/spec/mine/clusterer.js +0 -904
- package/dist/spec/mine/clusterer.js.map +0 -1
- package/dist/spec/mine/progress-handler.d.ts +0 -22
- package/dist/spec/mine/progress-handler.d.ts.map +0 -1
- package/dist/spec/mine/progress-handler.js +0 -108
- package/dist/spec/mine/progress-handler.js.map +0 -1
- package/dist/spec/mine/progress.d.ts +0 -23
- package/dist/spec/mine/progress.d.ts.map +0 -1
- package/dist/spec/mine/progress.js +0 -12
- package/dist/spec/mine/progress.js.map +0 -1
|
@@ -1,378 +1,378 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Run boundary-eval experiments sequentially. Multi-agent + HomeGraph A/B arm support.
|
|
4
|
-
|
|
5
|
-
Usage:
|
|
6
|
-
python run_all.py --both-arms --agent deveco # 一条命令跑 baseline + homegraph
|
|
7
|
-
python run_all.py --agent deveco --arm homegraph
|
|
8
|
-
python run_all.py --agent deveco --arm baseline
|
|
9
|
-
python run_all.py 1-1 1-2 1-3
|
|
10
|
-
python run_all.py --repo-path /path/to/photos
|
|
11
|
-
python run_all.py --both-arms --agent deveco --repo-path /path/to/photos --skip-index
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import json
|
|
16
|
-
import os
|
|
17
|
-
import shutil
|
|
18
|
-
import subprocess
|
|
19
|
-
import sys
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from typing import Optional, Tuple
|
|
22
|
-
|
|
23
|
-
import run_one
|
|
24
|
-
import run_session
|
|
25
|
-
import analyze
|
|
26
|
-
from _utils import (DATA_DIR, EVAL_ROOT, LOG_DIR, OUTPUT_DIR, GREEN, NC, append_run_log,
|
|
27
|
-
current_time_ms, create_output_dirs, error, get_agent, header,
|
|
28
|
-
info, load_config, log, subprocess_no_window_kwargs, verify_agent_binary, warn)
|
|
29
|
-
from deveco_arm import (ensure_homegraph_index, git_clean_repo,
|
|
30
|
-
write_deveco_project_config)
|
|
31
|
-
|
|
32
|
-
DEFAULT_EXPERIMENTS = ["1-1", "1-2", "1-3"]
|
|
33
|
-
SHARED_CLONE_DIR = DATA_DIR / "clone"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
_NW = subprocess_no_window_kwargs()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def clone_repo(url: str, branch: str, clone_dir: Path) -> bool:
|
|
40
|
-
clone_dir = Path(clone_dir)
|
|
41
|
-
if (clone_dir / ".git").is_dir():
|
|
42
|
-
log(f"Clone exists, updating: {clone_dir}")
|
|
43
|
-
subprocess.run(["git", "fetch", "origin", branch], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
44
|
-
subprocess.run(["git", "checkout", branch], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
45
|
-
subprocess.run(["git", "reset", "--hard", f"origin/{branch}"], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
46
|
-
return True
|
|
47
|
-
log(f"Cloning {url} (branch: {branch})...")
|
|
48
|
-
if clone_dir.exists():
|
|
49
|
-
shutil.rmtree(clone_dir)
|
|
50
|
-
clone_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
-
r = subprocess.run(
|
|
52
|
-
["git", "clone", "--depth", "1", "--branch", branch, url, str(clone_dir)],
|
|
53
|
-
capture_output=True, text=True,
|
|
54
|
-
**_NW,
|
|
55
|
-
)
|
|
56
|
-
if r.returncode != 0:
|
|
57
|
-
error(f"Clone failed: {r.stderr}")
|
|
58
|
-
return False
|
|
59
|
-
r2 = subprocess.run(["git", "rev-parse", "--short", "HEAD"], capture_output=True, text=True, cwd=clone_dir, **_NW)
|
|
60
|
-
log(f"Clone complete: {r2.stdout.strip()}")
|
|
61
|
-
return True
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def reset_clone(clone_dir: Path, branch: str, arm: str = "baseline"):
|
|
65
|
-
subprocess.run(["git", "checkout", branch], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
66
|
-
subprocess.run(["git", "checkout", "."], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
67
|
-
git_clean_repo(clone_dir, arm)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def reset_local(work_dir: Path, arm: str = "baseline"):
|
|
71
|
-
subprocess.run(["git", "checkout", "."], cwd=work_dir, capture_output=True, text=True, **_NW)
|
|
72
|
-
git_clean_repo(work_dir, arm)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def ensure_homegraph_index_for_arm(
|
|
76
|
-
repo_root: Path, arm: str, *, skip_index: bool = False,
|
|
77
|
-
) -> dict:
|
|
78
|
-
"""Index repo when running the homegraph arm."""
|
|
79
|
-
if arm != "homegraph":
|
|
80
|
-
return {"homegraph_index_ms": 0, "homegraph_index_success": None}
|
|
81
|
-
return ensure_homegraph_index(repo_root, skip=skip_index)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def write_run_manifest(state_dir: Path, results_dir: Path, meta: dict):
|
|
85
|
-
manifest = {
|
|
86
|
-
**meta,
|
|
87
|
-
"results_dir": str(results_dir),
|
|
88
|
-
"state_dir": str(state_dir),
|
|
89
|
-
}
|
|
90
|
-
for d in (state_dir, results_dir):
|
|
91
|
-
d.mkdir(parents=True, exist_ok=True)
|
|
92
|
-
(d / "run_manifest.json").write_text(
|
|
93
|
-
json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def resolve_work_dir(args, state_dir: Path) -> Tuple[Path, Optional[Path], bool]:
|
|
98
|
-
"""Return (work_dir, ephemeral_clone_dir_or_none, owns_ephemeral_clone)."""
|
|
99
|
-
if args.repo_path:
|
|
100
|
-
work_dir = Path(args.repo_path).resolve()
|
|
101
|
-
if not (work_dir / ".git").is_dir():
|
|
102
|
-
error(f"--repo-path 不是 git 仓库: {work_dir}")
|
|
103
|
-
sys.exit(1)
|
|
104
|
-
os.environ["REPO_PATH"] = str(work_dir)
|
|
105
|
-
os.environ.pop("REPO_CLONE", None)
|
|
106
|
-
log(f"使用本地仓库: {work_dir}")
|
|
107
|
-
return work_dir, None, False
|
|
108
|
-
|
|
109
|
-
if args.both_arms:
|
|
110
|
-
clone_dir = SHARED_CLONE_DIR
|
|
111
|
-
if not clone_repo(args.repo_url, args.branch, clone_dir):
|
|
112
|
-
error("Clone 失败。请检查网络,或改用 --repo-path。")
|
|
113
|
-
sys.exit(1)
|
|
114
|
-
os.environ["REPO_PATH"] = str(clone_dir)
|
|
115
|
-
os.environ.pop("REPO_CLONE", None)
|
|
116
|
-
log(f"A/B 共用仓库: {clone_dir}")
|
|
117
|
-
return clone_dir, None, False
|
|
118
|
-
|
|
119
|
-
clone_dir = state_dir / "clone"
|
|
120
|
-
if not clone_repo(args.repo_url, args.branch, clone_dir):
|
|
121
|
-
error("Clone 失败。请改用 --repo-path 指定本地 checkout。")
|
|
122
|
-
sys.exit(1)
|
|
123
|
-
os.environ["REPO_CLONE"] = str(clone_dir)
|
|
124
|
-
os.environ.pop("REPO_PATH", None)
|
|
125
|
-
return clone_dir, clone_dir, True
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def run_suite(
|
|
129
|
-
args, arm: str, *, prebuilt_index_meta: Optional[dict] = None,
|
|
130
|
-
) -> dict:
|
|
131
|
-
"""Run one arm (baseline or homegraph). Returns summary dict."""
|
|
132
|
-
agent = get_agent(args.agent)
|
|
133
|
-
skip_perms = not args.no_skip_permissions
|
|
134
|
-
experiments = args.experiments or DEFAULT_EXPERIMENTS
|
|
135
|
-
total = len(experiments)
|
|
136
|
-
passed = 0
|
|
137
|
-
failed = 0
|
|
138
|
-
|
|
139
|
-
results_dir, state_dir = create_output_dirs(args.agent or "default", arm)
|
|
140
|
-
work_dir, ephemeral_clone, owns_clone = resolve_work_dir(args, state_dir)
|
|
141
|
-
|
|
142
|
-
if arm == "baseline":
|
|
143
|
-
hg_dir = work_dir / ".homegraph"
|
|
144
|
-
if hg_dir.exists():
|
|
145
|
-
log("Baseline: 保留 .homegraph(A/B 共用仓库;baseline 通过 deveco.jsonc deny homegraph_* 隔离)")
|
|
146
|
-
|
|
147
|
-
if prebuilt_index_meta is not None:
|
|
148
|
-
index_meta = prebuilt_index_meta
|
|
149
|
-
else:
|
|
150
|
-
index_meta = ensure_homegraph_index_for_arm(
|
|
151
|
-
work_dir, arm, skip_index=getattr(args, "skip_index", False),
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
if arm == "homegraph" and not index_meta.get("homegraph_index_success"):
|
|
155
|
-
error("HomeGraph 索引失败,homegraph 组无法评测。")
|
|
156
|
-
err = index_meta.get("homegraph_index_error", "")
|
|
157
|
-
if err:
|
|
158
|
-
error(f" 原因: {err}")
|
|
159
|
-
error(" 请先在 homegraph 仓库执行 npm run build,再重试 run_all.py --arm homegraph")
|
|
160
|
-
append_run_log(state_dir, f"ABORT: homegraph index failed: {err}")
|
|
161
|
-
write_run_manifest(state_dir, results_dir, {
|
|
162
|
-
"arm": arm, "agent": agent["name"], "agent_key": args.agent,
|
|
163
|
-
"branch": args.branch, "repo_url": args.repo_url,
|
|
164
|
-
"repo_root": str(work_dir), "experiments": experiments,
|
|
165
|
-
"aborted": True, **index_meta,
|
|
166
|
-
})
|
|
167
|
-
sys.exit(1)
|
|
168
|
-
|
|
169
|
-
if agent.get("binary") == "deveco":
|
|
170
|
-
write_deveco_project_config(work_dir, arm)
|
|
171
|
-
|
|
172
|
-
write_run_manifest(state_dir, results_dir, {
|
|
173
|
-
"arm": arm,
|
|
174
|
-
"agent": agent["name"],
|
|
175
|
-
"agent_key": args.agent,
|
|
176
|
-
"branch": args.branch,
|
|
177
|
-
"repo_url": args.repo_url,
|
|
178
|
-
"repo_root": str(work_dir),
|
|
179
|
-
"experiments": experiments,
|
|
180
|
-
**index_meta,
|
|
181
|
-
})
|
|
182
|
-
append_run_log(state_dir, f"开始: arm={arm}, agent={agent['name']}, exps={experiments}")
|
|
183
|
-
|
|
184
|
-
os.environ["EVAL_ARM"] = arm
|
|
185
|
-
|
|
186
|
-
header(f"边界评测 — 档位切换 ({arm})")
|
|
187
|
-
print(f"组别 : {arm}")
|
|
188
|
-
print(f"Agent : {agent['name']} ({agent['binary']})")
|
|
189
|
-
print(f"仓库 : {work_dir}")
|
|
190
|
-
print(f"分支 : {args.branch}")
|
|
191
|
-
print(f"权限 : {'跳过确认' if skip_perms else '需确认'}")
|
|
192
|
-
print(f"实验数 : {total}")
|
|
193
|
-
print(f"输出 : {results_dir}")
|
|
194
|
-
print(f"日志 : {state_dir}\n")
|
|
195
|
-
|
|
196
|
-
overall_start = current_time_ms()
|
|
197
|
-
|
|
198
|
-
for exp_id in experiments:
|
|
199
|
-
header(f"实验 {exp_id}")
|
|
200
|
-
append_run_log(state_dir, f"开始实验 {exp_id}")
|
|
201
|
-
|
|
202
|
-
if ephemeral_clone:
|
|
203
|
-
log("重置 clone 到干净状态...")
|
|
204
|
-
reset_clone(ephemeral_clone, args.branch, arm)
|
|
205
|
-
else:
|
|
206
|
-
log("重置本地仓库...")
|
|
207
|
-
reset_local(work_dir, arm)
|
|
208
|
-
|
|
209
|
-
if exp_id == "5":
|
|
210
|
-
ok = run_session.run_session("5", skip_perms, args.agent,
|
|
211
|
-
results_dir=results_dir, state_dir=state_dir)
|
|
212
|
-
else:
|
|
213
|
-
ok = run_one.run_experiment(exp_id, skip_perms, args.agent,
|
|
214
|
-
results_dir=results_dir, state_dir=state_dir)
|
|
215
|
-
|
|
216
|
-
if ok:
|
|
217
|
-
passed += 1
|
|
218
|
-
else:
|
|
219
|
-
failed += 1
|
|
220
|
-
error(f"实验 {exp_id} 失败")
|
|
221
|
-
print()
|
|
222
|
-
|
|
223
|
-
overall_end = current_time_ms()
|
|
224
|
-
duration = (overall_end - overall_start) // 1000
|
|
225
|
-
|
|
226
|
-
append_run_log(state_dir, f"完成: passed={passed}, failed={failed}, duration_s={duration}")
|
|
227
|
-
|
|
228
|
-
header(f"组别完成 — {arm}")
|
|
229
|
-
print(f" 通过 : {passed} / {total}")
|
|
230
|
-
print(f" 失败 : {failed} / {total}")
|
|
231
|
-
print(f" 耗时 : {duration // 60}m {duration % 60}s")
|
|
232
|
-
print(f" 输出 : {results_dir}")
|
|
233
|
-
print(f" 日志 : {state_dir}")
|
|
234
|
-
|
|
235
|
-
if owns_clone and ephemeral_clone and not args.keep_clone and ephemeral_clone.exists():
|
|
236
|
-
warn(f"删除临时 clone: {ephemeral_clone}")
|
|
237
|
-
shutil.rmtree(ephemeral_clone)
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
"arm": arm,
|
|
241
|
-
"results_dir": results_dir,
|
|
242
|
-
"state_dir": state_dir,
|
|
243
|
-
"passed": passed,
|
|
244
|
-
"failed": failed,
|
|
245
|
-
"duration_s": duration,
|
|
246
|
-
"homegraph_index_ms": index_meta.get("homegraph_index_ms", 0),
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _generate_reports(summaries: list, agent_key: str):
|
|
251
|
-
"""Run analyze.py logic after experiments; print report paths."""
|
|
252
|
-
reports = []
|
|
253
|
-
for s in summaries:
|
|
254
|
-
try:
|
|
255
|
-
path = analyze.analyze_run(s["results_dir"], verbose=False)
|
|
256
|
-
reports.append((s["arm"], path))
|
|
257
|
-
except ValueError as e:
|
|
258
|
-
warn(f"分析 {s['arm']} 失败: {e}")
|
|
259
|
-
|
|
260
|
-
compare_path = None
|
|
261
|
-
if len(summaries) == 2 and len(reports) == 2:
|
|
262
|
-
try:
|
|
263
|
-
baseline_s = next(s for s in summaries if s["arm"] == "baseline")
|
|
264
|
-
homegraph_s = next(s for s in summaries if s["arm"] == "homegraph")
|
|
265
|
-
compare_path = analyze.compare_runs(
|
|
266
|
-
baseline_s["results_dir"],
|
|
267
|
-
homegraph_s["results_dir"],
|
|
268
|
-
agent=agent_key,
|
|
269
|
-
baseline_wall_s=baseline_s["duration_s"],
|
|
270
|
-
homegraph_wall_s=homegraph_s["duration_s"],
|
|
271
|
-
verbose=True,
|
|
272
|
-
)
|
|
273
|
-
except ValueError as e:
|
|
274
|
-
warn(f"A/B 对比报告失败: {e}")
|
|
275
|
-
|
|
276
|
-
baseline_report = next((p for arm, p in reports if arm == "baseline"), None)
|
|
277
|
-
homegraph_report = next((p for arm, p in reports if arm == "homegraph"), None)
|
|
278
|
-
single_report = reports[0][1] if len(reports) == 1 else None
|
|
279
|
-
|
|
280
|
-
analyze.print_report_locations(
|
|
281
|
-
baseline_report=baseline_report,
|
|
282
|
-
homegraph_report=homegraph_report,
|
|
283
|
-
compare_report=compare_path,
|
|
284
|
-
single_report=single_report,
|
|
285
|
-
)
|
|
286
|
-
return reports, compare_path
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def _blocking_homegraph_index(work_dir: Path, *, skip_index: bool = False) -> dict:
|
|
290
|
-
"""Run full ``homegraph index`` and block until complete (or validate existing index)."""
|
|
291
|
-
if skip_index:
|
|
292
|
-
header("HomeGraph 索引检查(--skip-index,不执行 index)")
|
|
293
|
-
else:
|
|
294
|
-
header("HomeGraph 全量 index(阻塞,Photos 约 15–30 分钟)")
|
|
295
|
-
log(f"仓库: {work_dir}")
|
|
296
|
-
if skip_index:
|
|
297
|
-
log("模式: 仅校验已有 .homegraph/(可从同 commit 的其他机器复制整个目录)")
|
|
298
|
-
else:
|
|
299
|
-
log("命令: node --stack-size=8192 … homegraph index(见 deveco_arm.HOMEGRAPH_NODE_STACK_SIZE_KB)")
|
|
300
|
-
meta = ensure_homegraph_index(work_dir, skip=skip_index)
|
|
301
|
-
if not meta.get("homegraph_index_success"):
|
|
302
|
-
error("HomeGraph index 失败,中止。")
|
|
303
|
-
err = meta.get("homegraph_index_error", "")
|
|
304
|
-
if err:
|
|
305
|
-
error(f" 原因: {err}")
|
|
306
|
-
sys.exit(1)
|
|
307
|
-
return meta
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def main():
|
|
311
|
-
analyze._configure_stdout()
|
|
312
|
-
cfg = load_config()
|
|
313
|
-
meta = cfg.get("meta", {})
|
|
314
|
-
|
|
315
|
-
parser = argparse.ArgumentParser(description="HomeGraph 档位切换边界评测")
|
|
316
|
-
parser.add_argument("--agent", default=os.environ.get("EVAL_AGENT", "deveco"),
|
|
317
|
-
help="Agent:claude / cursor / codex / opencode / deveco")
|
|
318
|
-
parser.add_argument("--arm", default=os.environ.get("EVAL_ARM", "baseline"),
|
|
319
|
-
choices=["baseline", "homegraph"],
|
|
320
|
-
help="baseline=不带 HomeGraph;homegraph=带 HomeGraph")
|
|
321
|
-
parser.add_argument("--both-arms", action="store_true",
|
|
322
|
-
help="一条命令依次跑 baseline 和 homegraph 两组 A/B")
|
|
323
|
-
parser.add_argument("--repo-path", default=os.environ.get("REPO_PATH", ""),
|
|
324
|
-
help="使用已有本地 clone,跳过 git clone")
|
|
325
|
-
parser.add_argument("--keep-clone", action="store_true")
|
|
326
|
-
parser.add_argument(
|
|
327
|
-
"--skip-index",
|
|
328
|
-
action="store_true",
|
|
329
|
-
help="不跑 homegraph init/index;要求仓库根目录已有健康的 .homegraph/(可复制自同 commit 的其他 checkout)",
|
|
330
|
-
)
|
|
331
|
-
parser.add_argument("--no-skip-permissions", action="store_true")
|
|
332
|
-
parser.add_argument("--branch", default=os.environ.get("BRANCH", meta.get("default_branch", "weekly_20260601")))
|
|
333
|
-
parser.add_argument("--repo-url", default=os.environ.get("REPO_URL", meta.get("repo_url", "")))
|
|
334
|
-
parser.add_argument("experiments", nargs="*")
|
|
335
|
-
args = parser.parse_args()
|
|
336
|
-
|
|
337
|
-
if not args.experiments:
|
|
338
|
-
args.experiments = DEFAULT_EXPERIMENTS
|
|
339
|
-
|
|
340
|
-
agent = get_agent(args.agent)
|
|
341
|
-
ok, resolved, err_msg = verify_agent_binary(agent)
|
|
342
|
-
if not ok:
|
|
343
|
-
error(err_msg)
|
|
344
|
-
sys.exit(1)
|
|
345
|
-
log(f"Agent CLI: {resolved}")
|
|
346
|
-
|
|
347
|
-
if args.both_arms and args.arm != "baseline":
|
|
348
|
-
warn("--both-arms 已指定,忽略 --arm")
|
|
349
|
-
|
|
350
|
-
if args.both_arms:
|
|
351
|
-
_rd, _sd = create_output_dirs(args.agent or "default", "baseline")
|
|
352
|
-
work_dir, _, _ = resolve_work_dir(args, _sd)
|
|
353
|
-
|
|
354
|
-
# Index FIRST so the user sees it immediately (15–30 min); baseline does not use MCP.
|
|
355
|
-
index_meta = _blocking_homegraph_index(work_dir, skip_index=args.skip_index)
|
|
356
|
-
|
|
357
|
-
summaries = []
|
|
358
|
-
print(f"\n{'=' * 60}\n 开始 homegraph 组(先看 HomeGraph 是否生效)\n{'=' * 60}\n")
|
|
359
|
-
summaries.append(run_suite(args, "homegraph", prebuilt_index_meta=index_meta))
|
|
360
|
-
|
|
361
|
-
print(f"\n{'=' * 60}\n 开始 baseline 组(无 HomeGraph)\n{'=' * 60}\n")
|
|
362
|
-
summaries.append(run_suite(args, "baseline"))
|
|
363
|
-
|
|
364
|
-
header("A/B 全部完成")
|
|
365
|
-
for s in summaries:
|
|
366
|
-
print(f" [{s['arm']}] 通过 {s['passed']},失败 {s['failed']},"
|
|
367
|
-
f"耗时 {s['duration_s'] // 60}m{s['duration_s'] % 60}s")
|
|
368
|
-
print(f" 输出 → {s['results_dir']}")
|
|
369
|
-
_generate_reports(summaries, args.agent or "default")
|
|
370
|
-
sys.exit(0 if all(s["failed"] == 0 for s in summaries) else 1)
|
|
371
|
-
|
|
372
|
-
summary = run_suite(args, args.arm)
|
|
373
|
-
_generate_reports([summary], args.agent or "default")
|
|
374
|
-
sys.exit(0 if summary["failed"] == 0 else 1)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if __name__ == "__main__":
|
|
378
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Run boundary-eval experiments sequentially. Multi-agent + HomeGraph A/B arm support.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python run_all.py --both-arms --agent deveco # 一条命令跑 baseline + homegraph
|
|
7
|
+
python run_all.py --agent deveco --arm homegraph
|
|
8
|
+
python run_all.py --agent deveco --arm baseline
|
|
9
|
+
python run_all.py 1-1 1-2 1-3
|
|
10
|
+
python run_all.py --repo-path /path/to/photos
|
|
11
|
+
python run_all.py --both-arms --agent deveco --repo-path /path/to/photos --skip-index
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional, Tuple
|
|
22
|
+
|
|
23
|
+
import run_one
|
|
24
|
+
import run_session
|
|
25
|
+
import analyze
|
|
26
|
+
from _utils import (DATA_DIR, EVAL_ROOT, LOG_DIR, OUTPUT_DIR, GREEN, NC, append_run_log,
|
|
27
|
+
current_time_ms, create_output_dirs, error, get_agent, header,
|
|
28
|
+
info, load_config, log, subprocess_no_window_kwargs, verify_agent_binary, warn)
|
|
29
|
+
from deveco_arm import (ensure_homegraph_index, git_clean_repo,
|
|
30
|
+
write_deveco_project_config)
|
|
31
|
+
|
|
32
|
+
DEFAULT_EXPERIMENTS = ["1-1", "1-2", "1-3"]
|
|
33
|
+
SHARED_CLONE_DIR = DATA_DIR / "clone"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_NW = subprocess_no_window_kwargs()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def clone_repo(url: str, branch: str, clone_dir: Path) -> bool:
|
|
40
|
+
clone_dir = Path(clone_dir)
|
|
41
|
+
if (clone_dir / ".git").is_dir():
|
|
42
|
+
log(f"Clone exists, updating: {clone_dir}")
|
|
43
|
+
subprocess.run(["git", "fetch", "origin", branch], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
44
|
+
subprocess.run(["git", "checkout", branch], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
45
|
+
subprocess.run(["git", "reset", "--hard", f"origin/{branch}"], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
46
|
+
return True
|
|
47
|
+
log(f"Cloning {url} (branch: {branch})...")
|
|
48
|
+
if clone_dir.exists():
|
|
49
|
+
shutil.rmtree(clone_dir)
|
|
50
|
+
clone_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
r = subprocess.run(
|
|
52
|
+
["git", "clone", "--depth", "1", "--branch", branch, url, str(clone_dir)],
|
|
53
|
+
capture_output=True, text=True,
|
|
54
|
+
**_NW,
|
|
55
|
+
)
|
|
56
|
+
if r.returncode != 0:
|
|
57
|
+
error(f"Clone failed: {r.stderr}")
|
|
58
|
+
return False
|
|
59
|
+
r2 = subprocess.run(["git", "rev-parse", "--short", "HEAD"], capture_output=True, text=True, cwd=clone_dir, **_NW)
|
|
60
|
+
log(f"Clone complete: {r2.stdout.strip()}")
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def reset_clone(clone_dir: Path, branch: str, arm: str = "baseline"):
|
|
65
|
+
subprocess.run(["git", "checkout", branch], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
66
|
+
subprocess.run(["git", "checkout", "."], cwd=clone_dir, capture_output=True, text=True, **_NW)
|
|
67
|
+
git_clean_repo(clone_dir, arm)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def reset_local(work_dir: Path, arm: str = "baseline"):
|
|
71
|
+
subprocess.run(["git", "checkout", "."], cwd=work_dir, capture_output=True, text=True, **_NW)
|
|
72
|
+
git_clean_repo(work_dir, arm)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def ensure_homegraph_index_for_arm(
|
|
76
|
+
repo_root: Path, arm: str, *, skip_index: bool = False,
|
|
77
|
+
) -> dict:
|
|
78
|
+
"""Index repo when running the homegraph arm."""
|
|
79
|
+
if arm != "homegraph":
|
|
80
|
+
return {"homegraph_index_ms": 0, "homegraph_index_success": None}
|
|
81
|
+
return ensure_homegraph_index(repo_root, skip=skip_index)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def write_run_manifest(state_dir: Path, results_dir: Path, meta: dict):
|
|
85
|
+
manifest = {
|
|
86
|
+
**meta,
|
|
87
|
+
"results_dir": str(results_dir),
|
|
88
|
+
"state_dir": str(state_dir),
|
|
89
|
+
}
|
|
90
|
+
for d in (state_dir, results_dir):
|
|
91
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
(d / "run_manifest.json").write_text(
|
|
93
|
+
json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def resolve_work_dir(args, state_dir: Path) -> Tuple[Path, Optional[Path], bool]:
|
|
98
|
+
"""Return (work_dir, ephemeral_clone_dir_or_none, owns_ephemeral_clone)."""
|
|
99
|
+
if args.repo_path:
|
|
100
|
+
work_dir = Path(args.repo_path).resolve()
|
|
101
|
+
if not (work_dir / ".git").is_dir():
|
|
102
|
+
error(f"--repo-path 不是 git 仓库: {work_dir}")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
os.environ["REPO_PATH"] = str(work_dir)
|
|
105
|
+
os.environ.pop("REPO_CLONE", None)
|
|
106
|
+
log(f"使用本地仓库: {work_dir}")
|
|
107
|
+
return work_dir, None, False
|
|
108
|
+
|
|
109
|
+
if args.both_arms:
|
|
110
|
+
clone_dir = SHARED_CLONE_DIR
|
|
111
|
+
if not clone_repo(args.repo_url, args.branch, clone_dir):
|
|
112
|
+
error("Clone 失败。请检查网络,或改用 --repo-path。")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
os.environ["REPO_PATH"] = str(clone_dir)
|
|
115
|
+
os.environ.pop("REPO_CLONE", None)
|
|
116
|
+
log(f"A/B 共用仓库: {clone_dir}")
|
|
117
|
+
return clone_dir, None, False
|
|
118
|
+
|
|
119
|
+
clone_dir = state_dir / "clone"
|
|
120
|
+
if not clone_repo(args.repo_url, args.branch, clone_dir):
|
|
121
|
+
error("Clone 失败。请改用 --repo-path 指定本地 checkout。")
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
os.environ["REPO_CLONE"] = str(clone_dir)
|
|
124
|
+
os.environ.pop("REPO_PATH", None)
|
|
125
|
+
return clone_dir, clone_dir, True
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def run_suite(
|
|
129
|
+
args, arm: str, *, prebuilt_index_meta: Optional[dict] = None,
|
|
130
|
+
) -> dict:
|
|
131
|
+
"""Run one arm (baseline or homegraph). Returns summary dict."""
|
|
132
|
+
agent = get_agent(args.agent)
|
|
133
|
+
skip_perms = not args.no_skip_permissions
|
|
134
|
+
experiments = args.experiments or DEFAULT_EXPERIMENTS
|
|
135
|
+
total = len(experiments)
|
|
136
|
+
passed = 0
|
|
137
|
+
failed = 0
|
|
138
|
+
|
|
139
|
+
results_dir, state_dir = create_output_dirs(args.agent or "default", arm)
|
|
140
|
+
work_dir, ephemeral_clone, owns_clone = resolve_work_dir(args, state_dir)
|
|
141
|
+
|
|
142
|
+
if arm == "baseline":
|
|
143
|
+
hg_dir = work_dir / ".homegraph"
|
|
144
|
+
if hg_dir.exists():
|
|
145
|
+
log("Baseline: 保留 .homegraph(A/B 共用仓库;baseline 通过 deveco.jsonc deny homegraph_* 隔离)")
|
|
146
|
+
|
|
147
|
+
if prebuilt_index_meta is not None:
|
|
148
|
+
index_meta = prebuilt_index_meta
|
|
149
|
+
else:
|
|
150
|
+
index_meta = ensure_homegraph_index_for_arm(
|
|
151
|
+
work_dir, arm, skip_index=getattr(args, "skip_index", False),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if arm == "homegraph" and not index_meta.get("homegraph_index_success"):
|
|
155
|
+
error("HomeGraph 索引失败,homegraph 组无法评测。")
|
|
156
|
+
err = index_meta.get("homegraph_index_error", "")
|
|
157
|
+
if err:
|
|
158
|
+
error(f" 原因: {err}")
|
|
159
|
+
error(" 请先在 homegraph 仓库执行 npm run build,再重试 run_all.py --arm homegraph")
|
|
160
|
+
append_run_log(state_dir, f"ABORT: homegraph index failed: {err}")
|
|
161
|
+
write_run_manifest(state_dir, results_dir, {
|
|
162
|
+
"arm": arm, "agent": agent["name"], "agent_key": args.agent,
|
|
163
|
+
"branch": args.branch, "repo_url": args.repo_url,
|
|
164
|
+
"repo_root": str(work_dir), "experiments": experiments,
|
|
165
|
+
"aborted": True, **index_meta,
|
|
166
|
+
})
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
|
|
169
|
+
if agent.get("binary") == "deveco":
|
|
170
|
+
write_deveco_project_config(work_dir, arm)
|
|
171
|
+
|
|
172
|
+
write_run_manifest(state_dir, results_dir, {
|
|
173
|
+
"arm": arm,
|
|
174
|
+
"agent": agent["name"],
|
|
175
|
+
"agent_key": args.agent,
|
|
176
|
+
"branch": args.branch,
|
|
177
|
+
"repo_url": args.repo_url,
|
|
178
|
+
"repo_root": str(work_dir),
|
|
179
|
+
"experiments": experiments,
|
|
180
|
+
**index_meta,
|
|
181
|
+
})
|
|
182
|
+
append_run_log(state_dir, f"开始: arm={arm}, agent={agent['name']}, exps={experiments}")
|
|
183
|
+
|
|
184
|
+
os.environ["EVAL_ARM"] = arm
|
|
185
|
+
|
|
186
|
+
header(f"边界评测 — 档位切换 ({arm})")
|
|
187
|
+
print(f"组别 : {arm}")
|
|
188
|
+
print(f"Agent : {agent['name']} ({agent['binary']})")
|
|
189
|
+
print(f"仓库 : {work_dir}")
|
|
190
|
+
print(f"分支 : {args.branch}")
|
|
191
|
+
print(f"权限 : {'跳过确认' if skip_perms else '需确认'}")
|
|
192
|
+
print(f"实验数 : {total}")
|
|
193
|
+
print(f"输出 : {results_dir}")
|
|
194
|
+
print(f"日志 : {state_dir}\n")
|
|
195
|
+
|
|
196
|
+
overall_start = current_time_ms()
|
|
197
|
+
|
|
198
|
+
for exp_id in experiments:
|
|
199
|
+
header(f"实验 {exp_id}")
|
|
200
|
+
append_run_log(state_dir, f"开始实验 {exp_id}")
|
|
201
|
+
|
|
202
|
+
if ephemeral_clone:
|
|
203
|
+
log("重置 clone 到干净状态...")
|
|
204
|
+
reset_clone(ephemeral_clone, args.branch, arm)
|
|
205
|
+
else:
|
|
206
|
+
log("重置本地仓库...")
|
|
207
|
+
reset_local(work_dir, arm)
|
|
208
|
+
|
|
209
|
+
if exp_id == "5":
|
|
210
|
+
ok = run_session.run_session("5", skip_perms, args.agent,
|
|
211
|
+
results_dir=results_dir, state_dir=state_dir)
|
|
212
|
+
else:
|
|
213
|
+
ok = run_one.run_experiment(exp_id, skip_perms, args.agent,
|
|
214
|
+
results_dir=results_dir, state_dir=state_dir)
|
|
215
|
+
|
|
216
|
+
if ok:
|
|
217
|
+
passed += 1
|
|
218
|
+
else:
|
|
219
|
+
failed += 1
|
|
220
|
+
error(f"实验 {exp_id} 失败")
|
|
221
|
+
print()
|
|
222
|
+
|
|
223
|
+
overall_end = current_time_ms()
|
|
224
|
+
duration = (overall_end - overall_start) // 1000
|
|
225
|
+
|
|
226
|
+
append_run_log(state_dir, f"完成: passed={passed}, failed={failed}, duration_s={duration}")
|
|
227
|
+
|
|
228
|
+
header(f"组别完成 — {arm}")
|
|
229
|
+
print(f" 通过 : {passed} / {total}")
|
|
230
|
+
print(f" 失败 : {failed} / {total}")
|
|
231
|
+
print(f" 耗时 : {duration // 60}m {duration % 60}s")
|
|
232
|
+
print(f" 输出 : {results_dir}")
|
|
233
|
+
print(f" 日志 : {state_dir}")
|
|
234
|
+
|
|
235
|
+
if owns_clone and ephemeral_clone and not args.keep_clone and ephemeral_clone.exists():
|
|
236
|
+
warn(f"删除临时 clone: {ephemeral_clone}")
|
|
237
|
+
shutil.rmtree(ephemeral_clone)
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
"arm": arm,
|
|
241
|
+
"results_dir": results_dir,
|
|
242
|
+
"state_dir": state_dir,
|
|
243
|
+
"passed": passed,
|
|
244
|
+
"failed": failed,
|
|
245
|
+
"duration_s": duration,
|
|
246
|
+
"homegraph_index_ms": index_meta.get("homegraph_index_ms", 0),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _generate_reports(summaries: list, agent_key: str):
|
|
251
|
+
"""Run analyze.py logic after experiments; print report paths."""
|
|
252
|
+
reports = []
|
|
253
|
+
for s in summaries:
|
|
254
|
+
try:
|
|
255
|
+
path = analyze.analyze_run(s["results_dir"], verbose=False)
|
|
256
|
+
reports.append((s["arm"], path))
|
|
257
|
+
except ValueError as e:
|
|
258
|
+
warn(f"分析 {s['arm']} 失败: {e}")
|
|
259
|
+
|
|
260
|
+
compare_path = None
|
|
261
|
+
if len(summaries) == 2 and len(reports) == 2:
|
|
262
|
+
try:
|
|
263
|
+
baseline_s = next(s for s in summaries if s["arm"] == "baseline")
|
|
264
|
+
homegraph_s = next(s for s in summaries if s["arm"] == "homegraph")
|
|
265
|
+
compare_path = analyze.compare_runs(
|
|
266
|
+
baseline_s["results_dir"],
|
|
267
|
+
homegraph_s["results_dir"],
|
|
268
|
+
agent=agent_key,
|
|
269
|
+
baseline_wall_s=baseline_s["duration_s"],
|
|
270
|
+
homegraph_wall_s=homegraph_s["duration_s"],
|
|
271
|
+
verbose=True,
|
|
272
|
+
)
|
|
273
|
+
except ValueError as e:
|
|
274
|
+
warn(f"A/B 对比报告失败: {e}")
|
|
275
|
+
|
|
276
|
+
baseline_report = next((p for arm, p in reports if arm == "baseline"), None)
|
|
277
|
+
homegraph_report = next((p for arm, p in reports if arm == "homegraph"), None)
|
|
278
|
+
single_report = reports[0][1] if len(reports) == 1 else None
|
|
279
|
+
|
|
280
|
+
analyze.print_report_locations(
|
|
281
|
+
baseline_report=baseline_report,
|
|
282
|
+
homegraph_report=homegraph_report,
|
|
283
|
+
compare_report=compare_path,
|
|
284
|
+
single_report=single_report,
|
|
285
|
+
)
|
|
286
|
+
return reports, compare_path
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _blocking_homegraph_index(work_dir: Path, *, skip_index: bool = False) -> dict:
|
|
290
|
+
"""Run full ``homegraph index`` and block until complete (or validate existing index)."""
|
|
291
|
+
if skip_index:
|
|
292
|
+
header("HomeGraph 索引检查(--skip-index,不执行 index)")
|
|
293
|
+
else:
|
|
294
|
+
header("HomeGraph 全量 index(阻塞,Photos 约 15–30 分钟)")
|
|
295
|
+
log(f"仓库: {work_dir}")
|
|
296
|
+
if skip_index:
|
|
297
|
+
log("模式: 仅校验已有 .homegraph/(可从同 commit 的其他机器复制整个目录)")
|
|
298
|
+
else:
|
|
299
|
+
log("命令: node --stack-size=8192 … homegraph index(见 deveco_arm.HOMEGRAPH_NODE_STACK_SIZE_KB)")
|
|
300
|
+
meta = ensure_homegraph_index(work_dir, skip=skip_index)
|
|
301
|
+
if not meta.get("homegraph_index_success"):
|
|
302
|
+
error("HomeGraph index 失败,中止。")
|
|
303
|
+
err = meta.get("homegraph_index_error", "")
|
|
304
|
+
if err:
|
|
305
|
+
error(f" 原因: {err}")
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
return meta
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def main():
|
|
311
|
+
analyze._configure_stdout()
|
|
312
|
+
cfg = load_config()
|
|
313
|
+
meta = cfg.get("meta", {})
|
|
314
|
+
|
|
315
|
+
parser = argparse.ArgumentParser(description="HomeGraph 档位切换边界评测")
|
|
316
|
+
parser.add_argument("--agent", default=os.environ.get("EVAL_AGENT", "deveco"),
|
|
317
|
+
help="Agent:claude / cursor / codex / opencode / deveco")
|
|
318
|
+
parser.add_argument("--arm", default=os.environ.get("EVAL_ARM", "baseline"),
|
|
319
|
+
choices=["baseline", "homegraph"],
|
|
320
|
+
help="baseline=不带 HomeGraph;homegraph=带 HomeGraph")
|
|
321
|
+
parser.add_argument("--both-arms", action="store_true",
|
|
322
|
+
help="一条命令依次跑 baseline 和 homegraph 两组 A/B")
|
|
323
|
+
parser.add_argument("--repo-path", default=os.environ.get("REPO_PATH", ""),
|
|
324
|
+
help="使用已有本地 clone,跳过 git clone")
|
|
325
|
+
parser.add_argument("--keep-clone", action="store_true")
|
|
326
|
+
parser.add_argument(
|
|
327
|
+
"--skip-index",
|
|
328
|
+
action="store_true",
|
|
329
|
+
help="不跑 homegraph init/index;要求仓库根目录已有健康的 .homegraph/(可复制自同 commit 的其他 checkout)",
|
|
330
|
+
)
|
|
331
|
+
parser.add_argument("--no-skip-permissions", action="store_true")
|
|
332
|
+
parser.add_argument("--branch", default=os.environ.get("BRANCH", meta.get("default_branch", "weekly_20260601")))
|
|
333
|
+
parser.add_argument("--repo-url", default=os.environ.get("REPO_URL", meta.get("repo_url", "")))
|
|
334
|
+
parser.add_argument("experiments", nargs="*")
|
|
335
|
+
args = parser.parse_args()
|
|
336
|
+
|
|
337
|
+
if not args.experiments:
|
|
338
|
+
args.experiments = DEFAULT_EXPERIMENTS
|
|
339
|
+
|
|
340
|
+
agent = get_agent(args.agent)
|
|
341
|
+
ok, resolved, err_msg = verify_agent_binary(agent)
|
|
342
|
+
if not ok:
|
|
343
|
+
error(err_msg)
|
|
344
|
+
sys.exit(1)
|
|
345
|
+
log(f"Agent CLI: {resolved}")
|
|
346
|
+
|
|
347
|
+
if args.both_arms and args.arm != "baseline":
|
|
348
|
+
warn("--both-arms 已指定,忽略 --arm")
|
|
349
|
+
|
|
350
|
+
if args.both_arms:
|
|
351
|
+
_rd, _sd = create_output_dirs(args.agent or "default", "baseline")
|
|
352
|
+
work_dir, _, _ = resolve_work_dir(args, _sd)
|
|
353
|
+
|
|
354
|
+
# Index FIRST so the user sees it immediately (15–30 min); baseline does not use MCP.
|
|
355
|
+
index_meta = _blocking_homegraph_index(work_dir, skip_index=args.skip_index)
|
|
356
|
+
|
|
357
|
+
summaries = []
|
|
358
|
+
print(f"\n{'=' * 60}\n 开始 homegraph 组(先看 HomeGraph 是否生效)\n{'=' * 60}\n")
|
|
359
|
+
summaries.append(run_suite(args, "homegraph", prebuilt_index_meta=index_meta))
|
|
360
|
+
|
|
361
|
+
print(f"\n{'=' * 60}\n 开始 baseline 组(无 HomeGraph)\n{'=' * 60}\n")
|
|
362
|
+
summaries.append(run_suite(args, "baseline"))
|
|
363
|
+
|
|
364
|
+
header("A/B 全部完成")
|
|
365
|
+
for s in summaries:
|
|
366
|
+
print(f" [{s['arm']}] 通过 {s['passed']},失败 {s['failed']},"
|
|
367
|
+
f"耗时 {s['duration_s'] // 60}m{s['duration_s'] % 60}s")
|
|
368
|
+
print(f" 输出 → {s['results_dir']}")
|
|
369
|
+
_generate_reports(summaries, args.agent or "default")
|
|
370
|
+
sys.exit(0 if all(s["failed"] == 0 for s in summaries) else 1)
|
|
371
|
+
|
|
372
|
+
summary = run_suite(args, args.arm)
|
|
373
|
+
_generate_reports([summary], args.agent or "default")
|
|
374
|
+
sys.exit(0 if summary["failed"] == 0 else 1)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
main()
|