homegraph 1.1.2 → 1.1.3
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 +243 -243
- package/dist/bin/homegraph.js +371 -9
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/db/migrations.js +18 -18
- package/dist/db/queries.js +140 -140
- package/dist/db/schema.sql +152 -152
- package/dist/directory.js +5 -5
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- 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/liveness-watchdog.js +16 -16
- package/dist/mcp/server-instructions.js +69 -69
- package/dist/mcp/tools.d.ts +17 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +192 -22
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reasoning/reasoner.js +32 -32
- package/dist/spec/config.d.ts +43 -0
- package/dist/spec/config.d.ts.map +1 -0
- package/dist/spec/config.js +275 -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 +23 -0
- package/dist/spec/db/fragment-node.d.ts.map +1 -0
- package/dist/spec/db/fragment-node.js +120 -0
- package/dist/spec/db/fragment-node.js.map +1 -0
- package/dist/spec/db/fts.d.ts +60 -0
- package/dist/spec/db/fts.d.ts.map +1 -0
- package/dist/spec/db/fts.js +285 -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 +30 -0
- package/dist/spec/db/schema.d.ts.map +1 -0
- package/dist/spec/db/schema.js +87 -0
- package/dist/spec/db/schema.js.map +1 -0
- package/dist/spec/db/schema.sql +107 -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 +48 -0
- package/dist/spec/evolve/logic-checker.js.map +1 -0
- package/dist/spec/evolve/pipeline.d.ts +40 -0
- package/dist/spec/evolve/pipeline.d.ts.map +1 -0
- package/dist/spec/evolve/pipeline.js +314 -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 +254 -0
- package/dist/spec/evolve/spec-rewriter.js.map +1 -0
- package/dist/spec/graph/queries.d.ts +58 -0
- package/dist/spec/graph/queries.d.ts.map +1 -0
- package/dist/spec/graph/queries.js +215 -0
- package/dist/spec/graph/queries.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 +91 -0
- package/dist/spec/mining/git-scanner.d.ts.map +1 -0
- package/dist/spec/mining/git-scanner.js +266 -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 +165 -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 +166 -0
- package/dist/spec/utils.d.ts.map +1 -0
- package/dist/spec/utils.js +461 -0
- package/dist/spec/utils.js.map +1 -0
- 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/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/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 +246 -246
- 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,72 +1,72 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# 3-arm offload eval for ONE indexed repo + ONE question, n reps each.
|
|
3
|
-
# ARM offload : homegraph attached, managed offload ON (per-run AI usage log)
|
|
4
|
-
# ARM raw : homegraph attached, HOMEGRAPH_OFFLOAD_DISABLE=1 (raw source)
|
|
5
|
-
# ARM nocg : no homegraph (empty MCP config) -> Read/Grep baseline
|
|
6
|
-
# All arms: claude -p sonnet --effort high. One JSON metrics line/run -> $RESULTS.
|
|
7
|
-
#
|
|
8
|
-
# Usage: offload-eval-3arm.sh <indexed-repo> <tier> <reps> "<question>"
|
|
9
|
-
# Env: MODEL=sonnet EFFORT=high RESULTS=<file> AGENT_EVAL_OUT=<scratch dir>
|
|
10
|
-
set -uo pipefail
|
|
11
|
-
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
-
ENGINE="$(cd "$HERE/../.." && pwd)"
|
|
13
|
-
BIN="$ENGINE/dist/bin/homegraph.js"
|
|
14
|
-
OUT="${AGENT_EVAL_OUT:-/tmp/cg-offload-eval}"
|
|
15
|
-
TARGET="${1:?usage: offload-eval-3arm.sh <indexed-repo> <tier> <reps> \"<question>\"}"
|
|
16
|
-
TIER="${2:?tier}"; REPS="${3:?reps}"; Q="${4:?question}"
|
|
17
|
-
RUNS="$OUT/runs"
|
|
18
|
-
EXTRACT="$HERE/offload-eval-metrics.mjs"
|
|
19
|
-
RESULTS="${RESULTS:-$OUT/results.jsonl}"
|
|
20
|
-
REPO=$(basename "$TARGET")
|
|
21
|
-
mkdir -p "$RUNS"
|
|
22
|
-
command -v claude >/dev/null || { echo "no claude on PATH"; exit 1; }
|
|
23
|
-
[ -d "$TARGET/.homegraph" ] || { echo "not indexed: $TARGET (run offload-eval-setup.sh first)"; exit 1; }
|
|
24
|
-
# Physical path so pkill matches the daemon's real cmdline (macOS /tmp->/private/tmp symlink
|
|
25
|
-
# otherwise makes the kill miss the daemon, and the next arm connects to the SURVIVING daemon
|
|
26
|
-
# — contaminating the raw arm with offload).
|
|
27
|
-
TARGET=$(cd "$TARGET" && pwd -P)
|
|
28
|
-
|
|
29
|
-
prewarm() { # path extra-env (e.g. "FOO=bar")
|
|
30
|
-
pkill -9 -f "serve --mcp --path $1" 2>/dev/null; rm -f "$1/.homegraph/daemon.sock" 2>/dev/null; sleep 0.6
|
|
31
|
-
env ${2:-} HOMEGRAPH_DAEMON_IDLE_TIMEOUT_MS=1800000 node "$BIN" serve --mcp --path "$1" </dev/null >/dev/null 2>&1 &
|
|
32
|
-
node -e 'const fs=require("fs");let n=0;const t=setInterval(()=>{if(fs.existsSync(process.argv[1]+"/.homegraph/daemon.sock")){clearInterval(t);process.exit(0)}if(n++>150){clearInterval(t);process.exit(1)}},100)' "$1" \
|
|
33
|
-
&& echo " daemon warm" || echo " WARN daemon never bound"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
run() { # arm rep mcp-config usage-log-or-dash
|
|
37
|
-
local arm="$1" rep="$2" cfg="$3" usage="$4" tag="$REPO-$1-$2"
|
|
38
|
-
[ "$usage" != "-" ] && : > "$usage"
|
|
39
|
-
# DISALLOW (optional): block sub-agent delegation across all arms so the A/B
|
|
40
|
-
# measures the retrieval mode, not whether Sonnet decides to spawn a homegraph-blind
|
|
41
|
-
# Explore subagent (which thrashes regardless and adds huge variance).
|
|
42
|
-
( cd "$TARGET" && claude -p "$Q" \
|
|
43
|
-
--output-format stream-json --verbose --permission-mode bypassPermissions \
|
|
44
|
-
--model "${MODEL:-sonnet}" --effort "${EFFORT:-high}" --max-budget-usd 4 \
|
|
45
|
-
${DISALLOW:+--disallowedTools "$DISALLOW"} \
|
|
46
|
-
--strict-mcp-config --mcp-config "$cfg" \
|
|
47
|
-
</dev/null > "$RUNS/$tag.jsonl" 2>"$RUNS/$tag.err" )
|
|
48
|
-
node "$EXTRACT" --run "$RUNS/$tag.jsonl" --usage "$usage" --arm "$arm" --rep "$rep" \
|
|
49
|
-
--repo "$REPO" --tier "$TIER" --q "$Q" >> "$RESULTS"
|
|
50
|
-
node -e 'const o=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n").pop());console.log(` [${o.arm} #${o.rep}] ${o.durationSec}s | main $${o.costUsdMain} ${o.tokBillable} tok | read=${o.read} grep=${o.grep} explore=${o.explore} offload=${o.offloadFired} | AI ${o.ai.calls}call/${o.ai.totalTokens}tok/$${o.ai.costUsd.toFixed(4)} | ok=${o.ok}`)' "$RESULTS"
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
CFG_OFF="$RUNS/mcp-offload-$REPO.json"; CFG_RAW="$RUNS/mcp-raw-$REPO.json"; CFG_NOCG="$RUNS/mcp-nocg.json"
|
|
54
|
-
USAGE="$RUNS/$REPO-usage.jsonl"
|
|
55
|
-
printf '{"mcpServers":{"homegraph":{"command":"env","args":["HOMEGRAPH_WASM_RELAUNCHED=1","HOMEGRAPH_OFFLOAD_USAGE_LOG=%s","node","%s","serve","--mcp","--path","%s"]}}}' "$USAGE" "$BIN" "$TARGET" > "$CFG_OFF"
|
|
56
|
-
printf '{"mcpServers":{"homegraph":{"command":"env","args":["HOMEGRAPH_WASM_RELAUNCHED=1","HOMEGRAPH_OFFLOAD_DISABLE=1","node","%s","serve","--mcp","--path","%s"]}}}' "$BIN" "$TARGET" > "$CFG_RAW"
|
|
57
|
-
printf '{"mcpServers":{}}' > "$CFG_NOCG"
|
|
58
|
-
|
|
59
|
-
# REP_START lets a later batch ADD reps without clobbering earlier jsonls
|
|
60
|
-
# (e.g. REP_START=4 REPS=3 -> reps 4,5,6; default starts at 1).
|
|
61
|
-
START="${REP_START:-1}"; END=$((START + REPS - 1))
|
|
62
|
-
echo "###### repo=$REPO tier=$TIER reps=$START..$END model=${MODEL:-sonnet}/${EFFORT:-high}"
|
|
63
|
-
echo "###### Q=$Q"
|
|
64
|
-
echo "== ARM offload =="; prewarm "$TARGET" "HOMEGRAPH_OFFLOAD_USAGE_LOG=$USAGE"
|
|
65
|
-
for r in $(seq "$START" "$END"); do run offload "$r" "$CFG_OFF" "$USAGE"; done
|
|
66
|
-
pkill -9 -f "serve --mcp --path $TARGET" 2>/dev/null; rm -f "$TARGET/.homegraph/daemon.sock" 2>/dev/null; sleep 1
|
|
67
|
-
echo "== ARM raw =="; prewarm "$TARGET" "HOMEGRAPH_OFFLOAD_DISABLE=1"
|
|
68
|
-
for r in $(seq "$START" "$END"); do run raw "$r" "$CFG_RAW" "-"; done
|
|
69
|
-
pkill -9 -f "serve --mcp --path $TARGET" 2>/dev/null; rm -f "$TARGET/.homegraph/daemon.sock" 2>/dev/null; sleep 1
|
|
70
|
-
echo "== ARM nocg =="
|
|
71
|
-
for r in $(seq "$START" "$END"); do run nocg "$r" "$CFG_NOCG" "-"; done
|
|
72
|
-
echo "###### DONE $REPO"
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 3-arm offload eval for ONE indexed repo + ONE question, n reps each.
|
|
3
|
+
# ARM offload : homegraph attached, managed offload ON (per-run AI usage log)
|
|
4
|
+
# ARM raw : homegraph attached, HOMEGRAPH_OFFLOAD_DISABLE=1 (raw source)
|
|
5
|
+
# ARM nocg : no homegraph (empty MCP config) -> Read/Grep baseline
|
|
6
|
+
# All arms: claude -p sonnet --effort high. One JSON metrics line/run -> $RESULTS.
|
|
7
|
+
#
|
|
8
|
+
# Usage: offload-eval-3arm.sh <indexed-repo> <tier> <reps> "<question>"
|
|
9
|
+
# Env: MODEL=sonnet EFFORT=high RESULTS=<file> AGENT_EVAL_OUT=<scratch dir>
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
ENGINE="$(cd "$HERE/../.." && pwd)"
|
|
13
|
+
BIN="$ENGINE/dist/bin/homegraph.js"
|
|
14
|
+
OUT="${AGENT_EVAL_OUT:-/tmp/cg-offload-eval}"
|
|
15
|
+
TARGET="${1:?usage: offload-eval-3arm.sh <indexed-repo> <tier> <reps> \"<question>\"}"
|
|
16
|
+
TIER="${2:?tier}"; REPS="${3:?reps}"; Q="${4:?question}"
|
|
17
|
+
RUNS="$OUT/runs"
|
|
18
|
+
EXTRACT="$HERE/offload-eval-metrics.mjs"
|
|
19
|
+
RESULTS="${RESULTS:-$OUT/results.jsonl}"
|
|
20
|
+
REPO=$(basename "$TARGET")
|
|
21
|
+
mkdir -p "$RUNS"
|
|
22
|
+
command -v claude >/dev/null || { echo "no claude on PATH"; exit 1; }
|
|
23
|
+
[ -d "$TARGET/.homegraph" ] || { echo "not indexed: $TARGET (run offload-eval-setup.sh first)"; exit 1; }
|
|
24
|
+
# Physical path so pkill matches the daemon's real cmdline (macOS /tmp->/private/tmp symlink
|
|
25
|
+
# otherwise makes the kill miss the daemon, and the next arm connects to the SURVIVING daemon
|
|
26
|
+
# — contaminating the raw arm with offload).
|
|
27
|
+
TARGET=$(cd "$TARGET" && pwd -P)
|
|
28
|
+
|
|
29
|
+
prewarm() { # path extra-env (e.g. "FOO=bar")
|
|
30
|
+
pkill -9 -f "serve --mcp --path $1" 2>/dev/null; rm -f "$1/.homegraph/daemon.sock" 2>/dev/null; sleep 0.6
|
|
31
|
+
env ${2:-} HOMEGRAPH_DAEMON_IDLE_TIMEOUT_MS=1800000 node "$BIN" serve --mcp --path "$1" </dev/null >/dev/null 2>&1 &
|
|
32
|
+
node -e 'const fs=require("fs");let n=0;const t=setInterval(()=>{if(fs.existsSync(process.argv[1]+"/.homegraph/daemon.sock")){clearInterval(t);process.exit(0)}if(n++>150){clearInterval(t);process.exit(1)}},100)' "$1" \
|
|
33
|
+
&& echo " daemon warm" || echo " WARN daemon never bound"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
run() { # arm rep mcp-config usage-log-or-dash
|
|
37
|
+
local arm="$1" rep="$2" cfg="$3" usage="$4" tag="$REPO-$1-$2"
|
|
38
|
+
[ "$usage" != "-" ] && : > "$usage"
|
|
39
|
+
# DISALLOW (optional): block sub-agent delegation across all arms so the A/B
|
|
40
|
+
# measures the retrieval mode, not whether Sonnet decides to spawn a homegraph-blind
|
|
41
|
+
# Explore subagent (which thrashes regardless and adds huge variance).
|
|
42
|
+
( cd "$TARGET" && claude -p "$Q" \
|
|
43
|
+
--output-format stream-json --verbose --permission-mode bypassPermissions \
|
|
44
|
+
--model "${MODEL:-sonnet}" --effort "${EFFORT:-high}" --max-budget-usd 4 \
|
|
45
|
+
${DISALLOW:+--disallowedTools "$DISALLOW"} \
|
|
46
|
+
--strict-mcp-config --mcp-config "$cfg" \
|
|
47
|
+
</dev/null > "$RUNS/$tag.jsonl" 2>"$RUNS/$tag.err" )
|
|
48
|
+
node "$EXTRACT" --run "$RUNS/$tag.jsonl" --usage "$usage" --arm "$arm" --rep "$rep" \
|
|
49
|
+
--repo "$REPO" --tier "$TIER" --q "$Q" >> "$RESULTS"
|
|
50
|
+
node -e 'const o=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n").pop());console.log(` [${o.arm} #${o.rep}] ${o.durationSec}s | main $${o.costUsdMain} ${o.tokBillable} tok | read=${o.read} grep=${o.grep} explore=${o.explore} offload=${o.offloadFired} | AI ${o.ai.calls}call/${o.ai.totalTokens}tok/$${o.ai.costUsd.toFixed(4)} | ok=${o.ok}`)' "$RESULTS"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
CFG_OFF="$RUNS/mcp-offload-$REPO.json"; CFG_RAW="$RUNS/mcp-raw-$REPO.json"; CFG_NOCG="$RUNS/mcp-nocg.json"
|
|
54
|
+
USAGE="$RUNS/$REPO-usage.jsonl"
|
|
55
|
+
printf '{"mcpServers":{"homegraph":{"command":"env","args":["HOMEGRAPH_WASM_RELAUNCHED=1","HOMEGRAPH_OFFLOAD_USAGE_LOG=%s","node","%s","serve","--mcp","--path","%s"]}}}' "$USAGE" "$BIN" "$TARGET" > "$CFG_OFF"
|
|
56
|
+
printf '{"mcpServers":{"homegraph":{"command":"env","args":["HOMEGRAPH_WASM_RELAUNCHED=1","HOMEGRAPH_OFFLOAD_DISABLE=1","node","%s","serve","--mcp","--path","%s"]}}}' "$BIN" "$TARGET" > "$CFG_RAW"
|
|
57
|
+
printf '{"mcpServers":{}}' > "$CFG_NOCG"
|
|
58
|
+
|
|
59
|
+
# REP_START lets a later batch ADD reps without clobbering earlier jsonls
|
|
60
|
+
# (e.g. REP_START=4 REPS=3 -> reps 4,5,6; default starts at 1).
|
|
61
|
+
START="${REP_START:-1}"; END=$((START + REPS - 1))
|
|
62
|
+
echo "###### repo=$REPO tier=$TIER reps=$START..$END model=${MODEL:-sonnet}/${EFFORT:-high}"
|
|
63
|
+
echo "###### Q=$Q"
|
|
64
|
+
echo "== ARM offload =="; prewarm "$TARGET" "HOMEGRAPH_OFFLOAD_USAGE_LOG=$USAGE"
|
|
65
|
+
for r in $(seq "$START" "$END"); do run offload "$r" "$CFG_OFF" "$USAGE"; done
|
|
66
|
+
pkill -9 -f "serve --mcp --path $TARGET" 2>/dev/null; rm -f "$TARGET/.homegraph/daemon.sock" 2>/dev/null; sleep 1
|
|
67
|
+
echo "== ARM raw =="; prewarm "$TARGET" "HOMEGRAPH_OFFLOAD_DISABLE=1"
|
|
68
|
+
for r in $(seq "$START" "$END"); do run raw "$r" "$CFG_RAW" "-"; done
|
|
69
|
+
pkill -9 -f "serve --mcp --path $TARGET" 2>/dev/null; rm -f "$TARGET/.homegraph/daemon.sock" 2>/dev/null; sleep 1
|
|
70
|
+
echo "== ARM nocg =="
|
|
71
|
+
for r in $(seq "$START" "$END"); do run nocg "$r" "$CFG_NOCG" "-"; done
|
|
72
|
+
echo "###### DONE $REPO"
|
|
@@ -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
|
+
}
|