homegraph 1.2.0 → 1.4.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.
Files changed (284) hide show
  1. package/dist/bin/homegraph.js +3 -0
  2. package/dist/bin/homegraph.js.map +1 -1
  3. package/dist/context/index.d.ts.map +1 -1
  4. package/dist/context/index.js +85 -0
  5. package/dist/context/index.js.map +1 -1
  6. package/dist/db/index.d.ts +64 -5
  7. package/dist/db/index.d.ts.map +1 -1
  8. package/dist/db/index.js +178 -14
  9. package/dist/db/index.js.map +1 -1
  10. package/dist/db/migrations.d.ts +1 -1
  11. package/dist/db/migrations.d.ts.map +1 -1
  12. package/dist/db/migrations.js +19 -1
  13. package/dist/db/migrations.js.map +1 -1
  14. package/dist/db/queries.d.ts +117 -2
  15. package/dist/db/queries.d.ts.map +1 -1
  16. package/dist/db/queries.js +299 -6
  17. package/dist/db/queries.js.map +1 -1
  18. package/dist/db/schema.sql +32 -1
  19. package/dist/db/wal-valve.d.ts +106 -0
  20. package/dist/db/wal-valve.d.ts.map +1 -0
  21. package/dist/db/wal-valve.js +208 -0
  22. package/dist/db/wal-valve.js.map +1 -0
  23. package/dist/directory.d.ts +9 -5
  24. package/dist/directory.d.ts.map +1 -1
  25. package/dist/directory.js +210 -19
  26. package/dist/directory.js.map +1 -1
  27. package/dist/extraction/cfml-extractor.d.ts +107 -0
  28. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  29. package/dist/extraction/cfml-extractor.js +494 -0
  30. package/dist/extraction/cfml-extractor.js.map +1 -0
  31. package/dist/extraction/grammars.d.ts +25 -1
  32. package/dist/extraction/grammars.d.ts.map +1 -1
  33. package/dist/extraction/grammars.js +177 -24
  34. package/dist/extraction/grammars.js.map +1 -1
  35. package/dist/extraction/index.d.ts +43 -4
  36. package/dist/extraction/index.d.ts.map +1 -1
  37. package/dist/extraction/index.js +308 -5
  38. package/dist/extraction/index.js.map +1 -1
  39. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  40. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  41. package/dist/extraction/languages/c-cpp.js +369 -4
  42. package/dist/extraction/languages/c-cpp.js.map +1 -1
  43. package/dist/extraction/languages/cfquery.d.ts +12 -0
  44. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  45. package/dist/extraction/languages/cfquery.js +28 -0
  46. package/dist/extraction/languages/cfquery.js.map +1 -0
  47. package/dist/extraction/languages/cfscript.d.ts +3 -0
  48. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  49. package/dist/extraction/languages/cfscript.js +73 -0
  50. package/dist/extraction/languages/cfscript.js.map +1 -0
  51. package/dist/extraction/languages/cobol.d.ts +33 -0
  52. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  53. package/dist/extraction/languages/cobol.js +499 -0
  54. package/dist/extraction/languages/cobol.js.map +1 -0
  55. package/dist/extraction/languages/erlang.d.ts +3 -0
  56. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  57. package/dist/extraction/languages/erlang.js +350 -0
  58. package/dist/extraction/languages/erlang.js.map +1 -0
  59. package/dist/extraction/languages/index.d.ts.map +1 -1
  60. package/dist/extraction/languages/index.js +16 -0
  61. package/dist/extraction/languages/index.js.map +1 -1
  62. package/dist/extraction/languages/nix.d.ts +3 -0
  63. package/dist/extraction/languages/nix.d.ts.map +1 -0
  64. package/dist/extraction/languages/nix.js +294 -0
  65. package/dist/extraction/languages/nix.js.map +1 -0
  66. package/dist/extraction/languages/solidity.d.ts +3 -0
  67. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  68. package/dist/extraction/languages/solidity.js +293 -0
  69. package/dist/extraction/languages/solidity.js.map +1 -0
  70. package/dist/extraction/languages/terraform.d.ts +3 -0
  71. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  72. package/dist/extraction/languages/terraform.js +641 -0
  73. package/dist/extraction/languages/terraform.js.map +1 -0
  74. package/dist/extraction/languages/vbnet.d.ts +11 -0
  75. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  76. package/dist/extraction/languages/vbnet.js +141 -0
  77. package/dist/extraction/languages/vbnet.js.map +1 -0
  78. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  79. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  80. package/dist/extraction/mybatis-extractor.js +140 -38
  81. package/dist/extraction/mybatis-extractor.js.map +1 -1
  82. package/dist/extraction/parse-pool.d.ts +27 -1
  83. package/dist/extraction/parse-pool.d.ts.map +1 -1
  84. package/dist/extraction/parse-pool.js +75 -7
  85. package/dist/extraction/parse-pool.js.map +1 -1
  86. package/dist/extraction/parse-worker.js +9 -2
  87. package/dist/extraction/parse-worker.js.map +1 -1
  88. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  89. package/dist/extraction/tree-sitter-helpers.js +1 -0
  90. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  91. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  92. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  93. package/dist/extraction/tree-sitter.d.ts +38 -0
  94. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  95. package/dist/extraction/tree-sitter.js +754 -10
  96. package/dist/extraction/tree-sitter.js.map +1 -1
  97. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  98. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  99. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  100. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  101. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  102. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  103. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  104. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  105. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  106. package/dist/graph/traversal.d.ts.map +1 -1
  107. package/dist/graph/traversal.js +1 -1
  108. package/dist/graph/traversal.js.map +1 -1
  109. package/dist/index.d.ts +60 -2
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +348 -5
  112. package/dist/index.js.map +1 -1
  113. package/dist/installer/index.d.ts +42 -0
  114. package/dist/installer/index.d.ts.map +1 -1
  115. package/dist/installer/index.js +97 -2
  116. package/dist/installer/index.js.map +1 -1
  117. package/dist/mcp/daemon.d.ts +25 -3
  118. package/dist/mcp/daemon.d.ts.map +1 -1
  119. package/dist/mcp/daemon.js +62 -8
  120. package/dist/mcp/daemon.js.map +1 -1
  121. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  122. package/dist/mcp/dynamic-boundaries.js +2 -1
  123. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  124. package/dist/mcp/early-ppid.d.ts +26 -0
  125. package/dist/mcp/early-ppid.d.ts.map +1 -0
  126. package/dist/mcp/early-ppid.js +29 -0
  127. package/dist/mcp/early-ppid.js.map +1 -0
  128. package/dist/mcp/engine.d.ts +4 -5
  129. package/dist/mcp/engine.d.ts.map +1 -1
  130. package/dist/mcp/engine.js +27 -1
  131. package/dist/mcp/engine.js.map +1 -1
  132. package/dist/mcp/index.d.ts.map +1 -1
  133. package/dist/mcp/index.js +41 -6
  134. package/dist/mcp/index.js.map +1 -1
  135. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  136. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  137. package/dist/mcp/liveness-watchdog.js +73 -13
  138. package/dist/mcp/liveness-watchdog.js.map +1 -1
  139. package/dist/mcp/memory-budget.d.ts +30 -0
  140. package/dist/mcp/memory-budget.d.ts.map +1 -0
  141. package/dist/mcp/memory-budget.js +123 -0
  142. package/dist/mcp/memory-budget.js.map +1 -0
  143. package/dist/mcp/proxy.d.ts.map +1 -1
  144. package/dist/mcp/proxy.js +88 -4
  145. package/dist/mcp/proxy.js.map +1 -1
  146. package/dist/mcp/query-cache.d.ts +35 -4
  147. package/dist/mcp/query-cache.d.ts.map +1 -1
  148. package/dist/mcp/query-cache.js +89 -19
  149. package/dist/mcp/query-cache.js.map +1 -1
  150. package/dist/mcp/query-pool.d.ts +46 -12
  151. package/dist/mcp/query-pool.d.ts.map +1 -1
  152. package/dist/mcp/query-pool.js +100 -23
  153. package/dist/mcp/query-pool.js.map +1 -1
  154. package/dist/mcp/server-instructions.d.ts +4 -27
  155. package/dist/mcp/server-instructions.d.ts.map +1 -1
  156. package/dist/mcp/server-instructions.js +39 -84
  157. package/dist/mcp/server-instructions.js.map +1 -1
  158. package/dist/mcp/session.d.ts +14 -0
  159. package/dist/mcp/session.d.ts.map +1 -1
  160. package/dist/mcp/session.js +28 -1
  161. package/dist/mcp/session.js.map +1 -1
  162. package/dist/mcp/startup-handshake.d.ts +44 -0
  163. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  164. package/dist/mcp/startup-handshake.js +73 -0
  165. package/dist/mcp/startup-handshake.js.map +1 -0
  166. package/dist/mcp/tools.d.ts +104 -4
  167. package/dist/mcp/tools.d.ts.map +1 -1
  168. package/dist/mcp/tools.js +2390 -191
  169. package/dist/mcp/tools.js.map +1 -1
  170. package/dist/mcp/transport.d.ts.map +1 -1
  171. package/dist/mcp/transport.js +18 -0
  172. package/dist/mcp/transport.js.map +1 -1
  173. package/dist/project-config.d.ts +38 -0
  174. package/dist/project-config.d.ts.map +1 -1
  175. package/dist/project-config.js +101 -2
  176. package/dist/project-config.js.map +1 -1
  177. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  178. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  179. package/dist/resolution/c-fnptr-synthesizer.js +178 -146
  180. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  181. package/dist/resolution/callback-synthesizer.d.ts +9 -1
  182. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  183. package/dist/resolution/callback-synthesizer.js +1164 -176
  184. package/dist/resolution/callback-synthesizer.js.map +1 -1
  185. package/dist/resolution/frameworks/cics.d.ts +20 -0
  186. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  187. package/dist/resolution/frameworks/cics.js +90 -0
  188. package/dist/resolution/frameworks/cics.js.map +1 -0
  189. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  190. package/dist/resolution/frameworks/index.js +3 -0
  191. package/dist/resolution/frameworks/index.js.map +1 -1
  192. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  193. package/dist/resolution/frameworks/java.js +14 -6
  194. package/dist/resolution/frameworks/java.js.map +1 -1
  195. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  196. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  197. package/dist/resolution/frameworks/terraform.js +277 -0
  198. package/dist/resolution/frameworks/terraform.js.map +1 -0
  199. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  200. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  201. package/dist/resolution/goframe-synthesizer.js +8 -3
  202. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  203. package/dist/resolution/import-resolver.d.ts +7 -0
  204. package/dist/resolution/import-resolver.d.ts.map +1 -1
  205. package/dist/resolution/import-resolver.js +129 -4
  206. package/dist/resolution/import-resolver.js.map +1 -1
  207. package/dist/resolution/index.d.ts +60 -7
  208. package/dist/resolution/index.d.ts.map +1 -1
  209. package/dist/resolution/index.js +430 -80
  210. package/dist/resolution/index.js.map +1 -1
  211. package/dist/resolution/name-matcher.d.ts +0 -3
  212. package/dist/resolution/name-matcher.d.ts.map +1 -1
  213. package/dist/resolution/name-matcher.js +354 -33
  214. package/dist/resolution/name-matcher.js.map +1 -1
  215. package/dist/resolution/strip-comments.d.ts +1 -1
  216. package/dist/resolution/strip-comments.d.ts.map +1 -1
  217. package/dist/resolution/strip-comments.js +49 -0
  218. package/dist/resolution/strip-comments.js.map +1 -1
  219. package/dist/resolution/types.d.ts +29 -0
  220. package/dist/resolution/types.d.ts.map +1 -1
  221. package/dist/resolution/workspace-packages.d.ts +10 -0
  222. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  223. package/dist/resolution/workspace-packages.js +142 -4
  224. package/dist/resolution/workspace-packages.js.map +1 -1
  225. package/dist/search/identifier-segments.d.ts +60 -0
  226. package/dist/search/identifier-segments.d.ts.map +1 -0
  227. package/dist/search/identifier-segments.js +176 -0
  228. package/dist/search/identifier-segments.js.map +1 -0
  229. package/dist/search/query-utils.d.ts +201 -0
  230. package/dist/search/query-utils.d.ts.map +1 -1
  231. package/dist/search/query-utils.js +919 -2
  232. package/dist/search/query-utils.js.map +1 -1
  233. package/dist/sync/git-hooks.d.ts.map +1 -1
  234. package/dist/sync/git-hooks.js +2 -0
  235. package/dist/sync/git-hooks.js.map +1 -1
  236. package/dist/sync/watcher.d.ts +10 -5
  237. package/dist/sync/watcher.d.ts.map +1 -1
  238. package/dist/sync/watcher.js +51 -14
  239. package/dist/sync/watcher.js.map +1 -1
  240. package/dist/sync/worktree.d.ts.map +1 -1
  241. package/dist/sync/worktree.js +5 -0
  242. package/dist/sync/worktree.js.map +1 -1
  243. package/dist/types.d.ts +19 -1
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/types.js +10 -0
  246. package/dist/types.js.map +1 -1
  247. package/dist/upgrade/index.d.ts +32 -0
  248. package/dist/upgrade/index.d.ts.map +1 -1
  249. package/dist/upgrade/index.js +162 -14
  250. package/dist/upgrade/index.js.map +1 -1
  251. package/dist/upgrade/remove-binary.d.ts +87 -0
  252. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  253. package/dist/upgrade/remove-binary.js +289 -0
  254. package/dist/upgrade/remove-binary.js.map +1 -0
  255. package/dist/upgrade/update-check.d.ts +92 -0
  256. package/dist/upgrade/update-check.d.ts.map +1 -0
  257. package/dist/upgrade/update-check.js +258 -0
  258. package/dist/upgrade/update-check.js.map +1 -0
  259. package/package.json +2 -2
  260. package/scripts/agent-eval/run-all.sh +6 -0
  261. package/scripts/build-bundle.sh +5 -0
  262. package/scripts/npm-shim.js +8 -1
  263. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  264. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  265. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  266. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  267. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  268. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  269. package/scripts/exp_boundary_eval/data/agents.json +0 -109
  270. package/scripts/exp_boundary_eval/data/experiments.json +0 -140
  271. package/scripts/qa_eval/README.md +0 -407
  272. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  273. package/scripts/qa_eval/agent_runner.py +0 -526
  274. package/scripts/qa_eval/data/.gitignore +0 -4
  275. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  276. package/scripts/qa_eval/eval_metrics.py +0 -274
  277. package/scripts/qa_eval/external_agent.py +0 -976
  278. package/scripts/qa_eval/llm_config.py +0 -92
  279. package/scripts/qa_eval/memory_monitor.py +0 -132
  280. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  281. package/scripts/qa_eval/requirements.txt +0 -2
  282. package/scripts/qa_eval/run_pipeline.py +0 -804
  283. package/scripts/qa_eval/stats_efficiency.py +0 -279
  284. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -1,526 +0,0 @@
1
- """Multi-turn QA agent: with homegraph tools vs without (grep/read only). DashScope Qwen."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import logging
7
- import os
8
- import re
9
- import shutil
10
- import subprocess
11
- import time
12
- from datetime import datetime
13
- from pathlib import Path
14
- from typing import Any
15
-
16
- try:
17
- from openai import OpenAI
18
- except ImportError:
19
- OpenAI = None # type: ignore[misc, assignment]
20
-
21
- from memory_monitor import sample_memory
22
-
23
- logger = logging.getLogger(__name__)
24
-
25
- TIME_FMT = "%Y-%m-%d %H:%M:%S.%f"
26
-
27
-
28
- def _arm_short(arm: str) -> str:
29
- return "WITH" if arm == "with" else "WITHOUT"
30
-
31
-
32
- def print_agent_progress(
33
- arm: str,
34
- index: int,
35
- total: int,
36
- item_id: str,
37
- message: str,
38
- *,
39
- indent: int = 0,
40
- ) -> None:
41
- prefix = " " * indent
42
- print(f"{prefix}[{_arm_short(arm)} {index}/{total}] {item_id} — {message}", flush=True)
43
-
44
-
45
- def print_agent_turn(task_id: int, turn: int, max_turns: int, message: str = "请求 LLM…") -> None:
46
- print(f" · 第 {turn}/{max_turns} 轮 {message}", flush=True)
47
-
48
-
49
- def _log_line(log_file: Path | None, msg: str) -> None:
50
- if log_file is None:
51
- return
52
- log_file.parent.mkdir(parents=True, exist_ok=True)
53
- with log_file.open("a", encoding="utf-8") as f:
54
- f.write(f"{datetime.now().strftime(TIME_FMT)} {msg}\n")
55
-
56
-
57
- def _log_memory(log_file: Path | None, mem: dict[str, float | None]) -> None:
58
- if mem.get("peak_rss_mb") is not None:
59
- _log_line(log_file, f"peakRssMb = {mem['peak_rss_mb']}")
60
- if mem.get("avg_rss_mb") is not None:
61
- _log_line(log_file, f"avgRssMb = {mem['avg_rss_mb']}")
62
-
63
-
64
- def find_homegraph_bin(explicit: str | None = None) -> str:
65
- if explicit:
66
- p = Path(explicit)
67
- if p.is_file() and os.access(p, os.X_OK):
68
- return str(p.resolve())
69
- raise FileNotFoundError(f"homegraph binary not found: {explicit}")
70
-
71
- # Prefer local dev build when testing unreleased changes
72
- repo_root = Path(__file__).resolve().parents[2]
73
- for local in (
74
- repo_root / "dist" / "bin" / "homegraph.js",
75
- repo_root / "dist" / "bin" / "homegraph.js",
76
- ):
77
- if local.is_file():
78
- return f"node {local.resolve()}"
79
-
80
- for name in ("homegraph", "homegraph"):
81
- found = shutil.which(name)
82
- if found:
83
- return found
84
- raise FileNotFoundError(
85
- "homegraph/homegraph not on PATH.\n"
86
- " 在 homegraph 仓库里: npm run build\n"
87
- " 或: python scripts/qa_eval/run_pipeline.py ab --homegraph-bin 'node /path/to/dist/bin/homegraph.js'"
88
- )
89
-
90
-
91
- def _hg_cmd(hg_bin: str, args: list[str]) -> list[str]:
92
- if hg_bin.startswith("node "):
93
- return hg_bin.split(" ", 1) + args
94
- return [hg_bin, *args]
95
-
96
-
97
- def require_index(repo: Path) -> None:
98
- for name in (".homegraph", ".homegraph"):
99
- d = repo / name
100
- if (d / "homegraph.db").exists() or (d / "homegraph.db").exists():
101
- return
102
- raise RuntimeError(f"仓库未索引: {repo}\n请先运行: homegraph sync {repo}")
103
-
104
-
105
- def tool_block(name: str, body: str) -> str:
106
- return f"---\n{name}\n{body.strip()}\n---"
107
-
108
-
109
- def homegraph_query(repo: Path, query: str, *, hg_bin: str, limit: int = 8) -> str:
110
- proc = subprocess.run(
111
- _hg_cmd(hg_bin, ["query", query, "-p", str(repo.resolve()), "-l", str(limit), "-j"]),
112
- capture_output=True,
113
- text=True,
114
- timeout=120,
115
- )
116
- if proc.returncode != 0:
117
- return f"(homegraph query failed: {(proc.stderr or proc.stdout)[-400:]})"
118
- try:
119
- hits = json.loads(proc.stdout)
120
- except json.JSONDecodeError:
121
- return proc.stdout[:4000]
122
- lines: list[str] = []
123
- for i, hit in enumerate(hits[:limit], 1):
124
- node = hit.get("node") or {}
125
- lines.append(
126
- f"{i}. [{node.get('kind')}] {node.get('qualifiedName', node.get('name'))}\n"
127
- f" file: {node.get('filePath')}:{node.get('startLine')}-{node.get('endLine')}\n"
128
- f" sig: {node.get('signature', '')}"
129
- )
130
- return "\n".join(lines) if lines else "(no hits)"
131
-
132
-
133
- def read_file(repo: Path, rel_path: str, start: int = 1, end: int | None = None, context: int = 0) -> str:
134
- path = (repo / rel_path).resolve()
135
- if not path.is_file() or not str(path).startswith(str(repo.resolve())):
136
- return f"(file not found: {rel_path})"
137
- lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
138
- if end is None:
139
- end = min(len(lines), start + 80)
140
- lo = max(1, start - context) - 1
141
- hi = min(len(lines), end + context)
142
- body = "\n".join(f"{i + 1:4}| {lines[i]}" for i in range(lo, hi))
143
- return f"// {rel_path}:{start}-{end}\n{body}"
144
-
145
-
146
- def find_rg_binary() -> str | None:
147
- found = shutil.which("rg")
148
- if found:
149
- return found
150
- candidates = [
151
- Path.home() / ".cursor-server/bin/*/node_modules/@vscode/ripgrep/bin/rg",
152
- ]
153
- import glob
154
-
155
- for pattern in candidates:
156
- for path in glob.glob(str(pattern)):
157
- if os.path.isfile(path) and os.access(path, os.X_OK):
158
- return path
159
- return None
160
-
161
-
162
- def search_text(repo: Path, pattern: str, max_hits: int = 20) -> str:
163
- rg = find_rg_binary()
164
- if not rg:
165
- return "(rg not found on PATH; install: sudo apt install ripgrep)"
166
- proc = subprocess.run(
167
- [rg, "-n", "--no-heading", "-m", str(max_hits), pattern, str(repo)],
168
- capture_output=True,
169
- text=True,
170
- timeout=60,
171
- )
172
- out = (proc.stdout or proc.stderr or "").strip()
173
- return out[:6000] if out else "(no matches)"
174
-
175
-
176
- def find_files(repo: Path, pattern: str, max_hits: int = 30) -> str:
177
- """Find files by glob. Invalid patterns return a hint instead of crashing the agent."""
178
- pattern = pattern.strip().lstrip("/")
179
- if not pattern:
180
- return "(empty pattern)"
181
-
182
- hits: list[str] = []
183
- try:
184
- # pathlib rglob: ** must be a whole path segment (e.g. **/*.ets, not */**/x)
185
- if "**" in pattern and not pattern.startswith("**"):
186
- # Fallback: treat as substring search via rg --files | rg
187
- rg = shutil.which("rg")
188
- if rg:
189
- proc = subprocess.run(
190
- [rg, "--files", str(repo)],
191
- capture_output=True,
192
- text=True,
193
- timeout=60,
194
- )
195
- needle = pattern.replace("**", "").replace("*", "")
196
- for line in (proc.stdout or "").splitlines():
197
- if needle and needle.lower() in line.lower():
198
- hits.append(line)
199
- if len(hits) >= max_hits:
200
- break
201
- return "\n".join(hits) if hits else "(no files; try SearchText instead)"
202
- return "(invalid glob: use pattern like **/*.ets or use SearchText)"
203
-
204
- for p in sorted(repo.rglob(pattern)):
205
- if p.is_file() and ".git" not in p.parts:
206
- hits.append(str(p.relative_to(repo)))
207
- if len(hits) >= max_hits:
208
- break
209
- except ValueError as e:
210
- return f"(invalid glob pattern '{pattern}': {e}. Try **/*.ets or SearchText)"
211
- except OSError as e:
212
- return f"(find failed: {e})"
213
-
214
- return "\n".join(hits) if hits else "(no files)"
215
-
216
-
217
- WITH_TOOLS = [
218
- {
219
- "type": "function",
220
- "function": {
221
- "name": "HomegraphQuery",
222
- "description": "Search symbols in the repo via homegraph index (preferred for finding functions/classes/files).",
223
- "parameters": {
224
- "type": "object",
225
- "properties": {
226
- "query": {"type": "string", "description": "Symbol name or search terms"},
227
- "limit": {"type": "integer", "description": "Max hits", "default": 8},
228
- },
229
- "required": ["query"],
230
- },
231
- },
232
- },
233
- {
234
- "type": "function",
235
- "function": {
236
- "name": "ReadFile",
237
- "description": "Read source file lines from the repo.",
238
- "parameters": {
239
- "type": "object",
240
- "properties": {
241
- "path": {"type": "string"},
242
- "start_line": {"type": "integer", "default": 1},
243
- "end_line": {"type": "integer"},
244
- },
245
- "required": ["path"],
246
- },
247
- },
248
- },
249
- ]
250
-
251
- WITHOUT_TOOLS = [
252
- {
253
- "type": "function",
254
- "function": {
255
- "name": "SearchText",
256
- "description": "Ripgrep search in the repo (like grep).",
257
- "parameters": {
258
- "type": "object",
259
- "properties": {"pattern": {"type": "string"}},
260
- "required": ["pattern"],
261
- },
262
- },
263
- },
264
- {
265
- "type": "function",
266
- "function": {
267
- "name": "ReadFile",
268
- "description": "Read source file lines from the repo.",
269
- "parameters": {
270
- "type": "object",
271
- "properties": {
272
- "path": {"type": "string"},
273
- "start_line": {"type": "integer", "default": 1},
274
- "end_line": {"type": "integer"},
275
- },
276
- "required": ["path"],
277
- },
278
- },
279
- },
280
- {
281
- "type": "function",
282
- "function": {
283
- "name": "FindFiles",
284
- "description": "Glob find files under repo. Use patterns like *.ets or **/BenchmarkHub*.ets (not */**/x).",
285
- "parameters": {
286
- "type": "object",
287
- "properties": {"pattern": {"type": "string"}},
288
- "required": ["pattern"],
289
- },
290
- },
291
- },
292
- ]
293
-
294
-
295
- def execute_tool(
296
- repo: Path,
297
- name: str,
298
- args: dict[str, Any],
299
- *,
300
- arm: str,
301
- hg_bin: str,
302
- ) -> str:
303
- if name == "HomegraphQuery":
304
- if arm != "with":
305
- return "(homegraph disabled in without arm)"
306
- return homegraph_query(repo, args["query"], hg_bin=hg_bin, limit=int(args.get("limit") or 8))
307
- if name == "ReadFile":
308
- return read_file(
309
- repo,
310
- args["path"],
311
- start=int(args.get("start_line") or 1),
312
- end=int(args["end_line"]) if args.get("end_line") else None,
313
- )
314
- if name == "SearchText":
315
- return search_text(repo, args["pattern"])
316
- if name == "FindFiles":
317
- return find_files(repo, args["pattern"])
318
- return f"(unknown tool: {name})"
319
-
320
-
321
- def run_agent_on_query(
322
- repo: Path,
323
- query: str,
324
- *,
325
- arm: str,
326
- api_key: str,
327
- base_url: str,
328
- model: str,
329
- hg_bin: str,
330
- log_file: Path | None = None,
331
- task_id: int = 1,
332
- max_turns: int = 8,
333
- timeout_sec: int = 600,
334
- extra_body: dict | None = None,
335
- ) -> dict[str, Any]:
336
- if OpenAI is None:
337
- raise RuntimeError("pip install openai")
338
-
339
- client = OpenAI(api_key=api_key, base_url=base_url, timeout=timeout_sec)
340
- tools = WITH_TOOLS if arm == "with" else WITHOUT_TOOLS
341
- backend = "agent-with-homegraph" if arm == "with" else "agent-grep-read"
342
-
343
- if arm == "with":
344
- system = (
345
- "你是鸿蒙 ArkTS 代码分析 Agent。可用 HomegraphQuery 检索符号、ReadFile 读源码。"
346
- "先检索再读文件,基于仓库事实作答。回答简洁准确,中文。"
347
- )
348
- else:
349
- system = (
350
- "你是鸿蒙 ArkTS 代码分析 Agent。可用 SearchText(rg)、FindFiles、ReadFile 探索仓库。"
351
- "没有 homegraph,请主动搜索和读文件后再答。回答简洁准确,中文。"
352
- )
353
-
354
- messages: list[dict[str, Any]] = [
355
- {"role": "system", "content": system},
356
- {"role": "user", "content": query},
357
- ]
358
- tool_trace: list[str] = []
359
- total_tokens = 0
360
- t0 = time.time()
361
- _log_line(log_file, f"Evaluate {task_id}:")
362
-
363
- with sample_memory() as mem_sampler:
364
- for turn in range(1, max_turns + 1):
365
- _log_line(log_file, f"the {turn} turn")
366
- print_agent_turn(task_id, turn, max_turns)
367
- create_kwargs: dict[str, Any] = {
368
- "model": model,
369
- "messages": messages,
370
- "tools": tools,
371
- "temperature": 0.2,
372
- }
373
- if extra_body:
374
- create_kwargs["extra_body"] = extra_body
375
- resp = client.chat.completions.create(**create_kwargs)
376
- if turn == 1:
377
- _log_line(log_file, "first token")
378
- if resp.usage:
379
- total_tokens += int(resp.usage.total_tokens or 0)
380
- _log_line(log_file, f"totalTokenCount = {resp.usage.total_tokens}")
381
-
382
- msg = resp.choices[0].message
383
- if msg.tool_calls:
384
- names = ", ".join(tc.function.name for tc in msg.tool_calls)
385
- print_agent_turn(task_id, turn, max_turns, f"工具: {names}")
386
- messages.append(msg.model_dump())
387
- for tc in msg.tool_calls:
388
- fn = tc.function
389
- try:
390
- fn_args = json.loads(fn.arguments or "{}")
391
- except json.JSONDecodeError:
392
- fn_args = {}
393
- result = execute_tool(repo, fn.name, fn_args, arm=arm, hg_bin=hg_bin)
394
- arg_str = json.dumps(fn_args, ensure_ascii=False)
395
- tool_trace.append(tool_block(fn.name, f"args: {arg_str}\nresult:\n{result[:3000]}"))
396
- messages.append(
397
- {
398
- "role": "tool",
399
- "tool_call_id": tc.id,
400
- "content": result[:8000],
401
- }
402
- )
403
- continue
404
-
405
- answer = (msg.content or "").strip()
406
- duration_ms = int((time.time() - t0) * 1000)
407
- trace_text = "\n\n".join(tool_trace)
408
- output = f"{trace_text}\n\n{answer}" if trace_text else answer
409
- usage = resp.usage
410
- mem = mem_sampler.last_stats
411
- _log_memory(log_file, mem)
412
- meta: dict[str, Any] = {
413
- "output_answer": output,
414
- "agent_status": "success" if answer else "error",
415
- "agent_backend": backend,
416
- "agent_host": "builtin",
417
- "agent_model": model,
418
- "agent_turns": turn,
419
- "agent_duration_ms": duration_ms,
420
- "ab_arm": "with-homegraph" if arm == "with" else "without-homegraph",
421
- "agent_memory_mb": mem,
422
- }
423
- if usage:
424
- meta["agent_usage"] = {
425
- "prompt_tokens": usage.prompt_tokens,
426
- "completion_tokens": usage.completion_tokens,
427
- "total_tokens": usage.total_tokens,
428
- }
429
- if not answer:
430
- meta["agent_error"] = "empty final answer"
431
- return meta
432
-
433
- mem = mem_sampler.last_stats
434
- _log_memory(log_file, mem)
435
- return {
436
- "output_answer": "\n\n".join(tool_trace),
437
- "agent_status": "error",
438
- "agent_error": f"max turns ({max_turns}) exceeded",
439
- "agent_backend": backend,
440
- "agent_host": "builtin",
441
- "agent_model": model,
442
- "ab_arm": "with-homegraph" if arm == "with" else "without-homegraph",
443
- "agent_memory_mb": mem,
444
- }
445
-
446
-
447
- def run_agent_dataset(
448
- repo: Path,
449
- dataset: list[dict[str, Any]],
450
- *,
451
- arm: str,
452
- output: Path,
453
- log_file: Path | None,
454
- api_key: str,
455
- base_url: str,
456
- model: str,
457
- hg_bin: str,
458
- max_turns: int = 8,
459
- extra_body: dict | None = None,
460
- ) -> list[dict[str, Any]]:
461
- if arm == "with":
462
- require_index(repo)
463
- hg = find_homegraph_bin(hg_bin) if arm == "with" else ""
464
- output.parent.mkdir(parents=True, exist_ok=True)
465
- if log_file:
466
- log_file.write_text("", encoding="utf-8")
467
-
468
- results: list[dict[str, Any]] = []
469
- auth_failed = False
470
- total = len(dataset)
471
- print(f" → {_arm_short(arm)} 臂:共 {total} 题", flush=True)
472
- with output.open("w", encoding="utf-8") as f:
473
- for i, item in enumerate(dataset, 1):
474
- q = str(item["query"])
475
- item_id = str(item.get("id") or i)
476
- print_agent_progress(arm, i, total, item_id, "开始…")
477
- logger.info("[%s] %s/%s %s", arm, i, total, item_id)
478
- try:
479
- agent_meta = run_agent_on_query(
480
- repo,
481
- q,
482
- arm=arm,
483
- api_key=api_key,
484
- base_url=base_url,
485
- model=model,
486
- hg_bin=hg,
487
- log_file=log_file,
488
- task_id=i,
489
- max_turns=max_turns,
490
- extra_body=extra_body,
491
- )
492
- except Exception as e:
493
- err = str(e)
494
- logger.error("Agent failed %s: %s", item.get("id"), e)
495
- if "401" in err or "invalid_api_key" in err or "Incorrect API key" in err:
496
- auth_failed = True
497
- agent_meta = {
498
- "output_answer": "",
499
- "agent_status": "error",
500
- "agent_error": err,
501
- "agent_backend": "agent-with-homegraph" if arm == "with" else "agent-grep-read",
502
- }
503
- row = {**item, **agent_meta}
504
- results.append(row)
505
- f.write(json.dumps(row, ensure_ascii=False) + "\n")
506
- f.flush()
507
- if agent_meta.get("agent_status") == "success":
508
- turns = agent_meta.get("agent_turns", "?")
509
- dur_ms = agent_meta.get("agent_duration_ms")
510
- dur_s = f"{dur_ms / 1000:.1f}s" if isinstance(dur_ms, (int, float)) else "?"
511
- print_agent_progress(arm, i, total, item_id, f"完成 ({turns} 轮, {dur_s})")
512
- else:
513
- err = str(agent_meta.get("agent_error") or agent_meta.get("agent_status") or "error")
514
- print_agent_progress(arm, i, total, item_id, f"失败: {err[:100]}")
515
- if auth_failed:
516
- raise RuntimeError(
517
- "LLM API 鉴权失败 (401)。智谱 Key 请用:\n"
518
- " export ZHIPU_API_KEY='your-id.your-secret'\n"
519
- " python scripts/qa_eval/run_pipeline.py ab --provider zhipu\n"
520
- "DashScope Key 请用:\n"
521
- " export DASHSCOPE_API_KEY='sk-...'\n"
522
- " python scripts/qa_eval/run_pipeline.py ab --provider dashscope"
523
- ) from None
524
- ok = sum(1 for r in results if r.get("agent_status") == "success")
525
- print(f" → {_arm_short(arm)} 臂结束:{ok}/{total} 成功", flush=True)
526
- return results
@@ -1,4 +0,0 @@
1
- # test-set.jsonl tracked; reports + other artifacts stay local.
2
- *
3
- !.gitignore
4
- !test-set.jsonl
@@ -1,2 +0,0 @@
1
- {"id": "R01", "level0": "camera", "level1": "ColorUtil", "category_l1": "检索", "category_l2": "函数", "difficulty": "简单", "language": "ArkTS", "query": "getColorString 函数在哪个文件里定义?", "reference_answer": "getColorString 定义在 camera/common/src/main/ets/utils/ColorUtil.ets,是一个 export function。"}
2
- {"id": "R02", "level0": "platform", "level1": "BenchmarkHub", "category_l1": "检索", "category_l2": "函数", "difficulty": "简单", "language": "ArkTS", "query": "launchPhotosFromDesktop 函数定义在哪个文件?", "reference_answer": "launchPhotosFromDesktop 定义在 platform/src/main/ets/BenchmarkHub.ets,与 BenchmarkHub 类同文件导出。"}