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.
- package/LICENSE +21 -21
- package/README.md +268 -243
- package/dist/arkts/ohos-api-index.d.ts +15 -0
- package/dist/arkts/ohos-api-index.d.ts.map +1 -0
- package/dist/arkts/ohos-api-index.js +190 -0
- package/dist/arkts/ohos-api-index.js.map +1 -0
- package/dist/arkts/ohos-sdk-input.d.ts +36 -0
- package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
- package/dist/arkts/ohos-sdk-input.js +214 -0
- package/dist/arkts/ohos-sdk-input.js.map +1 -0
- package/dist/bin/command-supervision.d.ts +12 -0
- package/dist/bin/command-supervision.d.ts.map +1 -0
- package/dist/bin/command-supervision.js +86 -0
- package/dist/bin/command-supervision.js.map +1 -0
- package/dist/bin/homegraph.d.ts +1 -1
- package/dist/bin/homegraph.js +971 -71
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/db/index.d.ts +20 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +39 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +58 -19
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +14 -0
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +314 -143
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +172 -152
- package/dist/directory.d.ts +32 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +88 -5
- package/dist/directory.js.map +1 -1
- package/dist/extraction/arkts-batch-worker.d.ts +2 -0
- package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
- package/dist/extraction/arkts-batch-worker.js +28 -0
- package/dist/extraction/arkts-batch-worker.js.map +1 -0
- package/dist/extraction/context.d.ts +11 -0
- package/dist/extraction/context.d.ts.map +1 -1
- package/dist/extraction/context.js +20 -0
- package/dist/extraction/context.js.map +1 -1
- package/dist/extraction/index.d.ts +16 -2
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +619 -380
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
- package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
- package/dist/extraction/languages/arkts-state-decorators.js +26 -0
- package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
- package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.js +21 -6
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +98 -2
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +1290 -61
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +56 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +198 -1
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.js +283 -0
- package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
- package/dist/extraction/parse-pool.d.ts +126 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -0
- package/dist/extraction/parse-pool.js +319 -0
- package/dist/extraction/parse-pool.js.map +1 -0
- package/dist/extraction/tree-sitter-types.d.ts +17 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +21 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +198 -27
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +76 -17
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -4
- package/dist/index.js.map +1 -1
- package/dist/installer/instructions-template.js +9 -9
- package/dist/installer/targets/shared.d.ts +5 -6
- package/dist/installer/targets/shared.d.ts.map +1 -1
- package/dist/installer/targets/shared.js +5 -6
- package/dist/installer/targets/shared.js.map +1 -1
- package/dist/mcp/daemon-paths.d.ts +30 -3
- package/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/dist/mcp/daemon-paths.js +50 -10
- package/dist/mcp/daemon-paths.js.map +1 -1
- package/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/dist/mcp/daemon-registry.js +7 -3
- package/dist/mcp/daemon-registry.js.map +1 -1
- package/dist/mcp/daemon.d.ts +48 -0
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +196 -32
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/engine.d.ts +17 -0
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +73 -1
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +25 -43
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.js +16 -16
- package/dist/mcp/ppid-watchdog.d.ts +18 -0
- package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
- package/dist/mcp/ppid-watchdog.js +37 -0
- package/dist/mcp/ppid-watchdog.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +25 -0
- package/dist/mcp/query-cache.d.ts.map +1 -0
- package/dist/mcp/query-cache.js +191 -0
- package/dist/mcp/query-cache.js.map +1 -0
- package/dist/mcp/query-pool.d.ts +94 -0
- package/dist/mcp/query-pool.d.ts.map +1 -0
- package/dist/mcp/query-pool.js +297 -0
- package/dist/mcp/query-pool.js.map +1 -0
- package/dist/mcp/query-worker.d.ts +24 -0
- package/dist/mcp/query-worker.d.ts.map +1 -0
- package/dist/mcp/query-worker.js +87 -0
- package/dist/mcp/query-worker.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +5 -7
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +72 -74
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/tools.d.ts +94 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +877 -71
- package/dist/mcp/tools.js.map +1 -1
- package/dist/project-config.d.ts +20 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +42 -2
- package/dist/project-config.js.map +1 -1
- package/dist/reasoning/login.js +1 -1
- package/dist/reasoning/login.js.map +1 -1
- package/dist/reasoning/reasoner.js +32 -32
- package/dist/resolution/c-fnptr-synthesizer.d.ts +0 -28
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +765 -79
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +1 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +72 -11
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/cooperative-yield.d.ts +32 -0
- package/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/dist/resolution/cooperative-yield.js +42 -0
- package/dist/resolution/cooperative-yield.js.map +1 -0
- package/dist/resolution/index.d.ts +11 -2
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +72 -4
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +22 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +317 -20
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/spec/config.d.ts +39 -0
- package/dist/spec/config.d.ts.map +1 -0
- package/dist/spec/config.js +304 -0
- package/dist/spec/config.js.map +1 -0
- package/dist/spec/db/commit-node.d.ts +23 -0
- package/dist/spec/db/commit-node.d.ts.map +1 -0
- package/dist/spec/db/commit-node.js +62 -0
- package/dist/spec/db/commit-node.js.map +1 -0
- package/dist/spec/db/fragment-node.d.ts +24 -0
- package/dist/spec/db/fragment-node.d.ts.map +1 -0
- package/dist/spec/db/fragment-node.js +128 -0
- package/dist/spec/db/fragment-node.js.map +1 -0
- package/dist/spec/db/fts.d.ts +74 -0
- package/dist/spec/db/fts.d.ts.map +1 -0
- package/dist/spec/db/fts.js +324 -0
- package/dist/spec/db/fts.js.map +1 -0
- package/dist/spec/db/index.d.ts +13 -0
- package/dist/spec/db/index.d.ts.map +1 -0
- package/dist/spec/db/index.js +50 -0
- package/dist/spec/db/index.js.map +1 -0
- package/dist/spec/db/relations.d.ts +55 -0
- package/dist/spec/db/relations.d.ts.map +1 -0
- package/dist/spec/db/relations.js +158 -0
- package/dist/spec/db/relations.js.map +1 -0
- package/dist/spec/db/schema.d.ts +33 -0
- package/dist/spec/db/schema.d.ts.map +1 -0
- package/dist/spec/db/schema.js +119 -0
- package/dist/spec/db/schema.js.map +1 -0
- package/dist/spec/db/schema.sql +117 -0
- package/dist/spec/db/spec-node.d.ts +41 -0
- package/dist/spec/db/spec-node.d.ts.map +1 -0
- package/dist/spec/db/spec-node.js +114 -0
- package/dist/spec/db/spec-node.js.map +1 -0
- package/dist/spec/evolve/impact-locator.d.ts +13 -0
- package/dist/spec/evolve/impact-locator.d.ts.map +1 -0
- package/dist/spec/evolve/impact-locator.js +25 -0
- package/dist/spec/evolve/impact-locator.js.map +1 -0
- package/dist/spec/evolve/llm-client.d.ts +50 -0
- package/dist/spec/evolve/llm-client.d.ts.map +1 -0
- package/dist/spec/evolve/llm-client.js +176 -0
- package/dist/spec/evolve/llm-client.js.map +1 -0
- package/dist/spec/evolve/logic-checker.d.ts +12 -0
- package/dist/spec/evolve/logic-checker.d.ts.map +1 -0
- package/dist/spec/evolve/logic-checker.js +24 -0
- package/dist/spec/evolve/logic-checker.js.map +1 -0
- package/dist/spec/evolve/pipeline.d.ts +42 -0
- package/dist/spec/evolve/pipeline.d.ts.map +1 -0
- package/dist/spec/evolve/pipeline.js +567 -0
- package/dist/spec/evolve/pipeline.js.map +1 -0
- package/dist/spec/evolve/spec-rewriter.d.ts +42 -0
- package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -0
- package/dist/spec/evolve/spec-rewriter.js +230 -0
- package/dist/spec/evolve/spec-rewriter.js.map +1 -0
- package/dist/spec/graph/queries.d.ts +155 -0
- package/dist/spec/graph/queries.d.ts.map +1 -0
- package/dist/spec/graph/queries.js +440 -0
- package/dist/spec/graph/queries.js.map +1 -0
- package/dist/spec/llm/client.d.ts +29 -0
- package/dist/spec/llm/client.d.ts.map +1 -0
- package/dist/spec/llm/client.js +123 -0
- package/dist/spec/llm/client.js.map +1 -0
- package/dist/spec/llm/index.d.ts +3 -0
- package/dist/spec/llm/index.d.ts.map +1 -0
- package/dist/spec/llm/index.js +11 -0
- package/dist/spec/llm/index.js.map +1 -0
- package/dist/spec/llm/prompts.d.ts +13 -0
- package/dist/spec/llm/prompts.d.ts.map +1 -0
- package/dist/spec/llm/prompts.js +75 -0
- package/dist/spec/llm/prompts.js.map +1 -0
- package/dist/spec/mining/diff-parser.d.ts +33 -0
- package/dist/spec/mining/diff-parser.d.ts.map +1 -0
- package/dist/spec/mining/diff-parser.js +166 -0
- package/dist/spec/mining/diff-parser.js.map +1 -0
- package/dist/spec/mining/git-scanner.d.ts +103 -0
- package/dist/spec/mining/git-scanner.d.ts.map +1 -0
- package/dist/spec/mining/git-scanner.js +307 -0
- package/dist/spec/mining/git-scanner.js.map +1 -0
- package/dist/spec/mining/pipeline.d.ts +53 -0
- package/dist/spec/mining/pipeline.d.ts.map +1 -0
- package/dist/spec/mining/pipeline.js +178 -0
- package/dist/spec/mining/pipeline.js.map +1 -0
- package/dist/spec/mining/scope-resolver.d.ts +45 -0
- package/dist/spec/mining/scope-resolver.d.ts.map +1 -0
- package/dist/spec/mining/scope-resolver.js +103 -0
- package/dist/spec/mining/scope-resolver.js.map +1 -0
- package/dist/spec/mining/spec-extractor.d.ts +69 -0
- package/dist/spec/mining/spec-extractor.d.ts.map +1 -0
- package/dist/spec/mining/spec-extractor.js +369 -0
- package/dist/spec/mining/spec-extractor.js.map +1 -0
- package/dist/spec/types.d.ts +149 -0
- package/dist/spec/types.d.ts.map +1 -0
- package/dist/spec/types.js +15 -0
- package/dist/spec/types.js.map +1 -0
- package/dist/spec/utils.d.ts +167 -0
- package/dist/spec/utils.d.ts.map +1 -0
- package/dist/spec/utils.js +463 -0
- package/dist/spec/utils.js.map +1 -0
- package/dist/sync/worktree.d.ts +9 -0
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +40 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/shimmer-progress.d.ts +2 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -1
- package/dist/ui/shimmer-progress.js +19 -2
- package/dist/ui/shimmer-progress.js.map +1 -1
- package/dist/upgrade/index.js +1 -1
- package/dist/upgrade/index.js.map +1 -1
- package/package.json +58 -57
- 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 -0
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
- package/scripts/agent-eval/run-agent.sh +34 -34
- package/scripts/agent-eval/run-all.sh +69 -69
- package/scripts/agent-eval/run-arms.sh +56 -56
- package/scripts/agent-eval/seq-matrix.mjs +137 -137
- package/scripts/build-bundle.sh +118 -118
- package/scripts/exp_boundary_eval/README.md +247 -0
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
- package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
- package/scripts/exp_boundary_eval/_utils.py +1116 -0
- package/scripts/exp_boundary_eval/analyze.py +1313 -0
- package/scripts/exp_boundary_eval/data/agents.json +109 -0
- package/scripts/exp_boundary_eval/data/experiments.json +140 -0
- package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
- package/scripts/exp_boundary_eval/run_all.py +378 -0
- package/scripts/exp_boundary_eval/run_one.py +165 -0
- package/scripts/exp_boundary_eval/run_session.py +158 -0
- package/scripts/exp_boundary_eval/setup.py +120 -0
- package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
- package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
- package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
- 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 +268 -246
- package/scripts/ohos-sdk-publish.mjs +133 -0
- package/scripts/pack-npm.sh +119 -119
- package/scripts/prepare-release.mjs +270 -270
- package/scripts/qa_eval/README.md +407 -404
- package/scripts/qa_eval/_test_deveco_probe.py +41 -41
- package/scripts/qa_eval/agent_runner.py +526 -526
- package/scripts/qa_eval/data/.gitignore +4 -4
- package/scripts/qa_eval/data/test-set.jsonl +2 -22
- package/scripts/qa_eval/eval_metrics.py +274 -233
- package/scripts/qa_eval/external_agent.py +976 -671
- package/scripts/qa_eval/llm_config.py +92 -92
- package/scripts/qa_eval/memory_monitor.py +132 -132
- package/scripts/qa_eval/my_answer_accuracy.py +187 -187
- package/scripts/qa_eval/requirements.txt +2 -2
- package/scripts/qa_eval/run_pipeline.py +804 -711
- package/scripts/qa_eval/stats_efficiency.py +279 -279
- package/scripts/qa_eval/stats_scores.py +207 -207
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DevEco A/B arm wiring — MCP config, git clean."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
from _utils import EVAL_ROOT, count_homegraph_tools, is_homegraph_tool, log, subprocess_no_window_kwargs, warn
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"count_homegraph_tools",
|
|
18
|
+
"is_homegraph_tool",
|
|
19
|
+
"find_homegraph_bin",
|
|
20
|
+
"find_homegraph_bin_path",
|
|
21
|
+
"homegraph_node_argv",
|
|
22
|
+
"write_deveco_project_config",
|
|
23
|
+
"git_clean_repo",
|
|
24
|
+
"try_remove_homegraph_index",
|
|
25
|
+
"is_homegraph_indexed",
|
|
26
|
+
"ensure_homegraph_index",
|
|
27
|
+
"analyze_homegraph_stream",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Photos-scale repos can take 15+ minutes on first index.
|
|
31
|
+
HOMEGRAPH_INDEX_TIMEOUT_S = 1800
|
|
32
|
+
|
|
33
|
+
# Node.js rejects --stack-size in NODE_OPTIONS; pass it as a direct node argv instead.
|
|
34
|
+
# Override with HOMEGRAPH_NODE_STACK_SIZE_KB=0 to use the V8 default (~984 KB).
|
|
35
|
+
def _homegraph_stack_size_kb() -> int:
|
|
36
|
+
raw = os.environ.get("HOMEGRAPH_NODE_STACK_SIZE_KB", "32768").strip()
|
|
37
|
+
if not raw:
|
|
38
|
+
return 32768
|
|
39
|
+
try:
|
|
40
|
+
return max(0, int(raw))
|
|
41
|
+
except ValueError:
|
|
42
|
+
return 32768
|
|
43
|
+
|
|
44
|
+
# Windows STATUS_STACK_OVERFLOW (native stack, not catchable in JS).
|
|
45
|
+
_STACK_OVERFLOW_EXIT_CODES = frozenset({3221225725, -1073741571})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _homegraph_stack_sizes_to_try() -> List[int]:
|
|
49
|
+
base = _homegraph_stack_size_kb()
|
|
50
|
+
sizes: List[int] = []
|
|
51
|
+
for kb in (base, 65536, 131072, 262144):
|
|
52
|
+
if kb > 0 and kb not in sizes:
|
|
53
|
+
sizes.append(kb)
|
|
54
|
+
return sizes
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _is_stack_overflow_exit(code: Optional[int]) -> bool:
|
|
58
|
+
return code in _STACK_OVERFLOW_EXIT_CODES if code is not None else False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_HG_FAILURE_MARKERS = (
|
|
62
|
+
"No HomeGraph project is loaded",
|
|
63
|
+
"isn't indexed with homegraph",
|
|
64
|
+
"not indexed with homegraph",
|
|
65
|
+
"Pass projectPath to the tool call",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
_DEVECO_WITH_AGENT_PROMPT = (
|
|
69
|
+
"你是鸿蒙 ArkTS 代码仓库问答 Agent(homegraph 评测臂)。"
|
|
70
|
+
"定位/理解代码时,优先调用 MCP 工具 homegraph_explore(不要用 homegraph_node);"
|
|
71
|
+
"query 写符号名(类名、方法名、组件名、常量名),"
|
|
72
|
+
"如 ThirdSelectAlbumGridBase、GRID_GUTTER、ImageGridItemComponent。"
|
|
73
|
+
"仅当 explore 结果不足时再用 grep/read/glob。"
|
|
74
|
+
"基于仓库事实作答,中文简洁准确。"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
_DEVECO_WITHOUT_AGENT_PROMPT = (
|
|
78
|
+
"你是鸿蒙 ArkTS 代码仓库问答 Agent(baseline 臂,无 homegraph)。"
|
|
79
|
+
"用 grep、read 等内置工具探索仓库后作答,中文简洁准确。"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def find_homegraph_bin_path() -> Optional[Path]:
|
|
84
|
+
hg_bin = EVAL_ROOT.parents[1] / "dist" / "bin" / "homegraph.js"
|
|
85
|
+
return hg_bin if hg_bin.exists() else None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _homegraph_node_inner(*args: str, stack_kb: Optional[int] = None) -> List[str]:
|
|
89
|
+
"""Args after ``node``: ``[--liftoff-only, --stack-size=N, homegraph.js, …]``."""
|
|
90
|
+
hg_bin = find_homegraph_bin_path()
|
|
91
|
+
if hg_bin is None:
|
|
92
|
+
raise FileNotFoundError("homegraph binary not found")
|
|
93
|
+
kb = _homegraph_stack_size_kb() if stack_kb is None else stack_kb
|
|
94
|
+
inner: List[str] = []
|
|
95
|
+
# Skip homegraph.js re-exec spawn (extra node.exe flash on Windows).
|
|
96
|
+
if os.name == "nt":
|
|
97
|
+
inner.append("--liftoff-only")
|
|
98
|
+
if kb > 0:
|
|
99
|
+
inner.append(f"--stack-size={kb}")
|
|
100
|
+
inner.append(str(hg_bin))
|
|
101
|
+
inner.extend(args)
|
|
102
|
+
return inner
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _homegraph_mcp_env() -> dict:
|
|
106
|
+
"""Fewer node spawns during eval MCP (no daemon / watchdog subprocesses)."""
|
|
107
|
+
return {
|
|
108
|
+
"HOMEGRAPH_NO_DAEMON": "1",
|
|
109
|
+
"HOMEGRAPH_NO_WATCHDOG": "1",
|
|
110
|
+
"HOMEGRAPH_WASM_RELAUNCHED": "1",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _resolve_node_binary() -> str:
|
|
115
|
+
return os.environ.get("HOMEGRAPH_NODE", "node").strip() or "node"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def homegraph_node_argv(*args: str, stack_kb: Optional[int] = None) -> List[str]:
|
|
119
|
+
"""``node [--stack-size=N] homegraph.js …`` for Python subprocess (capture_output-safe)."""
|
|
120
|
+
return [_resolve_node_binary(), *_homegraph_node_inner(*args, stack_kb=stack_kb)]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _mcp_launcher_python() -> str:
|
|
124
|
+
"""pythonw.exe — no console when DevEco spawns MCP."""
|
|
125
|
+
pythonw = Path(sys.executable).with_name("pythonw.exe")
|
|
126
|
+
return str(pythonw) if pythonw.is_file() else sys.executable
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def homegraph_mcp_command(repo: Path) -> List[str]:
|
|
130
|
+
"""MCP argv: pythonw (no flash) → hidden node, stdio preserved."""
|
|
131
|
+
inner = _homegraph_node_inner("serve", "--mcp", "--path", str(repo.resolve()))
|
|
132
|
+
if os.name == "nt":
|
|
133
|
+
launcher = EVAL_ROOT / "win_mcp_launcher.py"
|
|
134
|
+
return [_mcp_launcher_python(), str(launcher), *inner]
|
|
135
|
+
return [_resolve_node_binary(), *inner]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def find_homegraph_bin() -> str:
|
|
139
|
+
"""Human-readable homegraph CLI prefix (logging only)."""
|
|
140
|
+
hg_bin = find_homegraph_bin_path()
|
|
141
|
+
if hg_bin is None:
|
|
142
|
+
return ""
|
|
143
|
+
stack_kb = _homegraph_stack_size_kb()
|
|
144
|
+
stack = f" --stack-size={stack_kb}" if stack_kb > 0 else ""
|
|
145
|
+
return f"{_resolve_node_binary()}{stack} {hg_bin}"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def deveco_project_config_dir(repo: Path) -> Path:
|
|
149
|
+
return repo / ".deveco"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def write_deveco_project_config(repo: Path, arm: str) -> Optional[Path]:
|
|
153
|
+
"""Write repo/.deveco/deveco.jsonc for baseline or homegraph arm."""
|
|
154
|
+
config_dir = deveco_project_config_dir(repo)
|
|
155
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
config_path = config_dir / "deveco.jsonc"
|
|
157
|
+
body = {"$schema": "https://opencode.ai/config.json"}
|
|
158
|
+
|
|
159
|
+
if arm == "homegraph":
|
|
160
|
+
if find_homegraph_bin_path() is None:
|
|
161
|
+
warn("homegraph binary not found; MCP will not be configured")
|
|
162
|
+
return None
|
|
163
|
+
body["mcp"] = {
|
|
164
|
+
"homegraph": {
|
|
165
|
+
"type": "local",
|
|
166
|
+
"command": homegraph_mcp_command(repo),
|
|
167
|
+
"enabled": True,
|
|
168
|
+
"environment": _homegraph_mcp_env(),
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
body["agent"] = {"build": {"prompt": _DEVECO_WITH_AGENT_PROMPT}}
|
|
172
|
+
else:
|
|
173
|
+
body["mcp"] = {}
|
|
174
|
+
body["permission"] = {
|
|
175
|
+
"homegraph_*": "deny",
|
|
176
|
+
"homegraph_homegraph_*": "deny",
|
|
177
|
+
}
|
|
178
|
+
body["agent"] = {
|
|
179
|
+
"build": {
|
|
180
|
+
"prompt": _DEVECO_WITHOUT_AGENT_PROMPT,
|
|
181
|
+
"permission": {
|
|
182
|
+
"homegraph_*": "deny",
|
|
183
|
+
"homegraph_homegraph_*": "deny",
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
config_path.write_text(json.dumps(body, indent=2) + "\n", encoding="utf-8")
|
|
189
|
+
log(f"DevEco config ({arm}): {config_path}")
|
|
190
|
+
return config_path
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def git_clean_repo(repo_root: Path, arm: str) -> None:
|
|
194
|
+
"""Reset untracked files.
|
|
195
|
+
|
|
196
|
+
Always preserve ``.homegraph/`` — deleting it on every experiment reset hits
|
|
197
|
+
Windows file locks when homegraph.db is open. Baseline arm blocks HG via
|
|
198
|
+
``deveco.jsonc`` permissions instead.
|
|
199
|
+
"""
|
|
200
|
+
cmd = ["git", "clean", "-fd", "-e", ".homegraph"]
|
|
201
|
+
if arm == "homegraph":
|
|
202
|
+
cmd.append("-e")
|
|
203
|
+
cmd.append(".deveco")
|
|
204
|
+
subprocess.run(
|
|
205
|
+
cmd, cwd=repo_root, capture_output=True, text=True,
|
|
206
|
+
**subprocess_no_window_kwargs(),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def try_remove_homegraph_index(repo_root: Path) -> bool:
|
|
211
|
+
"""Best-effort remove ``.homegraph/`` once at baseline suite start."""
|
|
212
|
+
import os
|
|
213
|
+
import stat
|
|
214
|
+
import time
|
|
215
|
+
|
|
216
|
+
hg_dir = repo_root.resolve() / ".homegraph"
|
|
217
|
+
if not hg_dir.exists():
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
def _onerror(func, path, _exc_info):
|
|
221
|
+
try:
|
|
222
|
+
os.chmod(path, stat.S_IWRITE)
|
|
223
|
+
func(path)
|
|
224
|
+
except OSError:
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
for attempt in range(3):
|
|
228
|
+
try:
|
|
229
|
+
shutil.rmtree(str(hg_dir), onerror=_onerror)
|
|
230
|
+
except OSError:
|
|
231
|
+
pass
|
|
232
|
+
if not hg_dir.exists():
|
|
233
|
+
log(f"Removed .homegraph for baseline arm")
|
|
234
|
+
return True
|
|
235
|
+
time.sleep(0.5 * (attempt + 1))
|
|
236
|
+
|
|
237
|
+
warn(f"无法删除 {hg_dir}(homegraph.db 可能被占用)")
|
|
238
|
+
warn("baseline 组将继续:.deveco/deveco.jsonc 已 deny homegraph_* 工具")
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def homegraph_db_path(repo: Path) -> Path:
|
|
243
|
+
return repo.resolve() / ".homegraph" / "homegraph.db"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def is_homegraph_indexed(repo: Path) -> bool:
|
|
247
|
+
"""True when ``.homegraph/homegraph.db`` exists (not necessarily complete)."""
|
|
248
|
+
db = homegraph_db_path(repo)
|
|
249
|
+
return db.is_file() and db.stat().st_size > 0
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _run_homegraph_cli(
|
|
253
|
+
repo: Path, subcommand: str, *extra_args: str, capture: bool = False,
|
|
254
|
+
stack_kb: Optional[int] = None,
|
|
255
|
+
) -> subprocess.CompletedProcess:
|
|
256
|
+
if find_homegraph_bin_path() is None:
|
|
257
|
+
raise FileNotFoundError("homegraph binary not found")
|
|
258
|
+
kwargs: dict = {
|
|
259
|
+
"timeout": HOMEGRAPH_INDEX_TIMEOUT_S,
|
|
260
|
+
"text": True,
|
|
261
|
+
**subprocess_no_window_kwargs(),
|
|
262
|
+
}
|
|
263
|
+
if capture:
|
|
264
|
+
kwargs["capture_output"] = True
|
|
265
|
+
return subprocess.run(
|
|
266
|
+
homegraph_node_argv(subcommand, *extra_args, str(repo.resolve()), stack_kb=stack_kb),
|
|
267
|
+
**kwargs,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _run_homegraph_index_with_stack_retry(repo: Path, command: str) -> subprocess.CompletedProcess:
|
|
272
|
+
"""Run index/init; on Windows stack overflow, retry with larger --stack-size."""
|
|
273
|
+
last: Optional[subprocess.CompletedProcess] = None
|
|
274
|
+
for stack_kb in _homegraph_stack_sizes_to_try():
|
|
275
|
+
if stack_kb != _homegraph_stack_size_kb():
|
|
276
|
+
log(f"Retrying homegraph {command} with --stack-size={stack_kb} …")
|
|
277
|
+
last = _run_homegraph_cli(repo, command, stack_kb=stack_kb)
|
|
278
|
+
if last.returncode == 0 or not _is_stack_overflow_exit(last.returncode):
|
|
279
|
+
return last
|
|
280
|
+
assert last is not None
|
|
281
|
+
return last
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def get_homegraph_status(repo: Path) -> dict:
|
|
285
|
+
"""Parse ``homegraph status --json``."""
|
|
286
|
+
try:
|
|
287
|
+
r = _run_homegraph_cli(repo, "status", "-j", capture=True)
|
|
288
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
289
|
+
return {}
|
|
290
|
+
if r.returncode != 0:
|
|
291
|
+
return {}
|
|
292
|
+
raw = (r.stdout or "").strip()
|
|
293
|
+
start = raw.find("{")
|
|
294
|
+
if start < 0:
|
|
295
|
+
return {}
|
|
296
|
+
try:
|
|
297
|
+
return json.loads(raw[start:])
|
|
298
|
+
except json.JSONDecodeError:
|
|
299
|
+
return {}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _count_ets_files(repo: Path) -> int:
|
|
303
|
+
cache = getattr(_count_ets_files, "_cache", {})
|
|
304
|
+
key = str(repo.resolve())
|
|
305
|
+
if key not in cache:
|
|
306
|
+
cache[key] = sum(1 for _ in repo.rglob("*.ets"))
|
|
307
|
+
_count_ets_files._cache = cache
|
|
308
|
+
return cache[key]
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def assess_index_health(repo: Path, status: Optional[dict] = None) -> Tuple[bool, str]:
|
|
312
|
+
"""Return (healthy, reason). Detects partial indexes (e.g. aborted init)."""
|
|
313
|
+
status = status if status is not None else get_homegraph_status(repo)
|
|
314
|
+
if not status.get("initialized"):
|
|
315
|
+
return False, "not initialized"
|
|
316
|
+
|
|
317
|
+
index_meta = status.get("index") or {}
|
|
318
|
+
if index_meta.get("reindexRecommended"):
|
|
319
|
+
return False, "homegraph status reports reindexRecommended"
|
|
320
|
+
|
|
321
|
+
kinds = status.get("nodesByKind") or {}
|
|
322
|
+
arkts_nodes = kinds.get("struct", 0) + kinds.get("component", 0)
|
|
323
|
+
langs = status.get("languages") or []
|
|
324
|
+
file_count = int(status.get("fileCount") or 0)
|
|
325
|
+
ets_count = _count_ets_files(repo)
|
|
326
|
+
|
|
327
|
+
if ets_count >= 50 and arkts_nodes == 0 and "arkts" not in langs:
|
|
328
|
+
return False, (
|
|
329
|
+
f"repo has {ets_count} .ets files but index has no arkts/struct/component "
|
|
330
|
+
f"(fileCount={file_count})"
|
|
331
|
+
)
|
|
332
|
+
if ets_count >= 50 and file_count < max(ets_count // 2, 200):
|
|
333
|
+
return False, (
|
|
334
|
+
f"indexed only {file_count} files but repo has {ets_count} .ets files "
|
|
335
|
+
"(likely partial/aborted index)"
|
|
336
|
+
)
|
|
337
|
+
return True, ""
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def ensure_homegraph_index(repo: Path, *, force: bool = False, skip: bool = False) -> dict:
|
|
341
|
+
"""Ensure index exists via full rebuild (never sync-only).
|
|
342
|
+
|
|
343
|
+
- No ``.homegraph/`` → ``homegraph init <repo>`` (then ``index`` if still unhealthy)
|
|
344
|
+
- Otherwise → ``homegraph index`` every time (clears DB + rebuilds ArkTS Scene)
|
|
345
|
+
- ``skip=True`` → use existing ``.homegraph/`` only (no init/index); fails if missing/unhealthy
|
|
346
|
+
|
|
347
|
+
Use ``sync`` only for manual incremental updates outside this eval harness.
|
|
348
|
+
"""
|
|
349
|
+
import time
|
|
350
|
+
|
|
351
|
+
repo = repo.resolve()
|
|
352
|
+
hg_bin = find_homegraph_bin_path()
|
|
353
|
+
if hg_bin is None:
|
|
354
|
+
msg = "homegraph binary not found; run npm run build in homegraph repo first"
|
|
355
|
+
warn(msg)
|
|
356
|
+
return _index_result(False, "none", 0, msg)
|
|
357
|
+
|
|
358
|
+
if skip:
|
|
359
|
+
return _use_existing_homegraph_index(repo)
|
|
360
|
+
|
|
361
|
+
status = get_homegraph_status(repo)
|
|
362
|
+
healthy, health_reason = assess_index_health(repo, status)
|
|
363
|
+
|
|
364
|
+
if not status.get("initialized"):
|
|
365
|
+
command = "init"
|
|
366
|
+
log("HomeGraph not initialized — running homegraph init (Photos may take 15–30 min)...")
|
|
367
|
+
else:
|
|
368
|
+
command = "index"
|
|
369
|
+
if not healthy:
|
|
370
|
+
warn(f"HomeGraph index unhealthy: {health_reason}")
|
|
371
|
+
elif force:
|
|
372
|
+
log("HomeGraph index — forced full rebuild (homegraph index)...")
|
|
373
|
+
else:
|
|
374
|
+
log("HomeGraph index — full rebuild (homegraph index, progress below)...")
|
|
375
|
+
|
|
376
|
+
start_ms = int(time.time() * 1000)
|
|
377
|
+
try:
|
|
378
|
+
if command == "index":
|
|
379
|
+
r = _run_homegraph_index_with_stack_retry(repo, command)
|
|
380
|
+
else:
|
|
381
|
+
r = _run_homegraph_cli(repo, command)
|
|
382
|
+
except subprocess.TimeoutExpired:
|
|
383
|
+
msg = f"homegraph {command} timed out after {HOMEGRAPH_INDEX_TIMEOUT_S}s"
|
|
384
|
+
warn(msg)
|
|
385
|
+
return _index_result(False, command, HOMEGRAPH_INDEX_TIMEOUT_S * 1000, msg)
|
|
386
|
+
|
|
387
|
+
elapsed_ms = int(time.time() * 1000) - start_ms
|
|
388
|
+
err_tail = (r.stderr or r.stdout or "").strip()
|
|
389
|
+
if len(err_tail) > 500:
|
|
390
|
+
err_tail = err_tail[:500] + "…"
|
|
391
|
+
|
|
392
|
+
# init on an already-initialized repo exits 0 but does not re-index — follow with index.
|
|
393
|
+
if command == "init" and r.returncode == 0 and is_homegraph_indexed(repo):
|
|
394
|
+
post_status = get_homegraph_status(repo)
|
|
395
|
+
post_healthy, post_reason = assess_index_health(repo, post_status)
|
|
396
|
+
if not post_healthy:
|
|
397
|
+
log(f"init finished but index still unhealthy ({post_reason}) — running homegraph index...")
|
|
398
|
+
try:
|
|
399
|
+
r = _run_homegraph_index_with_stack_retry(repo, "index")
|
|
400
|
+
elapsed_ms = int(time.time() * 1000) - start_ms
|
|
401
|
+
if r.returncode != 0:
|
|
402
|
+
err_tail = (r.stderr or r.stdout or "").strip()[:500]
|
|
403
|
+
return _index_result(False, "index", elapsed_ms, err_tail or "index failed after init")
|
|
404
|
+
command = "index"
|
|
405
|
+
except subprocess.TimeoutExpired:
|
|
406
|
+
return _index_result(False, "index", elapsed_ms, "index timed out after init")
|
|
407
|
+
|
|
408
|
+
if r.returncode != 0:
|
|
409
|
+
return _index_result(False, command, elapsed_ms, err_tail or f"exit code {r.returncode}")
|
|
410
|
+
|
|
411
|
+
final_status = get_homegraph_status(repo)
|
|
412
|
+
final_healthy, final_reason = assess_index_health(repo, final_status)
|
|
413
|
+
if not final_healthy:
|
|
414
|
+
return _index_result(False, command, elapsed_ms, final_reason)
|
|
415
|
+
|
|
416
|
+
log(
|
|
417
|
+
f"homegraph {command} OK ({elapsed_ms / 1000:.1f}s) — "
|
|
418
|
+
f"{final_status.get('fileCount', '?')} files, "
|
|
419
|
+
f"{final_status.get('nodeCount', '?')} nodes"
|
|
420
|
+
)
|
|
421
|
+
return _index_result(True, command, elapsed_ms, "", final_status)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _use_existing_homegraph_index(repo: Path) -> dict:
|
|
425
|
+
"""Validate a pre-copied ``.homegraph/`` without running init/index."""
|
|
426
|
+
repo = repo.resolve()
|
|
427
|
+
if not is_homegraph_indexed(repo):
|
|
428
|
+
msg = (
|
|
429
|
+
"no .homegraph/homegraph.db — copy a .homegraph/ directory into the repo "
|
|
430
|
+
"or omit --skip-index"
|
|
431
|
+
)
|
|
432
|
+
warn(msg)
|
|
433
|
+
return _index_result(False, "skip", 0, msg)
|
|
434
|
+
|
|
435
|
+
status = get_homegraph_status(repo)
|
|
436
|
+
if not status.get("initialized"):
|
|
437
|
+
msg = "homegraph status: not initialized (invalid or empty .homegraph/)"
|
|
438
|
+
warn(msg)
|
|
439
|
+
return _index_result(False, "skip", 0, msg)
|
|
440
|
+
|
|
441
|
+
healthy, health_reason = assess_index_health(repo, status)
|
|
442
|
+
if not healthy:
|
|
443
|
+
warn(f"已有 .homegraph/ 不健康: {health_reason}")
|
|
444
|
+
return _index_result(False, "skip", 0, health_reason or "index unhealthy")
|
|
445
|
+
|
|
446
|
+
changes = status.get("pendingChanges") or {}
|
|
447
|
+
pending = int(changes.get("added", 0)) + int(changes.get("modified", 0)) + int(
|
|
448
|
+
changes.get("removed", 0)
|
|
449
|
+
)
|
|
450
|
+
if pending > 0:
|
|
451
|
+
warn(
|
|
452
|
+
f"仓库相对索引有 {pending} 处文件变更(added/modified/removed);"
|
|
453
|
+
"复制索引要求相同 commit。可 `homegraph sync` 或去掉 --skip-index 全量 index。"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
log(
|
|
457
|
+
f"跳过 index — 使用已有 .homegraph/ "
|
|
458
|
+
f"({status.get('fileCount', '?')} files, {status.get('nodeCount', '?')} nodes)"
|
|
459
|
+
)
|
|
460
|
+
out = _index_result(True, "skip", 0, "", status)
|
|
461
|
+
out["homegraph_index_skipped"] = True
|
|
462
|
+
return out
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _index_result(success: bool, command: str, ms: int, error: str,
|
|
466
|
+
status: Optional[dict] = None) -> dict:
|
|
467
|
+
out = {
|
|
468
|
+
"homegraph_index_ms": ms,
|
|
469
|
+
"homegraph_index_success": success,
|
|
470
|
+
"homegraph_index_command": command,
|
|
471
|
+
"homegraph_index_skipped": False,
|
|
472
|
+
"homegraph_index_error": error,
|
|
473
|
+
}
|
|
474
|
+
if status:
|
|
475
|
+
out["homegraph_file_count"] = status.get("fileCount", 0)
|
|
476
|
+
out["homegraph_node_count"] = status.get("nodeCount", 0)
|
|
477
|
+
kinds = status.get("nodesByKind") or {}
|
|
478
|
+
out["homegraph_arkts_nodes"] = kinds.get("struct", 0) + kinds.get("component", 0)
|
|
479
|
+
return out
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def analyze_homegraph_stream(stream_path: Path) -> dict:
|
|
483
|
+
"""Count HomeGraph MCP calls and how many returned usable graph data."""
|
|
484
|
+
result = {
|
|
485
|
+
"homegraph_calls": 0,
|
|
486
|
+
"homegraph_effective_calls": 0,
|
|
487
|
+
"homegraph_failed_calls": 0,
|
|
488
|
+
"homegraph_failure_snippets": [],
|
|
489
|
+
}
|
|
490
|
+
if not stream_path.exists():
|
|
491
|
+
return result
|
|
492
|
+
|
|
493
|
+
import json
|
|
494
|
+
for line in stream_path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
|
495
|
+
line = line.strip()
|
|
496
|
+
if not line:
|
|
497
|
+
continue
|
|
498
|
+
try:
|
|
499
|
+
event = json.loads(line)
|
|
500
|
+
except json.JSONDecodeError:
|
|
501
|
+
continue
|
|
502
|
+
if event.get("type") != "tool_use":
|
|
503
|
+
continue
|
|
504
|
+
part = event.get("part") or {}
|
|
505
|
+
tool = part.get("tool", "")
|
|
506
|
+
if not is_homegraph_tool(tool):
|
|
507
|
+
continue
|
|
508
|
+
result["homegraph_calls"] += 1
|
|
509
|
+
state = part.get("state") or {}
|
|
510
|
+
output = str(state.get("output") or "")
|
|
511
|
+
failed = any(m in output for m in _HG_FAILURE_MARKERS)
|
|
512
|
+
if failed:
|
|
513
|
+
result["homegraph_failed_calls"] += 1
|
|
514
|
+
snippet = output.split("\n", 1)[0][:120]
|
|
515
|
+
if snippet and snippet not in result["homegraph_failure_snippets"]:
|
|
516
|
+
result["homegraph_failure_snippets"].append(snippet)
|
|
517
|
+
else:
|
|
518
|
+
result["homegraph_effective_calls"] += 1
|
|
519
|
+
return result
|