homegraph 1.1.2 → 1.2.0

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 (361) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +268 -243
  3. package/dist/arkts/ohos-api-index.d.ts +15 -0
  4. package/dist/arkts/ohos-api-index.d.ts.map +1 -0
  5. package/dist/arkts/ohos-api-index.js +190 -0
  6. package/dist/arkts/ohos-api-index.js.map +1 -0
  7. package/dist/arkts/ohos-sdk-input.d.ts +36 -0
  8. package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
  9. package/dist/arkts/ohos-sdk-input.js +214 -0
  10. package/dist/arkts/ohos-sdk-input.js.map +1 -0
  11. package/dist/bin/command-supervision.d.ts +12 -0
  12. package/dist/bin/command-supervision.d.ts.map +1 -0
  13. package/dist/bin/command-supervision.js +86 -0
  14. package/dist/bin/command-supervision.js.map +1 -0
  15. package/dist/bin/homegraph.d.ts +1 -1
  16. package/dist/bin/homegraph.js +971 -71
  17. package/dist/bin/homegraph.js.map +1 -1
  18. package/dist/db/index.d.ts +20 -0
  19. package/dist/db/index.d.ts.map +1 -1
  20. package/dist/db/index.js +39 -0
  21. package/dist/db/index.js.map +1 -1
  22. package/dist/db/migrations.d.ts +1 -1
  23. package/dist/db/migrations.d.ts.map +1 -1
  24. package/dist/db/migrations.js +58 -19
  25. package/dist/db/migrations.js.map +1 -1
  26. package/dist/db/queries.d.ts +14 -0
  27. package/dist/db/queries.d.ts.map +1 -1
  28. package/dist/db/queries.js +314 -143
  29. package/dist/db/queries.js.map +1 -1
  30. package/dist/db/schema.sql +172 -152
  31. package/dist/directory.d.ts +32 -0
  32. package/dist/directory.d.ts.map +1 -1
  33. package/dist/directory.js +88 -5
  34. package/dist/directory.js.map +1 -1
  35. package/dist/extraction/arkts-batch-worker.d.ts +2 -0
  36. package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
  37. package/dist/extraction/arkts-batch-worker.js +28 -0
  38. package/dist/extraction/arkts-batch-worker.js.map +1 -0
  39. package/dist/extraction/context.d.ts +11 -0
  40. package/dist/extraction/context.d.ts.map +1 -1
  41. package/dist/extraction/context.js +20 -0
  42. package/dist/extraction/context.js.map +1 -1
  43. package/dist/extraction/index.d.ts +16 -2
  44. package/dist/extraction/index.d.ts.map +1 -1
  45. package/dist/extraction/index.js +619 -380
  46. package/dist/extraction/index.js.map +1 -1
  47. package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
  48. package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
  49. package/dist/extraction/languages/arkts-state-decorators.js +26 -0
  50. package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
  51. package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
  52. package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
  53. package/dist/extraction/languages/arkts-viewtree.js +21 -6
  54. package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
  55. package/dist/extraction/languages/arkts.d.ts +98 -2
  56. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  57. package/dist/extraction/languages/arkts.js +1290 -61
  58. package/dist/extraction/languages/arkts.js.map +1 -1
  59. package/dist/extraction/languages/c-cpp.d.ts +56 -0
  60. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  61. package/dist/extraction/languages/c-cpp.js +198 -1
  62. package/dist/extraction/languages/c-cpp.js.map +1 -1
  63. package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
  64. package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
  65. package/dist/extraction/languages/ohos-api-consumer.js +283 -0
  66. package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
  67. package/dist/extraction/parse-pool.d.ts +126 -0
  68. package/dist/extraction/parse-pool.d.ts.map +1 -0
  69. package/dist/extraction/parse-pool.js +319 -0
  70. package/dist/extraction/parse-pool.js.map +1 -0
  71. package/dist/extraction/tree-sitter-types.d.ts +17 -0
  72. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  73. package/dist/extraction/tree-sitter.d.ts +21 -0
  74. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  75. package/dist/extraction/tree-sitter.js +198 -27
  76. package/dist/extraction/tree-sitter.js.map +1 -1
  77. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  78. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  79. package/dist/graph/traversal.d.ts.map +1 -1
  80. package/dist/graph/traversal.js +76 -17
  81. package/dist/graph/traversal.js.map +1 -1
  82. package/dist/index.d.ts +25 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +78 -4
  85. package/dist/index.js.map +1 -1
  86. package/dist/installer/instructions-template.js +9 -9
  87. package/dist/installer/targets/shared.d.ts +5 -6
  88. package/dist/installer/targets/shared.d.ts.map +1 -1
  89. package/dist/installer/targets/shared.js +5 -6
  90. package/dist/installer/targets/shared.js.map +1 -1
  91. package/dist/mcp/daemon-paths.d.ts +30 -3
  92. package/dist/mcp/daemon-paths.d.ts.map +1 -1
  93. package/dist/mcp/daemon-paths.js +50 -10
  94. package/dist/mcp/daemon-paths.js.map +1 -1
  95. package/dist/mcp/daemon-registry.d.ts.map +1 -1
  96. package/dist/mcp/daemon-registry.js +7 -3
  97. package/dist/mcp/daemon-registry.js.map +1 -1
  98. package/dist/mcp/daemon.d.ts +48 -0
  99. package/dist/mcp/daemon.d.ts.map +1 -1
  100. package/dist/mcp/daemon.js +196 -32
  101. package/dist/mcp/daemon.js.map +1 -1
  102. package/dist/mcp/engine.d.ts +17 -0
  103. package/dist/mcp/engine.d.ts.map +1 -1
  104. package/dist/mcp/engine.js +73 -1
  105. package/dist/mcp/engine.js.map +1 -1
  106. package/dist/mcp/index.d.ts.map +1 -1
  107. package/dist/mcp/index.js +25 -43
  108. package/dist/mcp/index.js.map +1 -1
  109. package/dist/mcp/liveness-watchdog.js +16 -16
  110. package/dist/mcp/ppid-watchdog.d.ts +18 -0
  111. package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
  112. package/dist/mcp/ppid-watchdog.js +37 -0
  113. package/dist/mcp/ppid-watchdog.js.map +1 -1
  114. package/dist/mcp/query-cache.d.ts +25 -0
  115. package/dist/mcp/query-cache.d.ts.map +1 -0
  116. package/dist/mcp/query-cache.js +191 -0
  117. package/dist/mcp/query-cache.js.map +1 -0
  118. package/dist/mcp/query-pool.d.ts +94 -0
  119. package/dist/mcp/query-pool.d.ts.map +1 -0
  120. package/dist/mcp/query-pool.js +297 -0
  121. package/dist/mcp/query-pool.js.map +1 -0
  122. package/dist/mcp/query-worker.d.ts +24 -0
  123. package/dist/mcp/query-worker.d.ts.map +1 -0
  124. package/dist/mcp/query-worker.js +87 -0
  125. package/dist/mcp/query-worker.js.map +1 -0
  126. package/dist/mcp/server-instructions.d.ts +5 -7
  127. package/dist/mcp/server-instructions.d.ts.map +1 -1
  128. package/dist/mcp/server-instructions.js +72 -74
  129. package/dist/mcp/server-instructions.js.map +1 -1
  130. package/dist/mcp/tools.d.ts +94 -4
  131. package/dist/mcp/tools.d.ts.map +1 -1
  132. package/dist/mcp/tools.js +877 -71
  133. package/dist/mcp/tools.js.map +1 -1
  134. package/dist/project-config.d.ts +20 -0
  135. package/dist/project-config.d.ts.map +1 -1
  136. package/dist/project-config.js +42 -2
  137. package/dist/project-config.js.map +1 -1
  138. package/dist/reasoning/login.js +1 -1
  139. package/dist/reasoning/login.js.map +1 -1
  140. package/dist/reasoning/reasoner.js +32 -32
  141. package/dist/resolution/c-fnptr-synthesizer.d.ts +0 -28
  142. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  143. package/dist/resolution/c-fnptr-synthesizer.js +765 -79
  144. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  145. package/dist/resolution/callback-synthesizer.d.ts +1 -1
  146. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  147. package/dist/resolution/callback-synthesizer.js +72 -11
  148. package/dist/resolution/callback-synthesizer.js.map +1 -1
  149. package/dist/resolution/cooperative-yield.d.ts +32 -0
  150. package/dist/resolution/cooperative-yield.d.ts.map +1 -0
  151. package/dist/resolution/cooperative-yield.js +42 -0
  152. package/dist/resolution/cooperative-yield.js.map +1 -0
  153. package/dist/resolution/index.d.ts +11 -2
  154. package/dist/resolution/index.d.ts.map +1 -1
  155. package/dist/resolution/index.js +72 -4
  156. package/dist/resolution/index.js.map +1 -1
  157. package/dist/resolution/name-matcher.d.ts +22 -0
  158. package/dist/resolution/name-matcher.d.ts.map +1 -1
  159. package/dist/resolution/name-matcher.js +317 -20
  160. package/dist/resolution/name-matcher.js.map +1 -1
  161. package/dist/spec/config.d.ts +39 -0
  162. package/dist/spec/config.d.ts.map +1 -0
  163. package/dist/spec/config.js +304 -0
  164. package/dist/spec/config.js.map +1 -0
  165. package/dist/spec/db/commit-node.d.ts +23 -0
  166. package/dist/spec/db/commit-node.d.ts.map +1 -0
  167. package/dist/spec/db/commit-node.js +62 -0
  168. package/dist/spec/db/commit-node.js.map +1 -0
  169. package/dist/spec/db/fragment-node.d.ts +24 -0
  170. package/dist/spec/db/fragment-node.d.ts.map +1 -0
  171. package/dist/spec/db/fragment-node.js +128 -0
  172. package/dist/spec/db/fragment-node.js.map +1 -0
  173. package/dist/spec/db/fts.d.ts +74 -0
  174. package/dist/spec/db/fts.d.ts.map +1 -0
  175. package/dist/spec/db/fts.js +324 -0
  176. package/dist/spec/db/fts.js.map +1 -0
  177. package/dist/spec/db/index.d.ts +13 -0
  178. package/dist/spec/db/index.d.ts.map +1 -0
  179. package/dist/spec/db/index.js +50 -0
  180. package/dist/spec/db/index.js.map +1 -0
  181. package/dist/spec/db/relations.d.ts +55 -0
  182. package/dist/spec/db/relations.d.ts.map +1 -0
  183. package/dist/spec/db/relations.js +158 -0
  184. package/dist/spec/db/relations.js.map +1 -0
  185. package/dist/spec/db/schema.d.ts +33 -0
  186. package/dist/spec/db/schema.d.ts.map +1 -0
  187. package/dist/spec/db/schema.js +119 -0
  188. package/dist/spec/db/schema.js.map +1 -0
  189. package/dist/spec/db/schema.sql +117 -0
  190. package/dist/spec/db/spec-node.d.ts +41 -0
  191. package/dist/spec/db/spec-node.d.ts.map +1 -0
  192. package/dist/spec/db/spec-node.js +114 -0
  193. package/dist/spec/db/spec-node.js.map +1 -0
  194. package/dist/spec/evolve/impact-locator.d.ts +13 -0
  195. package/dist/spec/evolve/impact-locator.d.ts.map +1 -0
  196. package/dist/spec/evolve/impact-locator.js +25 -0
  197. package/dist/spec/evolve/impact-locator.js.map +1 -0
  198. package/dist/spec/evolve/llm-client.d.ts +50 -0
  199. package/dist/spec/evolve/llm-client.d.ts.map +1 -0
  200. package/dist/spec/evolve/llm-client.js +176 -0
  201. package/dist/spec/evolve/llm-client.js.map +1 -0
  202. package/dist/spec/evolve/logic-checker.d.ts +12 -0
  203. package/dist/spec/evolve/logic-checker.d.ts.map +1 -0
  204. package/dist/spec/evolve/logic-checker.js +24 -0
  205. package/dist/spec/evolve/logic-checker.js.map +1 -0
  206. package/dist/spec/evolve/pipeline.d.ts +42 -0
  207. package/dist/spec/evolve/pipeline.d.ts.map +1 -0
  208. package/dist/spec/evolve/pipeline.js +567 -0
  209. package/dist/spec/evolve/pipeline.js.map +1 -0
  210. package/dist/spec/evolve/spec-rewriter.d.ts +42 -0
  211. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -0
  212. package/dist/spec/evolve/spec-rewriter.js +230 -0
  213. package/dist/spec/evolve/spec-rewriter.js.map +1 -0
  214. package/dist/spec/graph/queries.d.ts +155 -0
  215. package/dist/spec/graph/queries.d.ts.map +1 -0
  216. package/dist/spec/graph/queries.js +440 -0
  217. package/dist/spec/graph/queries.js.map +1 -0
  218. package/dist/spec/llm/client.d.ts +29 -0
  219. package/dist/spec/llm/client.d.ts.map +1 -0
  220. package/dist/spec/llm/client.js +123 -0
  221. package/dist/spec/llm/client.js.map +1 -0
  222. package/dist/spec/llm/index.d.ts +3 -0
  223. package/dist/spec/llm/index.d.ts.map +1 -0
  224. package/dist/spec/llm/index.js +11 -0
  225. package/dist/spec/llm/index.js.map +1 -0
  226. package/dist/spec/llm/prompts.d.ts +13 -0
  227. package/dist/spec/llm/prompts.d.ts.map +1 -0
  228. package/dist/spec/llm/prompts.js +75 -0
  229. package/dist/spec/llm/prompts.js.map +1 -0
  230. package/dist/spec/mining/diff-parser.d.ts +33 -0
  231. package/dist/spec/mining/diff-parser.d.ts.map +1 -0
  232. package/dist/spec/mining/diff-parser.js +166 -0
  233. package/dist/spec/mining/diff-parser.js.map +1 -0
  234. package/dist/spec/mining/git-scanner.d.ts +103 -0
  235. package/dist/spec/mining/git-scanner.d.ts.map +1 -0
  236. package/dist/spec/mining/git-scanner.js +307 -0
  237. package/dist/spec/mining/git-scanner.js.map +1 -0
  238. package/dist/spec/mining/pipeline.d.ts +53 -0
  239. package/dist/spec/mining/pipeline.d.ts.map +1 -0
  240. package/dist/spec/mining/pipeline.js +178 -0
  241. package/dist/spec/mining/pipeline.js.map +1 -0
  242. package/dist/spec/mining/scope-resolver.d.ts +45 -0
  243. package/dist/spec/mining/scope-resolver.d.ts.map +1 -0
  244. package/dist/spec/mining/scope-resolver.js +103 -0
  245. package/dist/spec/mining/scope-resolver.js.map +1 -0
  246. package/dist/spec/mining/spec-extractor.d.ts +69 -0
  247. package/dist/spec/mining/spec-extractor.d.ts.map +1 -0
  248. package/dist/spec/mining/spec-extractor.js +369 -0
  249. package/dist/spec/mining/spec-extractor.js.map +1 -0
  250. package/dist/spec/types.d.ts +149 -0
  251. package/dist/spec/types.d.ts.map +1 -0
  252. package/dist/spec/types.js +15 -0
  253. package/dist/spec/types.js.map +1 -0
  254. package/dist/spec/utils.d.ts +167 -0
  255. package/dist/spec/utils.d.ts.map +1 -0
  256. package/dist/spec/utils.js +463 -0
  257. package/dist/spec/utils.js.map +1 -0
  258. package/dist/sync/worktree.d.ts +9 -0
  259. package/dist/sync/worktree.d.ts.map +1 -1
  260. package/dist/sync/worktree.js +40 -0
  261. package/dist/sync/worktree.js.map +1 -1
  262. package/dist/types.d.ts +6 -1
  263. package/dist/types.d.ts.map +1 -1
  264. package/dist/ui/shimmer-progress.d.ts +2 -0
  265. package/dist/ui/shimmer-progress.d.ts.map +1 -1
  266. package/dist/ui/shimmer-progress.js +19 -2
  267. package/dist/ui/shimmer-progress.js.map +1 -1
  268. package/dist/upgrade/index.js +1 -1
  269. package/dist/upgrade/index.js.map +1 -1
  270. package/package.json +58 -57
  271. package/scripts/add-lang/bench.sh +60 -60
  272. package/scripts/add-lang/check-grammar.mjs +75 -75
  273. package/scripts/add-lang/dump-ast.mjs +103 -103
  274. package/scripts/add-lang/verify-extraction.mjs +70 -70
  275. package/scripts/agent-eval/ab-adoption.sh +91 -91
  276. package/scripts/agent-eval/ab-hook.sh +86 -86
  277. package/scripts/agent-eval/ab-impl.sh +78 -78
  278. package/scripts/agent-eval/ab-new-vs-baseline.sh +102 -102
  279. package/scripts/agent-eval/ab-sufficiency.sh +78 -78
  280. package/scripts/agent-eval/arms-F.sh +21 -21
  281. package/scripts/agent-eval/arms-matrix.sh +37 -37
  282. package/scripts/agent-eval/audit.sh +68 -68
  283. package/scripts/agent-eval/bench-readme.sh +28 -28
  284. package/scripts/agent-eval/bench-why-repo.sh +22 -22
  285. package/scripts/agent-eval/block-read-hook.sh +19 -19
  286. package/scripts/agent-eval/hook-settings.json +15 -15
  287. package/scripts/agent-eval/itrun.sh +120 -120
  288. package/scripts/agent-eval/offload-eval-3arm.sh +72 -72
  289. package/scripts/agent-eval/offload-eval-cost.mjs +133 -133
  290. package/scripts/agent-eval/offload-eval-effort.mjs +108 -108
  291. package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -25
  292. package/scripts/agent-eval/offload-eval-frontload.sh +47 -47
  293. package/scripts/agent-eval/offload-eval-ground-truth.json +18 -18
  294. package/scripts/agent-eval/offload-eval-hook.mjs +84 -84
  295. package/scripts/agent-eval/offload-eval-judge.mjs +103 -103
  296. package/scripts/agent-eval/offload-eval-matrix.sh +20 -20
  297. package/scripts/agent-eval/offload-eval-metrics.mjs +94 -94
  298. package/scripts/agent-eval/offload-eval-refs1.sh +50 -50
  299. package/scripts/agent-eval/offload-eval-setup.sh +24 -24
  300. package/scripts/agent-eval/offload-eval-styles.sh +71 -71
  301. package/scripts/agent-eval/offload-eval-summarize.mjs +68 -68
  302. package/scripts/agent-eval/offload-eval.md +76 -76
  303. package/scripts/agent-eval/parse-arms.mjs +116 -116
  304. package/scripts/agent-eval/parse-bench-readme.mjs +84 -84
  305. package/scripts/agent-eval/parse-run.mjs +45 -45
  306. package/scripts/agent-eval/parse-session.mjs +93 -93
  307. package/scripts/agent-eval/probe-context.mjs +21 -21
  308. package/scripts/agent-eval/probe-explore.mjs +40 -40
  309. package/scripts/agent-eval/probe-node.mjs +20 -20
  310. package/scripts/agent-eval/probe-sweep.mjs +119 -119
  311. package/scripts/agent-eval/probe-trace.mjs +20 -20
  312. package/scripts/agent-eval/redirect-read-hook.sh +38 -38
  313. package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
  314. package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
  315. package/scripts/agent-eval/run-agent.sh +34 -34
  316. package/scripts/agent-eval/run-all.sh +69 -69
  317. package/scripts/agent-eval/run-arms.sh +56 -56
  318. package/scripts/agent-eval/seq-matrix.mjs +137 -137
  319. package/scripts/build-bundle.sh +118 -118
  320. package/scripts/exp_boundary_eval/README.md +247 -0
  321. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  322. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  323. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  324. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  325. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  326. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  327. package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
  328. package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
  329. package/scripts/exp_boundary_eval/_utils.py +1116 -0
  330. package/scripts/exp_boundary_eval/analyze.py +1313 -0
  331. package/scripts/exp_boundary_eval/data/agents.json +109 -0
  332. package/scripts/exp_boundary_eval/data/experiments.json +140 -0
  333. package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
  334. package/scripts/exp_boundary_eval/run_all.py +378 -0
  335. package/scripts/exp_boundary_eval/run_one.py +165 -0
  336. package/scripts/exp_boundary_eval/run_session.py +158 -0
  337. package/scripts/exp_boundary_eval/setup.py +120 -0
  338. package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
  339. package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
  340. package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
  341. package/scripts/extract-release-notes.mjs +130 -130
  342. package/scripts/local-install.sh +41 -41
  343. package/scripts/npm-sdk.js +75 -75
  344. package/scripts/npm-shim.js +268 -246
  345. package/scripts/ohos-sdk-publish.mjs +133 -0
  346. package/scripts/pack-npm.sh +119 -119
  347. package/scripts/prepare-release.mjs +270 -270
  348. package/scripts/qa_eval/README.md +407 -404
  349. package/scripts/qa_eval/_test_deveco_probe.py +41 -41
  350. package/scripts/qa_eval/agent_runner.py +526 -526
  351. package/scripts/qa_eval/data/.gitignore +4 -4
  352. package/scripts/qa_eval/data/test-set.jsonl +2 -22
  353. package/scripts/qa_eval/eval_metrics.py +274 -233
  354. package/scripts/qa_eval/external_agent.py +976 -671
  355. package/scripts/qa_eval/llm_config.py +92 -92
  356. package/scripts/qa_eval/memory_monitor.py +132 -132
  357. package/scripts/qa_eval/my_answer_accuracy.py +187 -187
  358. package/scripts/qa_eval/requirements.txt +2 -2
  359. package/scripts/qa_eval/run_pipeline.py +804 -711
  360. package/scripts/qa_eval/stats_efficiency.py +279 -279
  361. package/scripts/qa_eval/stats_scores.py +207 -207
@@ -0,0 +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()
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env python3
2
+ """Run a single one-shot experiment. Supports multiple agents via --agent."""
3
+
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import setup as setup_mod
11
+ from _utils import (OUTPUT_DIR, append_run_log, build_agent_cmd, current_time_ms,
12
+ error, find_experiment, get_agent, info, header,
13
+ parse_output, resolve_agent_model, run_monitored_subprocess,
14
+ subprocess_no_window_kwargs, warn, write_json)
15
+ from deveco_arm import analyze_homegraph_stream
16
+
17
+
18
+ def run_experiment(exp_id: str, skip_permissions: bool = True,
19
+ agent_name: str = "",
20
+ results_dir: Optional[Path] = None,
21
+ state_dir: Optional[Path] = None) -> bool:
22
+ exp = find_experiment(exp_id)
23
+ if exp is None:
24
+ error(f"Experiment {exp_id} not found in config")
25
+ return False
26
+ if exp.get("type") == "session":
27
+ error(f"Experiment {exp_id} is session type. Use run_session.py.")
28
+ return False
29
+
30
+ agent = get_agent(agent_name)
31
+ clone_path = os.environ.get("REPO_CLONE", "")
32
+ local_repo = os.environ.get("REPO_PATH", "")
33
+ if clone_path:
34
+ repo_root = Path(clone_path)
35
+ elif local_repo:
36
+ repo_root = Path(local_repo)
37
+ else:
38
+ error("No repo. Use run_all.py to clone, or set REPO_PATH / REPO_CLONE.")
39
+ return False
40
+
41
+ title = exp["title"]
42
+ prompt = exp["prompt"]
43
+ max_turns = exp.get("max_turns", 15)
44
+
45
+ header(f"Experiment {exp_id}: {title}")
46
+ info(f"Agent: {agent['name']} ({agent['binary']}) | Repo: {repo_root}")
47
+ info(f"Max turns: {max_turns} | Permissions: {'skip' if skip_permissions else 'ask'}")
48
+
49
+ session_id, _ = setup_mod.prepare_environment(exp_id, "full", agent_name,
50
+ state_dir=state_dir)
51
+
52
+ result_dir = results_dir / exp_id if results_dir else OUTPUT_DIR / exp_id
53
+ result_dir.mkdir(parents=True, exist_ok=True)
54
+ (result_dir / "prompt.txt").write_text(prompt, encoding="utf-8")
55
+ write_json(result_dir / "experiment_config.json", {**exp, "agent": agent_name or "default"})
56
+
57
+ info("Starting agent...")
58
+ start_ms = current_time_ms()
59
+ os.chdir(repo_root)
60
+
61
+ cmd = build_agent_cmd(agent, session_id, max_turns, prompt,
62
+ resume=False, skip_permissions=skip_permissions,
63
+ repo_root=repo_root)
64
+
65
+ stream_path = result_dir / "stream_output.jsonl"
66
+ text_path = result_dir / "raw_output.txt"
67
+ track_hg = os.environ.get("EVAL_ARM", "") == "homegraph"
68
+
69
+ with open(stream_path, "w", encoding="utf-8") as stream_f:
70
+ result, mem = run_monitored_subprocess(
71
+ cmd, stdout=stream_f, stderr=subprocess.STDOUT, text=True,
72
+ track_homegraph=track_hg,
73
+ )
74
+ exit_code = result.returncode
75
+
76
+ end_ms = current_time_ms()
77
+ dur_ms = end_ms - start_ms
78
+ dur_s = f"{dur_ms / 1000:.1f}"
79
+ info(f"Completed in {dur_s}s (exit={exit_code})")
80
+
81
+ # Parse agent output
82
+ parser_name = agent.get("parser", "claude_stream_json")
83
+ stats = parse_output(stream_path, parser_name)
84
+
85
+ if exit_code != 0:
86
+ error(f"Agent 退出码 {exit_code}")
87
+ for err in stats.errors[:3]:
88
+ error(f" {err}")
89
+ if "Unexpected server error" in " ".join(stats.errors):
90
+ warn("DevEco 服务端报错 — 通常为 API/网络/配额问题,与评测脚本无关。"
91
+ "请检查 DevEco 登录状态、模型配置,稍后重试 `deveco run`。")
92
+ elif stats.tool_calls == 0 and not stats.errors:
93
+ warn("无工具调用且无错误事件 — 检查 stream_output.jsonl 是否为空。")
94
+
95
+ # Human-readable text
96
+ text_content = "\n".join(stats.text_lines)
97
+ text_path.write_text(text_content or "(no text output)", encoding="utf-8")
98
+
99
+ # Modified files
100
+ r2 = subprocess.run(
101
+ ["git", "diff", "--name-only"], capture_output=True, text=True, cwd=repo_root,
102
+ **subprocess_no_window_kwargs(),
103
+ )
104
+ mod_files = len([l for l in r2.stdout.strip().split("\n") if l])
105
+
106
+ hg_stream = analyze_homegraph_stream(stream_path)
107
+
108
+ results = {
109
+ "experiment_id": exp_id, "title": title, "agent": agent["name"],
110
+ "agent_binary": agent["binary"], "session_id": session_id,
111
+ "start_time_ms": start_ms, "end_time_ms": end_ms,
112
+ "duration_ms": dur_ms, "duration_s": dur_s,
113
+ "exit_code": exit_code, "max_turns": max_turns,
114
+ "skip_permissions": skip_permissions,
115
+ "tool_calls": stats.tool_calls, "tool_names": stats.tool_names,
116
+ "homegraph_tool_calls": stats.homegraph_tool_calls,
117
+ "used_homegraph": stats.used_homegraph,
118
+ **hg_stream,
119
+ "eval_arm": os.environ.get("EVAL_ARM", ""),
120
+ "files_read": list(set(stats.files_read)),
121
+ "files_edited": list(set(stats.files_edited)),
122
+ "model": resolve_agent_model(agent, stats),
123
+ "deveco_session_id": stats.deveco_session_id,
124
+ "input_tokens": stats.total_input_tokens,
125
+ "output_tokens": stats.total_output_tokens,
126
+ "assistant_messages": stats.assistant_messages,
127
+ "max_turns_hit": stats.max_turns_hit,
128
+ "errors": stats.errors[:5],
129
+ "modified_files": mod_files,
130
+ "memory": mem.to_dict(),
131
+ }
132
+ write_json(result_dir / "results.json", results)
133
+
134
+ if state_dir:
135
+ append_run_log(state_dir, f"Exp {exp_id} finished in {dur_s}s, tools={stats.tool_calls}, exit={exit_code}")
136
+ info(f"Results saved: {result_dir}/")
137
+ print(f" Duration: {dur_s}s | Tool calls: {stats.tool_calls} | "
138
+ f"HG tools: {stats.homegraph_tool_calls} | "
139
+ f"Files read: {len(set(stats.files_read))} | Edited: {mod_files} | "
140
+ f"Peak mem: {mem.peak_combined_rss_mb:.0f} MB")
141
+ if stats.max_turns_hit:
142
+ print(f" ⚠️ Max turns ({max_turns}) hit")
143
+ return exit_code == 0
144
+
145
+
146
+ if __name__ == "__main__":
147
+ # Parse: python run_one.py <id> [--agent <name>] [--no-skip-permissions]
148
+ args = sys.argv[1:]
149
+ exp_id = args[0] if args else ""
150
+ agent_name = ""
151
+ skip = True
152
+
153
+ i = 1
154
+ while i < len(args):
155
+ if args[i] == "--agent" and i + 1 < len(args):
156
+ agent_name = args[i + 1]; i += 2
157
+ elif args[i] == "--no-skip-permissions":
158
+ skip = False; i += 1
159
+ else:
160
+ i += 1
161
+
162
+ if not exp_id:
163
+ error("Usage: python run_one.py <experiment_id> [--agent <name>]")
164
+ sys.exit(1)
165
+ sys.exit(0 if run_experiment(exp_id, skip, agent_name) else 1)