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
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Cost/token analysis for the 3-arm offload eval, with a MAIN-vs-SUBAGENT split.
|
|
3
|
-
//
|
|
4
|
-
// The explore-subagent question. With delegation ALLOWED, the nocg arm spawns a
|
|
5
|
-
// Claude Code Explore subagent; the homegraph arms do all work in the main agent.
|
|
6
|
-
// Two facts make naive accounting wrong:
|
|
7
|
-
// 1. The Explore subagent runs on HAIKU 4.5; the main agent on SONNET 4.6.
|
|
8
|
-
// So per-token cost differs ~3x between them — you cannot price both the same.
|
|
9
|
-
// 2. The subagent's consumption is ~95% cache-reads. At Haiku's $0.10/MTok
|
|
10
|
-
// cache-read rate, a huge TOKEN volume is a small DOLLAR cost.
|
|
11
|
-
//
|
|
12
|
-
// Rather than re-derive cost from raw token counts (and guess the cache TTL —
|
|
13
|
-
// Claude Code uses 1-hour ephemeral cache here, 2x write, not 5-min), we read
|
|
14
|
-
// Claude Code's OWN authoritative accounting from the `result` event:
|
|
15
|
-
// result.modelUsage[model].costUSD — per-model cost CC itself billed
|
|
16
|
-
// result.total_cost_usd — their sum (INCLUDES the Haiku subagent;
|
|
17
|
-
// the handoff's "excludes subagent" was wrong)
|
|
18
|
-
// The model split IS the agent split here: sonnet => main, haiku => Explore subagent
|
|
19
|
-
// (only nocg spawns one, and only nocg shows haiku usage). Token volume is still
|
|
20
|
-
// summed per-model from modelUsage for the separate "tokens" story.
|
|
21
|
-
//
|
|
22
|
-
// Usage: offload-eval-cost.mjs <runs-dir> <repo> [reps]
|
|
23
|
-
// e.g. offload-eval-cost.mjs /tmp/cg-offload-eval/runs trezor 3
|
|
24
|
-
import { readFileSync, existsSync } from 'fs';
|
|
25
|
-
|
|
26
|
-
const MAIN_TIER = /sonnet/; // main agent
|
|
27
|
-
const SUB_TIER = /haiku/; // Claude Code Explore subagent
|
|
28
|
-
|
|
29
|
-
const [,, runsDir, repo, repsArg] = process.argv;
|
|
30
|
-
if (!runsDir || !repo) { console.error('usage: offload-eval-cost.mjs <runs-dir> <repo> [reps] (env ARMS=nocg,raw,offload)'); process.exit(1); }
|
|
31
|
-
const REPS = Number(repsArg || 3);
|
|
32
|
-
// Arms to analyze (file stems `<repo>-<arm>-<rep>.jsonl`). Override for the style A/B:
|
|
33
|
-
// ARMS=raw,refs,map,src. nocg's Haiku subagent is the only sub-tier; the rest are main-only.
|
|
34
|
-
const ARMS = (process.env.ARMS || 'nocg,raw,offload').split(',').map((s) => s.trim()).filter(Boolean);
|
|
35
|
-
|
|
36
|
-
const toks = (u) => (u.inputTokens||0)+(u.outputTokens||0)+(u.cacheReadInputTokens||0)+(u.cacheCreationInputTokens||0);
|
|
37
|
-
|
|
38
|
-
function analyzeRun(file) {
|
|
39
|
-
let result = null, agentCalls = 0;
|
|
40
|
-
const tools = {}, subPids = new Set();
|
|
41
|
-
for (const line of readFileSync(file, 'utf8').split('\n')) {
|
|
42
|
-
if (!line) continue;
|
|
43
|
-
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
44
|
-
if (e.parent_tool_use_id && e.message?.usage) subPids.add(e.parent_tool_use_id);
|
|
45
|
-
if (e.type === 'assistant' && Array.isArray(e.message?.content))
|
|
46
|
-
for (const b of e.message.content)
|
|
47
|
-
if (b.type === 'tool_use') { tools[b.name] = (tools[b.name]||0)+1; if (b.name === 'Agent') agentCalls++; }
|
|
48
|
-
if (e.type === 'result') result = e;
|
|
49
|
-
}
|
|
50
|
-
// Authoritative cost + tokens from Claude Code's per-model accounting.
|
|
51
|
-
const mu = result?.modelUsage || {};
|
|
52
|
-
const main = { cost: 0, tok: 0 }, sub = { cost: 0, tok: 0 };
|
|
53
|
-
for (const [model, u] of Object.entries(mu)) {
|
|
54
|
-
const bucket = SUB_TIER.test(model) ? sub : main; // sonnet/anything-else => main
|
|
55
|
-
bucket.cost += u.costUSD || 0;
|
|
56
|
-
bucket.tok += toks(u);
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
main, sub, subagents: subPids.size, agentCalls,
|
|
60
|
-
ccTotal: result?.total_cost_usd ?? null,
|
|
61
|
-
ok: result?.subtype === 'success',
|
|
62
|
-
durationSec: result?.duration_ms ? +(result.duration_ms/1000).toFixed(1) : null,
|
|
63
|
-
models: Object.keys(mu), tools,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const k = (n) => (n/1000).toFixed(0).padStart(5) + 'K';
|
|
68
|
-
const d = (n) => '$' + n.toFixed(3);
|
|
69
|
-
const cost = (b) => b.cost;
|
|
70
|
-
const tot = (b) => b.tok;
|
|
71
|
-
|
|
72
|
-
const byArm = {};
|
|
73
|
-
for (const arm of ARMS) {
|
|
74
|
-
const runs = [];
|
|
75
|
-
for (let r = 1; r <= REPS; r++) {
|
|
76
|
-
const f = `${runsDir}/${repo}-${arm}-${r}.jsonl`;
|
|
77
|
-
if (existsSync(f)) runs.push({ rep: r, ...analyzeRun(f) });
|
|
78
|
-
}
|
|
79
|
-
byArm[arm] = runs;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Per-run detail. Cost is Claude Code's own modelUsage.costUSD (authoritative,
|
|
83
|
-
// per-model pricing + correct cache TTL). MAIN=Sonnet, SUB=Haiku Explore subagent.
|
|
84
|
-
// cc-check: main$+sub$ must equal result.total_cost_usd (delta should be ~0).
|
|
85
|
-
console.log(`\n=== ${repo}: per-run main(Sonnet)/sub(Haiku) split — Claude Code's own cost accounting ===`);
|
|
86
|
-
console.log('arm rep | subAg | MAIN(sonnet) tok / $ | SUB(haiku) tok / $ | TOTAL tok / $ | cc_total Δ | dur reads');
|
|
87
|
-
for (const arm of ARMS) for (const r of byArm[arm]) {
|
|
88
|
-
const mC = cost(r.main), sC = cost(r.sub), mT = tot(r.main), sT = tot(r.sub);
|
|
89
|
-
const reads = r.tools['Read'] || 0, grep = (r.tools['Grep']||0)+(r.tools['Bash']||0)+(r.tools['Glob']||0);
|
|
90
|
-
const explore = r.tools['mcp__homegraph__homegraph_explore'] || 0;
|
|
91
|
-
const delta = (mC + sC) - (r.ccTotal || 0); // should be ~0
|
|
92
|
-
console.log(
|
|
93
|
-
`${arm.padEnd(8)} #${r.rep} | ${String(r.subagents).padStart(2)} | ${k(mT)} ${d(mC).padStart(7)} | ${k(sT)} ${d(sC).padStart(7)} | ${k(mT+sT)} ${d(mC+sC).padStart(7)} | ${d(r.ccTotal||0).padStart(7)} ${(delta>=0?'+':'')+delta.toFixed(4)} | ${String(r.durationSec).padStart(5)} r=${reads} g=${grep} x=${explore}`
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Per-arm means
|
|
98
|
-
const mean = (arr, f) => arr.length ? arr.reduce((s,x)=>s+f(x),0)/arr.length : 0;
|
|
99
|
-
console.log(`\n=== ${repo}: per-arm MEANS (n per arm) ===`);
|
|
100
|
-
console.log('arm n | main $ sub $ TOTAL $ | main tok sub tok TOTAL tok | %$ in sub | %tok in sub');
|
|
101
|
-
for (const arm of ARMS) {
|
|
102
|
-
const runs = byArm[arm]; if (!runs.length) continue;
|
|
103
|
-
const mC = mean(runs, r=>cost(r.main)), sC = mean(runs, r=>cost(r.sub));
|
|
104
|
-
const mT = mean(runs, r=>tot(r.main)), sT = mean(runs, r=>tot(r.sub));
|
|
105
|
-
const pctSubC = (mC+sC) ? (100*sC/(mC+sC)) : 0;
|
|
106
|
-
const pctSubT = (mT+sT) ? (100*sT/(mT+sT)) : 0;
|
|
107
|
-
console.log(
|
|
108
|
-
`${arm.padEnd(8)} ${runs.length} | ${d(mC).padStart(7)} ${d(sC).padStart(7)} ${d(mC+sC).padStart(7)} | ${k(mT)} ${k(sT)} ${k(mT+sT)} | ${pctSubC.toFixed(0).padStart(3)}% | ${pctSubT.toFixed(0).padStart(3)}%`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Headline ladders — cost, tokens, duration, all vs a baseline (nocg if present, else first arm).
|
|
113
|
-
console.log(`\n=== Ladders (mean, incl. subagent) ===`);
|
|
114
|
-
const totals = ARMS.map(a => ({ a, c: mean(byArm[a], r=>cost(r.main)+cost(r.sub)), t: mean(byArm[a], r=>tot(r.main)+tot(r.sub)) })).filter(x=>byArm[x.a].length);
|
|
115
|
-
const base = totals.find(x=>x.a==='nocg') ?? totals[0];
|
|
116
|
-
const bn = base?.a ?? '?';
|
|
117
|
-
console.log(` COST (vs ${bn}):`);
|
|
118
|
-
for (const x of totals) {
|
|
119
|
-
const vs = base && base.c ? ` (${((x.c/base.c-1)*100>=0?'+':'')}${((x.c/base.c-1)*100).toFixed(0)}%)` : '';
|
|
120
|
-
console.log(` ${x.a.padEnd(8)} ${d(x.c)}${vs}`);
|
|
121
|
-
}
|
|
122
|
-
console.log(` TOKENS (vs ${bn}):`);
|
|
123
|
-
for (const x of totals) {
|
|
124
|
-
const vs = base && base.t ? ` (${((x.t/base.t-1)*100>=0?'+':'')}${((x.t/base.t-1)*100).toFixed(0)}%)` : '';
|
|
125
|
-
console.log(` ${x.a.padEnd(8)} ${k(x.t)}${vs}`);
|
|
126
|
-
}
|
|
127
|
-
console.log(` DURATION (wall-clock, vs ${bn}):`);
|
|
128
|
-
const durs = ARMS.map(a => ({ a, s: mean(byArm[a].filter(r=>r.durationSec!=null), r=>r.durationSec) })).filter(x=>byArm[x.a].length);
|
|
129
|
-
const dbase = durs.find(x=>x.a==='nocg') ?? durs[0];
|
|
130
|
-
for (const x of durs) {
|
|
131
|
-
const vs = dbase && dbase.s ? ` (${((x.s/dbase.s-1)*100>=0?'+':'')}${((x.s/dbase.s-1)*100).toFixed(0)}%)` : '';
|
|
132
|
-
console.log(` ${x.a.padEnd(8)} ${x.s.toFixed(0)}s${vs}`);
|
|
133
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Cost/token analysis for the 3-arm offload eval, with a MAIN-vs-SUBAGENT split.
|
|
3
|
+
//
|
|
4
|
+
// The explore-subagent question. With delegation ALLOWED, the nocg arm spawns a
|
|
5
|
+
// Claude Code Explore subagent; the homegraph arms do all work in the main agent.
|
|
6
|
+
// Two facts make naive accounting wrong:
|
|
7
|
+
// 1. The Explore subagent runs on HAIKU 4.5; the main agent on SONNET 4.6.
|
|
8
|
+
// So per-token cost differs ~3x between them — you cannot price both the same.
|
|
9
|
+
// 2. The subagent's consumption is ~95% cache-reads. At Haiku's $0.10/MTok
|
|
10
|
+
// cache-read rate, a huge TOKEN volume is a small DOLLAR cost.
|
|
11
|
+
//
|
|
12
|
+
// Rather than re-derive cost from raw token counts (and guess the cache TTL —
|
|
13
|
+
// Claude Code uses 1-hour ephemeral cache here, 2x write, not 5-min), we read
|
|
14
|
+
// Claude Code's OWN authoritative accounting from the `result` event:
|
|
15
|
+
// result.modelUsage[model].costUSD — per-model cost CC itself billed
|
|
16
|
+
// result.total_cost_usd — their sum (INCLUDES the Haiku subagent;
|
|
17
|
+
// the handoff's "excludes subagent" was wrong)
|
|
18
|
+
// The model split IS the agent split here: sonnet => main, haiku => Explore subagent
|
|
19
|
+
// (only nocg spawns one, and only nocg shows haiku usage). Token volume is still
|
|
20
|
+
// summed per-model from modelUsage for the separate "tokens" story.
|
|
21
|
+
//
|
|
22
|
+
// Usage: offload-eval-cost.mjs <runs-dir> <repo> [reps]
|
|
23
|
+
// e.g. offload-eval-cost.mjs /tmp/cg-offload-eval/runs trezor 3
|
|
24
|
+
import { readFileSync, existsSync } from 'fs';
|
|
25
|
+
|
|
26
|
+
const MAIN_TIER = /sonnet/; // main agent
|
|
27
|
+
const SUB_TIER = /haiku/; // Claude Code Explore subagent
|
|
28
|
+
|
|
29
|
+
const [,, runsDir, repo, repsArg] = process.argv;
|
|
30
|
+
if (!runsDir || !repo) { console.error('usage: offload-eval-cost.mjs <runs-dir> <repo> [reps] (env ARMS=nocg,raw,offload)'); process.exit(1); }
|
|
31
|
+
const REPS = Number(repsArg || 3);
|
|
32
|
+
// Arms to analyze (file stems `<repo>-<arm>-<rep>.jsonl`). Override for the style A/B:
|
|
33
|
+
// ARMS=raw,refs,map,src. nocg's Haiku subagent is the only sub-tier; the rest are main-only.
|
|
34
|
+
const ARMS = (process.env.ARMS || 'nocg,raw,offload').split(',').map((s) => s.trim()).filter(Boolean);
|
|
35
|
+
|
|
36
|
+
const toks = (u) => (u.inputTokens||0)+(u.outputTokens||0)+(u.cacheReadInputTokens||0)+(u.cacheCreationInputTokens||0);
|
|
37
|
+
|
|
38
|
+
function analyzeRun(file) {
|
|
39
|
+
let result = null, agentCalls = 0;
|
|
40
|
+
const tools = {}, subPids = new Set();
|
|
41
|
+
for (const line of readFileSync(file, 'utf8').split('\n')) {
|
|
42
|
+
if (!line) continue;
|
|
43
|
+
let e; try { e = JSON.parse(line); } catch { continue; }
|
|
44
|
+
if (e.parent_tool_use_id && e.message?.usage) subPids.add(e.parent_tool_use_id);
|
|
45
|
+
if (e.type === 'assistant' && Array.isArray(e.message?.content))
|
|
46
|
+
for (const b of e.message.content)
|
|
47
|
+
if (b.type === 'tool_use') { tools[b.name] = (tools[b.name]||0)+1; if (b.name === 'Agent') agentCalls++; }
|
|
48
|
+
if (e.type === 'result') result = e;
|
|
49
|
+
}
|
|
50
|
+
// Authoritative cost + tokens from Claude Code's per-model accounting.
|
|
51
|
+
const mu = result?.modelUsage || {};
|
|
52
|
+
const main = { cost: 0, tok: 0 }, sub = { cost: 0, tok: 0 };
|
|
53
|
+
for (const [model, u] of Object.entries(mu)) {
|
|
54
|
+
const bucket = SUB_TIER.test(model) ? sub : main; // sonnet/anything-else => main
|
|
55
|
+
bucket.cost += u.costUSD || 0;
|
|
56
|
+
bucket.tok += toks(u);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
main, sub, subagents: subPids.size, agentCalls,
|
|
60
|
+
ccTotal: result?.total_cost_usd ?? null,
|
|
61
|
+
ok: result?.subtype === 'success',
|
|
62
|
+
durationSec: result?.duration_ms ? +(result.duration_ms/1000).toFixed(1) : null,
|
|
63
|
+
models: Object.keys(mu), tools,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const k = (n) => (n/1000).toFixed(0).padStart(5) + 'K';
|
|
68
|
+
const d = (n) => '$' + n.toFixed(3);
|
|
69
|
+
const cost = (b) => b.cost;
|
|
70
|
+
const tot = (b) => b.tok;
|
|
71
|
+
|
|
72
|
+
const byArm = {};
|
|
73
|
+
for (const arm of ARMS) {
|
|
74
|
+
const runs = [];
|
|
75
|
+
for (let r = 1; r <= REPS; r++) {
|
|
76
|
+
const f = `${runsDir}/${repo}-${arm}-${r}.jsonl`;
|
|
77
|
+
if (existsSync(f)) runs.push({ rep: r, ...analyzeRun(f) });
|
|
78
|
+
}
|
|
79
|
+
byArm[arm] = runs;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Per-run detail. Cost is Claude Code's own modelUsage.costUSD (authoritative,
|
|
83
|
+
// per-model pricing + correct cache TTL). MAIN=Sonnet, SUB=Haiku Explore subagent.
|
|
84
|
+
// cc-check: main$+sub$ must equal result.total_cost_usd (delta should be ~0).
|
|
85
|
+
console.log(`\n=== ${repo}: per-run main(Sonnet)/sub(Haiku) split — Claude Code's own cost accounting ===`);
|
|
86
|
+
console.log('arm rep | subAg | MAIN(sonnet) tok / $ | SUB(haiku) tok / $ | TOTAL tok / $ | cc_total Δ | dur reads');
|
|
87
|
+
for (const arm of ARMS) for (const r of byArm[arm]) {
|
|
88
|
+
const mC = cost(r.main), sC = cost(r.sub), mT = tot(r.main), sT = tot(r.sub);
|
|
89
|
+
const reads = r.tools['Read'] || 0, grep = (r.tools['Grep']||0)+(r.tools['Bash']||0)+(r.tools['Glob']||0);
|
|
90
|
+
const explore = r.tools['mcp__homegraph__homegraph_explore'] || 0;
|
|
91
|
+
const delta = (mC + sC) - (r.ccTotal || 0); // should be ~0
|
|
92
|
+
console.log(
|
|
93
|
+
`${arm.padEnd(8)} #${r.rep} | ${String(r.subagents).padStart(2)} | ${k(mT)} ${d(mC).padStart(7)} | ${k(sT)} ${d(sC).padStart(7)} | ${k(mT+sT)} ${d(mC+sC).padStart(7)} | ${d(r.ccTotal||0).padStart(7)} ${(delta>=0?'+':'')+delta.toFixed(4)} | ${String(r.durationSec).padStart(5)} r=${reads} g=${grep} x=${explore}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Per-arm means
|
|
98
|
+
const mean = (arr, f) => arr.length ? arr.reduce((s,x)=>s+f(x),0)/arr.length : 0;
|
|
99
|
+
console.log(`\n=== ${repo}: per-arm MEANS (n per arm) ===`);
|
|
100
|
+
console.log('arm n | main $ sub $ TOTAL $ | main tok sub tok TOTAL tok | %$ in sub | %tok in sub');
|
|
101
|
+
for (const arm of ARMS) {
|
|
102
|
+
const runs = byArm[arm]; if (!runs.length) continue;
|
|
103
|
+
const mC = mean(runs, r=>cost(r.main)), sC = mean(runs, r=>cost(r.sub));
|
|
104
|
+
const mT = mean(runs, r=>tot(r.main)), sT = mean(runs, r=>tot(r.sub));
|
|
105
|
+
const pctSubC = (mC+sC) ? (100*sC/(mC+sC)) : 0;
|
|
106
|
+
const pctSubT = (mT+sT) ? (100*sT/(mT+sT)) : 0;
|
|
107
|
+
console.log(
|
|
108
|
+
`${arm.padEnd(8)} ${runs.length} | ${d(mC).padStart(7)} ${d(sC).padStart(7)} ${d(mC+sC).padStart(7)} | ${k(mT)} ${k(sT)} ${k(mT+sT)} | ${pctSubC.toFixed(0).padStart(3)}% | ${pctSubT.toFixed(0).padStart(3)}%`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Headline ladders — cost, tokens, duration, all vs a baseline (nocg if present, else first arm).
|
|
113
|
+
console.log(`\n=== Ladders (mean, incl. subagent) ===`);
|
|
114
|
+
const totals = ARMS.map(a => ({ a, c: mean(byArm[a], r=>cost(r.main)+cost(r.sub)), t: mean(byArm[a], r=>tot(r.main)+tot(r.sub)) })).filter(x=>byArm[x.a].length);
|
|
115
|
+
const base = totals.find(x=>x.a==='nocg') ?? totals[0];
|
|
116
|
+
const bn = base?.a ?? '?';
|
|
117
|
+
console.log(` COST (vs ${bn}):`);
|
|
118
|
+
for (const x of totals) {
|
|
119
|
+
const vs = base && base.c ? ` (${((x.c/base.c-1)*100>=0?'+':'')}${((x.c/base.c-1)*100).toFixed(0)}%)` : '';
|
|
120
|
+
console.log(` ${x.a.padEnd(8)} ${d(x.c)}${vs}`);
|
|
121
|
+
}
|
|
122
|
+
console.log(` TOKENS (vs ${bn}):`);
|
|
123
|
+
for (const x of totals) {
|
|
124
|
+
const vs = base && base.t ? ` (${((x.t/base.t-1)*100>=0?'+':'')}${((x.t/base.t-1)*100).toFixed(0)}%)` : '';
|
|
125
|
+
console.log(` ${x.a.padEnd(8)} ${k(x.t)}${vs}`);
|
|
126
|
+
}
|
|
127
|
+
console.log(` DURATION (wall-clock, vs ${bn}):`);
|
|
128
|
+
const durs = ARMS.map(a => ({ a, s: mean(byArm[a].filter(r=>r.durationSec!=null), r=>r.durationSec) })).filter(x=>byArm[x.a].length);
|
|
129
|
+
const dbase = durs.find(x=>x.a==='nocg') ?? durs[0];
|
|
130
|
+
for (const x of durs) {
|
|
131
|
+
const vs = dbase && dbase.s ? ` (${((x.s/dbase.s-1)*100>=0?'+':'')}${((x.s/dbase.s-1)*100).toFixed(0)}%)` : '';
|
|
132
|
+
console.log(` ${x.a.padEnd(8)} ${x.s.toFixed(0)}s${vs}`);
|
|
133
|
+
}
|
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Effort A/B — does HOMEGRAPH_OFFLOAD_EFFORT=high improve offload SYNTHESIS FIDELITY vs low?
|
|
3
|
-
// Probe-based (no agent): for each repo × effort × rep, run homegraph_explore with the offload
|
|
4
|
-
// ON on the canonical question, capture the synthesized answer + AI tokens/cost/latency, then
|
|
5
|
-
// Sonnet-judge that answer's fidelity vs source-verified ground truth. Isolates the synthesis
|
|
6
|
-
// from agent/adoption noise. Requires `homegraph login` (managed offload) + indexed repos.
|
|
7
|
-
//
|
|
8
|
-
// Env: REPS (default 3) · CG_ENGINE (engine repo) · AGENT_EVAL_OUT (repos under /repos) · CONC (judge concurrency)
|
|
9
|
-
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
10
|
-
import { resolve, dirname, join } from 'node:path';
|
|
11
|
-
import { readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
|
12
|
-
import { execFile } from 'node:child_process';
|
|
13
|
-
import { tmpdir } from 'node:os';
|
|
14
|
-
|
|
15
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
const ENGINE = process.env.CG_ENGINE || resolve(HERE, '..', '..');
|
|
17
|
-
const OUT = process.env.AGENT_EVAL_OUT || '/tmp/cg-offload-eval';
|
|
18
|
-
const REPOS = join(OUT, 'repos');
|
|
19
|
-
const GT = JSON.parse(readFileSync(resolve(HERE, 'offload-eval-ground-truth.json'), 'utf8'));
|
|
20
|
-
const REPS = Number(process.env.REPS || 3);
|
|
21
|
-
const CONC = Number(process.env.CONC || 4);
|
|
22
|
-
const EFFORTS = (process.env.EFFORTS_FILTER || 'low,high').split(',');
|
|
23
|
-
const ONLY = process.env.REPOS_FILTER ? new Set(process.env.REPOS_FILTER.split(',')) : null;
|
|
24
|
-
const TIER = { mtkruto: 'small', postybirb: 'medium', shapeshift: 'complex', trezor: 'large' };
|
|
25
|
-
|
|
26
|
-
const load = async (rel) => import(pathToFileURL(resolve(ENGINE, rel)).href);
|
|
27
|
-
const idx = await load('dist/index.js');
|
|
28
|
-
const toolsMod = await load('dist/mcp/tools.js');
|
|
29
|
-
const HomeGraph = idx.default?.default ?? idx.default ?? idx.HomeGraph;
|
|
30
|
-
const ToolHandler = toolsMod.ToolHandler ?? toolsMod.default?.ToolHandler;
|
|
31
|
-
if (typeof HomeGraph?.openSync !== 'function' || typeof ToolHandler !== 'function') {
|
|
32
|
-
console.error('could not load engine from', ENGINE); process.exit(2);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const fidPrompt = (gt, ans) => `You are scoring the FIDELITY of a machine-synthesized code-exploration answer against verified ground truth. Do NOT use any tools.
|
|
36
|
-
|
|
37
|
-
QUESTION: ${gt.question}
|
|
38
|
-
|
|
39
|
-
VERIFIED GROUND TRUTH (the actual call path + files):
|
|
40
|
-
${gt.truth}
|
|
41
|
-
|
|
42
|
-
SYNTHESIZED ANSWER (to score):
|
|
43
|
-
${ans || '(empty)'}
|
|
44
|
-
|
|
45
|
-
Judge: (1) is the traced call path correct vs ground truth? (2) are the cited files/symbols correct (not fabricated)? (3) if it gave a "Coverage:" verdict, was it honest? A confident WRONG trace is the worst outcome — penalize it harder than an honest partial.
|
|
46
|
-
Output ONLY minified JSON: {"verdict":"pass|partial|fail","score":<0-100>,"fabrication":<true|false>,"coverageHonest":<true|false>,"note":"<=20 words"}`;
|
|
47
|
-
|
|
48
|
-
const askJudge = (prompt) => new Promise((res) => {
|
|
49
|
-
execFile('claude', ['-p', prompt, '--model', 'sonnet', '--effort', 'high', '--max-budget-usd', '0.5',
|
|
50
|
-
'--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}'],
|
|
51
|
-
{ cwd: OUT, maxBuffer: 1 << 24, timeout: 120000 }, (err, stdout) => {
|
|
52
|
-
const m = (stdout || '').match(/\{[\s\S]*\}/);
|
|
53
|
-
if (!m) return res({ verdict: 'error', score: null, note: (err ? err.message : 'no json').slice(0, 60) });
|
|
54
|
-
try { res(JSON.parse(m[0])); } catch { res({ verdict: 'error', score: null }); }
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ---- 1. Probe: collect synthesized answers at each effort -------------------
|
|
59
|
-
const records = [];
|
|
60
|
-
for (const repo of Object.keys(GT)) {
|
|
61
|
-
if (ONLY && !ONLY.has(repo)) continue;
|
|
62
|
-
const dir = join(REPOS, repo);
|
|
63
|
-
if (!existsSync(join(dir, '.homegraph'))) { console.error('skip (not indexed):', repo); continue; }
|
|
64
|
-
const cg = HomeGraph.openSync(dir);
|
|
65
|
-
const h = new ToolHandler(cg);
|
|
66
|
-
for (const effort of EFFORTS) {
|
|
67
|
-
for (let rep = 1; rep <= REPS; rep++) {
|
|
68
|
-
process.env.HOMEGRAPH_OFFLOAD_EFFORT = effort;
|
|
69
|
-
const usageLog = join(tmpdir(), `effort-${repo}-${effort}-${rep}.jsonl`);
|
|
70
|
-
try { rmSync(usageLog); } catch { /* none */ }
|
|
71
|
-
process.env.HOMEGRAPH_OFFLOAD_USAGE_LOG = usageLog;
|
|
72
|
-
let answer = '';
|
|
73
|
-
try { answer = (await h.execute('homegraph_explore', { query: GT[repo].question }))?.content?.[0]?.text ?? ''; }
|
|
74
|
-
catch (e) { console.error(` ${repo}/${effort}#${rep} explore failed: ${e?.message}`); }
|
|
75
|
-
const fired = /Synthesized by HomeGraph/.test(answer);
|
|
76
|
-
const ai = { tokens: 0, cost: 0, ms: 0 };
|
|
77
|
-
if (existsSync(usageLog)) for (const e of readFileSync(usageLog, 'utf8').split('\n').filter(Boolean).map(JSON.parse)) {
|
|
78
|
-
ai.tokens += e.totalTokens || 0; ai.cost += e.costUsd || 0; ai.ms += e.ms || 0;
|
|
79
|
-
}
|
|
80
|
-
records.push({ repo, tier: TIER[repo], effort, rep, fired, ai, answer });
|
|
81
|
-
console.error(` ${repo}/${effort}#${rep}: fired=${fired} ${ai.tokens}tok $${ai.cost.toFixed(4)} ${ai.ms}ms`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
try { cg.close?.(); } catch { /* none */ }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ---- 2. Judge fidelity (concurrency) ---------------------------------------
|
|
88
|
-
console.error(`\njudging ${records.length} answers (concurrency ${CONC})...`);
|
|
89
|
-
let done = 0;
|
|
90
|
-
const q = [...records];
|
|
91
|
-
async function worker() { while (q.length) { const r = q.shift(); r.fid = await askJudge(fidPrompt(GT[r.repo], r.answer)); console.error(` [${++done}/${records.length}] ${r.repo}/${r.effort}#${r.rep}: ${r.fid.verdict} ${r.fid.score ?? ''}`); } }
|
|
92
|
-
await Promise.all(Array.from({ length: CONC }, worker));
|
|
93
|
-
writeFileSync(join(OUT, 'effort-results.jsonl'), records.map((r) => JSON.stringify(r)).join('\n') + '\n');
|
|
94
|
-
|
|
95
|
-
// ---- 3. Aggregate: low vs high per repo ------------------------------------
|
|
96
|
-
const med = (a) => { a = a.filter((x) => x != null).sort((x, y) => x - y); return a.length ? (a.length % 2 ? a[(a.length - 1) / 2] : (a[a.length / 2 - 1] + a[a.length / 2]) / 2) : null; };
|
|
97
|
-
console.log(`\n${'='.repeat(80)}\nEFFORT A/B — offload synthesis fidelity (probe, n=${REPS}/cell)\n${'='.repeat(80)}`);
|
|
98
|
-
console.log(`${'repo'.padEnd(11)} ${'tier'.padEnd(8)} ${'effort'.padEnd(6)} fired ${'fid(med)'.padStart(8)} ${'fab%'.padStart(5)} ${'AItok'.padStart(7)} ${'AIcost'.padStart(8)} ${'ms(med)'.padStart(8)}`);
|
|
99
|
-
for (const repo of Object.keys(GT)) {
|
|
100
|
-
for (const effort of EFFORTS) {
|
|
101
|
-
const rs = records.filter((r) => r.repo === repo && r.effort === effort);
|
|
102
|
-
if (!rs.length) continue;
|
|
103
|
-
const fids = rs.map((r) => r.fid?.score).filter((x) => x != null);
|
|
104
|
-
const fab = rs.filter((r) => r.fid?.fabrication === true).length;
|
|
105
|
-
console.log(`${repo.padEnd(11)} ${TIER[repo].padEnd(8)} ${effort.padEnd(6)} ${rs.filter((r) => r.fired).length}/${rs.length} ${String(med(fids) ?? '—').padStart(8)} ${String(Math.round(100 * fab / rs.length) + '%').padStart(5)} ${String(Math.round(med(rs.map((r) => r.ai.tokens)) / 1000) + 'k').padStart(7)} ${('$' + (med(rs.map((r) => r.ai.cost)) ?? 0).toFixed(4)).padStart(8)} ${String(med(rs.map((r) => r.ai.ms)) ?? '—').padStart(8)}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
console.log('');
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Effort A/B — does HOMEGRAPH_OFFLOAD_EFFORT=high improve offload SYNTHESIS FIDELITY vs low?
|
|
3
|
+
// Probe-based (no agent): for each repo × effort × rep, run homegraph_explore with the offload
|
|
4
|
+
// ON on the canonical question, capture the synthesized answer + AI tokens/cost/latency, then
|
|
5
|
+
// Sonnet-judge that answer's fidelity vs source-verified ground truth. Isolates the synthesis
|
|
6
|
+
// from agent/adoption noise. Requires `homegraph login` (managed offload) + indexed repos.
|
|
7
|
+
//
|
|
8
|
+
// Env: REPS (default 3) · CG_ENGINE (engine repo) · AGENT_EVAL_OUT (repos under /repos) · CONC (judge concurrency)
|
|
9
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
10
|
+
import { resolve, dirname, join } from 'node:path';
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
|
12
|
+
import { execFile } from 'node:child_process';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
|
|
15
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const ENGINE = process.env.CG_ENGINE || resolve(HERE, '..', '..');
|
|
17
|
+
const OUT = process.env.AGENT_EVAL_OUT || '/tmp/cg-offload-eval';
|
|
18
|
+
const REPOS = join(OUT, 'repos');
|
|
19
|
+
const GT = JSON.parse(readFileSync(resolve(HERE, 'offload-eval-ground-truth.json'), 'utf8'));
|
|
20
|
+
const REPS = Number(process.env.REPS || 3);
|
|
21
|
+
const CONC = Number(process.env.CONC || 4);
|
|
22
|
+
const EFFORTS = (process.env.EFFORTS_FILTER || 'low,high').split(',');
|
|
23
|
+
const ONLY = process.env.REPOS_FILTER ? new Set(process.env.REPOS_FILTER.split(',')) : null;
|
|
24
|
+
const TIER = { mtkruto: 'small', postybirb: 'medium', shapeshift: 'complex', trezor: 'large' };
|
|
25
|
+
|
|
26
|
+
const load = async (rel) => import(pathToFileURL(resolve(ENGINE, rel)).href);
|
|
27
|
+
const idx = await load('dist/index.js');
|
|
28
|
+
const toolsMod = await load('dist/mcp/tools.js');
|
|
29
|
+
const HomeGraph = idx.default?.default ?? idx.default ?? idx.HomeGraph;
|
|
30
|
+
const ToolHandler = toolsMod.ToolHandler ?? toolsMod.default?.ToolHandler;
|
|
31
|
+
if (typeof HomeGraph?.openSync !== 'function' || typeof ToolHandler !== 'function') {
|
|
32
|
+
console.error('could not load engine from', ENGINE); process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const fidPrompt = (gt, ans) => `You are scoring the FIDELITY of a machine-synthesized code-exploration answer against verified ground truth. Do NOT use any tools.
|
|
36
|
+
|
|
37
|
+
QUESTION: ${gt.question}
|
|
38
|
+
|
|
39
|
+
VERIFIED GROUND TRUTH (the actual call path + files):
|
|
40
|
+
${gt.truth}
|
|
41
|
+
|
|
42
|
+
SYNTHESIZED ANSWER (to score):
|
|
43
|
+
${ans || '(empty)'}
|
|
44
|
+
|
|
45
|
+
Judge: (1) is the traced call path correct vs ground truth? (2) are the cited files/symbols correct (not fabricated)? (3) if it gave a "Coverage:" verdict, was it honest? A confident WRONG trace is the worst outcome — penalize it harder than an honest partial.
|
|
46
|
+
Output ONLY minified JSON: {"verdict":"pass|partial|fail","score":<0-100>,"fabrication":<true|false>,"coverageHonest":<true|false>,"note":"<=20 words"}`;
|
|
47
|
+
|
|
48
|
+
const askJudge = (prompt) => new Promise((res) => {
|
|
49
|
+
execFile('claude', ['-p', prompt, '--model', 'sonnet', '--effort', 'high', '--max-budget-usd', '0.5',
|
|
50
|
+
'--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}'],
|
|
51
|
+
{ cwd: OUT, maxBuffer: 1 << 24, timeout: 120000 }, (err, stdout) => {
|
|
52
|
+
const m = (stdout || '').match(/\{[\s\S]*\}/);
|
|
53
|
+
if (!m) return res({ verdict: 'error', score: null, note: (err ? err.message : 'no json').slice(0, 60) });
|
|
54
|
+
try { res(JSON.parse(m[0])); } catch { res({ verdict: 'error', score: null }); }
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ---- 1. Probe: collect synthesized answers at each effort -------------------
|
|
59
|
+
const records = [];
|
|
60
|
+
for (const repo of Object.keys(GT)) {
|
|
61
|
+
if (ONLY && !ONLY.has(repo)) continue;
|
|
62
|
+
const dir = join(REPOS, repo);
|
|
63
|
+
if (!existsSync(join(dir, '.homegraph'))) { console.error('skip (not indexed):', repo); continue; }
|
|
64
|
+
const cg = HomeGraph.openSync(dir);
|
|
65
|
+
const h = new ToolHandler(cg);
|
|
66
|
+
for (const effort of EFFORTS) {
|
|
67
|
+
for (let rep = 1; rep <= REPS; rep++) {
|
|
68
|
+
process.env.HOMEGRAPH_OFFLOAD_EFFORT = effort;
|
|
69
|
+
const usageLog = join(tmpdir(), `effort-${repo}-${effort}-${rep}.jsonl`);
|
|
70
|
+
try { rmSync(usageLog); } catch { /* none */ }
|
|
71
|
+
process.env.HOMEGRAPH_OFFLOAD_USAGE_LOG = usageLog;
|
|
72
|
+
let answer = '';
|
|
73
|
+
try { answer = (await h.execute('homegraph_explore', { query: GT[repo].question }))?.content?.[0]?.text ?? ''; }
|
|
74
|
+
catch (e) { console.error(` ${repo}/${effort}#${rep} explore failed: ${e?.message}`); }
|
|
75
|
+
const fired = /Synthesized by HomeGraph/.test(answer);
|
|
76
|
+
const ai = { tokens: 0, cost: 0, ms: 0 };
|
|
77
|
+
if (existsSync(usageLog)) for (const e of readFileSync(usageLog, 'utf8').split('\n').filter(Boolean).map(JSON.parse)) {
|
|
78
|
+
ai.tokens += e.totalTokens || 0; ai.cost += e.costUsd || 0; ai.ms += e.ms || 0;
|
|
79
|
+
}
|
|
80
|
+
records.push({ repo, tier: TIER[repo], effort, rep, fired, ai, answer });
|
|
81
|
+
console.error(` ${repo}/${effort}#${rep}: fired=${fired} ${ai.tokens}tok $${ai.cost.toFixed(4)} ${ai.ms}ms`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
try { cg.close?.(); } catch { /* none */ }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---- 2. Judge fidelity (concurrency) ---------------------------------------
|
|
88
|
+
console.error(`\njudging ${records.length} answers (concurrency ${CONC})...`);
|
|
89
|
+
let done = 0;
|
|
90
|
+
const q = [...records];
|
|
91
|
+
async function worker() { while (q.length) { const r = q.shift(); r.fid = await askJudge(fidPrompt(GT[r.repo], r.answer)); console.error(` [${++done}/${records.length}] ${r.repo}/${r.effort}#${r.rep}: ${r.fid.verdict} ${r.fid.score ?? ''}`); } }
|
|
92
|
+
await Promise.all(Array.from({ length: CONC }, worker));
|
|
93
|
+
writeFileSync(join(OUT, 'effort-results.jsonl'), records.map((r) => JSON.stringify(r)).join('\n') + '\n');
|
|
94
|
+
|
|
95
|
+
// ---- 3. Aggregate: low vs high per repo ------------------------------------
|
|
96
|
+
const med = (a) => { a = a.filter((x) => x != null).sort((x, y) => x - y); return a.length ? (a.length % 2 ? a[(a.length - 1) / 2] : (a[a.length / 2 - 1] + a[a.length / 2]) / 2) : null; };
|
|
97
|
+
console.log(`\n${'='.repeat(80)}\nEFFORT A/B — offload synthesis fidelity (probe, n=${REPS}/cell)\n${'='.repeat(80)}`);
|
|
98
|
+
console.log(`${'repo'.padEnd(11)} ${'tier'.padEnd(8)} ${'effort'.padEnd(6)} fired ${'fid(med)'.padStart(8)} ${'fab%'.padStart(5)} ${'AItok'.padStart(7)} ${'AIcost'.padStart(8)} ${'ms(med)'.padStart(8)}`);
|
|
99
|
+
for (const repo of Object.keys(GT)) {
|
|
100
|
+
for (const effort of EFFORTS) {
|
|
101
|
+
const rs = records.filter((r) => r.repo === repo && r.effort === effort);
|
|
102
|
+
if (!rs.length) continue;
|
|
103
|
+
const fids = rs.map((r) => r.fid?.score).filter((x) => x != null);
|
|
104
|
+
const fab = rs.filter((r) => r.fid?.fabrication === true).length;
|
|
105
|
+
console.log(`${repo.padEnd(11)} ${TIER[repo].padEnd(8)} ${effort.padEnd(6)} ${rs.filter((r) => r.fired).length}/${rs.length} ${String(med(fids) ?? '—').padStart(8)} ${String(Math.round(100 * fab / rs.length) + '%').padStart(5)} ${String(Math.round(med(rs.map((r) => r.ai.tokens)) / 1000) + 'k').padStart(7)} ${('$' + (med(rs.map((r) => r.ai.cost)) ?? 0).toFixed(4)).padStart(8)} ${String(med(rs.map((r) => r.ai.ms)) ?? '—').padStart(8)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
console.log('');
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Run the FRONTLOAD arm across all 4 tiers (n reps), then judge + merge with the existing
|
|
3
|
-
# matrix (offload/raw/nocg in $OUT/judged.jsonl, if present) + emit a combined summary.
|
|
4
|
-
# Env: REPS (default 3) AGENT_EVAL_OUT=<scratch dir>
|
|
5
|
-
set -uo pipefail
|
|
6
|
-
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
OUT="${AGENT_EVAL_OUT:-/tmp/cg-offload-eval}"
|
|
8
|
-
GT="$HERE/offload-eval-ground-truth.json"
|
|
9
|
-
REPS="${REPS:-3}"
|
|
10
|
-
export RESULTS="$OUT/results-fl.jsonl"
|
|
11
|
-
: > "$RESULTS"; rm -f "$OUT/runs/hook-debug.log"
|
|
12
|
-
for repo in mtkruto postybirb shapeshift trezor; do
|
|
13
|
-
case "$repo" in mtkruto) tier=small;; postybirb) tier=medium;; shapeshift) tier=complex;; trezor) tier=large;; esac
|
|
14
|
-
Q=$(node -e "console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))[process.argv[2]].question)" "$GT" "$repo")
|
|
15
|
-
echo ""; echo "### $repo ($tier) $(date +%H:%M:%S)"
|
|
16
|
-
bash "$HERE/offload-eval-frontload.sh" "$OUT/repos/$repo" "$tier" "$REPS" "$Q"
|
|
17
|
-
done
|
|
18
|
-
echo ""
|
|
19
|
-
echo "frontload: $(wc -l < "$RESULTS") runs | hook injections: $(grep -c INJECTED "$OUT/runs/hook-debug.log" 2>/dev/null) | errors: $(grep -c ERROR "$OUT/runs/hook-debug.log" 2>/dev/null)"
|
|
20
|
-
echo "=== JUDGE frontload ==="
|
|
21
|
-
node "$HERE/offload-eval-judge.mjs" --results "$RESULTS" --truth "$GT" --out "$OUT/judged-fl.jsonl" --concurrency 4 2>&1 | tail -4
|
|
22
|
-
if [ -f "$OUT/judged.jsonl" ]; then cat "$OUT/judged.jsonl" "$OUT/judged-fl.jsonl" > "$OUT/judged-all.jsonl"; else cp "$OUT/judged-fl.jsonl" "$OUT/judged-all.jsonl"; fi
|
|
23
|
-
echo "=== COMBINED SUMMARY ==="
|
|
24
|
-
node "$HERE/offload-eval-summarize.mjs" "$OUT/judged-all.jsonl"
|
|
25
|
-
echo "###### FRONTLOAD MATRIX DONE"
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Run the FRONTLOAD arm across all 4 tiers (n reps), then judge + merge with the existing
|
|
3
|
+
# matrix (offload/raw/nocg in $OUT/judged.jsonl, if present) + emit a combined summary.
|
|
4
|
+
# Env: REPS (default 3) AGENT_EVAL_OUT=<scratch dir>
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
OUT="${AGENT_EVAL_OUT:-/tmp/cg-offload-eval}"
|
|
8
|
+
GT="$HERE/offload-eval-ground-truth.json"
|
|
9
|
+
REPS="${REPS:-3}"
|
|
10
|
+
export RESULTS="$OUT/results-fl.jsonl"
|
|
11
|
+
: > "$RESULTS"; rm -f "$OUT/runs/hook-debug.log"
|
|
12
|
+
for repo in mtkruto postybirb shapeshift trezor; do
|
|
13
|
+
case "$repo" in mtkruto) tier=small;; postybirb) tier=medium;; shapeshift) tier=complex;; trezor) tier=large;; esac
|
|
14
|
+
Q=$(node -e "console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))[process.argv[2]].question)" "$GT" "$repo")
|
|
15
|
+
echo ""; echo "### $repo ($tier) $(date +%H:%M:%S)"
|
|
16
|
+
bash "$HERE/offload-eval-frontload.sh" "$OUT/repos/$repo" "$tier" "$REPS" "$Q"
|
|
17
|
+
done
|
|
18
|
+
echo ""
|
|
19
|
+
echo "frontload: $(wc -l < "$RESULTS") runs | hook injections: $(grep -c INJECTED "$OUT/runs/hook-debug.log" 2>/dev/null) | errors: $(grep -c ERROR "$OUT/runs/hook-debug.log" 2>/dev/null)"
|
|
20
|
+
echo "=== JUDGE frontload ==="
|
|
21
|
+
node "$HERE/offload-eval-judge.mjs" --results "$RESULTS" --truth "$GT" --out "$OUT/judged-fl.jsonl" --concurrency 4 2>&1 | tail -4
|
|
22
|
+
if [ -f "$OUT/judged.jsonl" ]; then cat "$OUT/judged.jsonl" "$OUT/judged-fl.jsonl" > "$OUT/judged-all.jsonl"; else cp "$OUT/judged-fl.jsonl" "$OUT/judged-all.jsonl"; fi
|
|
23
|
+
echo "=== COMBINED SUMMARY ==="
|
|
24
|
+
node "$HERE/offload-eval-summarize.mjs" "$OUT/judged-all.jsonl"
|
|
25
|
+
echo "###### FRONTLOAD MATRIX DONE"
|