cgraphx 1.2.0 → 1.3.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/dist/.claude-template/hooks/precommit-check/precommit-check.cjs +90 -0
- package/dist/.claude-template/skills/cgraphx-guide/how-to-use.html +99 -78
- package/dist/.claude-template/skills/precommit-review/SKILL.md +50 -0
- package/dist/.claude-template/skills/run-api-test/SKILL.md +187 -0
- package/dist/.claude-template/skills/run-api-test/assets/template-test-report.md +103 -0
- package/dist/.claude-template/skills/run-api-test/assets/template-test-verify.jsonl +5 -0
- package/dist/.claude-template/skills/run-api-test/references/bru-run.md +60 -0
- package/dist/.claude-template/skills/run-api-test/references/db-verification.md +81 -0
- package/dist/.claude-template/skills/run-api-test/references/report-format.md +104 -0
- package/dist/.claude-template/skills/run-api-test/references/service-readiness.md +61 -0
- package/dist/.claude-template/skills/run-api-test/references/test-scope.md +64 -0
- package/dist/.claude-template/skills/write-api/SKILL.md +150 -0
- package/dist/.claude-template/skills/write-api/assets/template-api-spec.md +112 -0
- package/dist/.claude-template/skills/write-api/assets/template-request.bru +75 -0
- package/dist/.claude-template/skills/write-api/references/ai-prompts.md +133 -0
- package/dist/.claude-template/skills/write-api/references/api-spec-format.md +108 -0
- package/dist/.claude-template/skills/write-api/references/bru-format.md +144 -0
- package/dist/.claude-template/skills/write-api/references/collection-layout.md +81 -0
- package/dist/.claude-template/skills/write-api/references/environment-setup.md +105 -0
- package/dist/.claude-template/skills/write-api/references/interface-scope.md +74 -0
- package/dist/api-test/ai-fields.d.ts +37 -0
- package/dist/api-test/ai-fields.d.ts.map +1 -0
- package/dist/api-test/ai-fields.js +114 -0
- package/dist/api-test/ai-fields.js.map +1 -0
- package/dist/api-test/assemble.d.ts +76 -0
- package/dist/api-test/assemble.d.ts.map +1 -0
- package/dist/api-test/assemble.js +185 -0
- package/dist/api-test/assemble.js.map +1 -0
- package/dist/api-test/bru-cli-invoker.d.ts +72 -0
- package/dist/api-test/bru-cli-invoker.d.ts.map +1 -0
- package/dist/api-test/bru-cli-invoker.js +169 -0
- package/dist/api-test/bru-cli-invoker.js.map +1 -0
- package/dist/api-test/bru-report-parser.d.ts +24 -0
- package/dist/api-test/bru-report-parser.d.ts.map +1 -0
- package/dist/api-test/bru-report-parser.js +110 -0
- package/dist/api-test/bru-report-parser.js.map +1 -0
- package/dist/api-test/bru-runner.d.ts +101 -0
- package/dist/api-test/bru-runner.d.ts.map +1 -0
- package/dist/api-test/bru-runner.js +316 -0
- package/dist/api-test/bru-runner.js.map +1 -0
- package/dist/api-test/bru-writer.d.ts +52 -0
- package/dist/api-test/bru-writer.d.ts.map +1 -0
- package/dist/api-test/bru-writer.js +159 -0
- package/dist/api-test/bru-writer.js.map +1 -0
- package/dist/api-test/call-chain-extractor.d.ts +80 -0
- package/dist/api-test/call-chain-extractor.d.ts.map +1 -0
- package/dist/api-test/call-chain-extractor.js +179 -0
- package/dist/api-test/call-chain-extractor.js.map +1 -0
- package/dist/api-test/cli.d.ts +133 -0
- package/dist/api-test/cli.d.ts.map +1 -0
- package/dist/api-test/cli.js +1009 -0
- package/dist/api-test/cli.js.map +1 -0
- package/dist/api-test/config.d.ts +75 -0
- package/dist/api-test/config.d.ts.map +1 -0
- package/dist/api-test/config.js +406 -0
- package/dist/api-test/config.js.map +1 -0
- package/dist/api-test/db-query-cli.d.ts +51 -0
- package/dist/api-test/db-query-cli.d.ts.map +1 -0
- package/dist/api-test/db-query-cli.js +119 -0
- package/dist/api-test/db-query-cli.js.map +1 -0
- package/dist/api-test/enhance-prepare.d.ts +111 -0
- package/dist/api-test/enhance-prepare.d.ts.map +1 -0
- package/dist/api-test/enhance-prepare.js +425 -0
- package/dist/api-test/enhance-prepare.js.map +1 -0
- package/dist/api-test/enhance-write.d.ts +28 -0
- package/dist/api-test/enhance-write.d.ts.map +1 -0
- package/dist/api-test/enhance-write.js +145 -0
- package/dist/api-test/enhance-write.js.map +1 -0
- package/dist/api-test/errors.d.ts +48 -0
- package/dist/api-test/errors.d.ts.map +1 -0
- package/dist/api-test/errors.js +76 -0
- package/dist/api-test/errors.js.map +1 -0
- package/dist/api-test/field-extractor.d.ts +98 -0
- package/dist/api-test/field-extractor.d.ts.map +1 -0
- package/dist/api-test/field-extractor.js +327 -0
- package/dist/api-test/field-extractor.js.map +1 -0
- package/dist/api-test/impl-finder.d.ts +37 -0
- package/dist/api-test/impl-finder.d.ts.map +1 -0
- package/dist/api-test/impl-finder.js +54 -0
- package/dist/api-test/impl-finder.js.map +1 -0
- package/dist/api-test/index.d.ts +41 -0
- package/dist/api-test/index.d.ts.map +1 -0
- package/dist/api-test/index.js +124 -0
- package/dist/api-test/index.js.map +1 -0
- package/dist/api-test/java-parser.d.ts +89 -0
- package/dist/api-test/java-parser.d.ts.map +1 -0
- package/dist/api-test/java-parser.js +508 -0
- package/dist/api-test/java-parser.js.map +1 -0
- package/dist/api-test/md-writer.d.ts +49 -0
- package/dist/api-test/md-writer.d.ts.map +1 -0
- package/dist/api-test/md-writer.js +202 -0
- package/dist/api-test/md-writer.js.map +1 -0
- package/dist/api-test/parser-httpservice.d.ts +91 -0
- package/dist/api-test/parser-httpservice.d.ts.map +1 -0
- package/dist/api-test/parser-httpservice.js +271 -0
- package/dist/api-test/parser-httpservice.js.map +1 -0
- package/dist/api-test/report.d.ts +188 -0
- package/dist/api-test/report.d.ts.map +1 -0
- package/dist/api-test/report.js +522 -0
- package/dist/api-test/report.js.map +1 -0
- package/dist/api-test/snapshot.d.ts +26 -0
- package/dist/api-test/snapshot.d.ts.map +1 -0
- package/dist/api-test/snapshot.js +150 -0
- package/dist/api-test/snapshot.js.map +1 -0
- package/dist/api-test/test-history.d.ts +48 -0
- package/dist/api-test/test-history.d.ts.map +1 -0
- package/dist/api-test/test-history.js +122 -0
- package/dist/api-test/test-history.js.map +1 -0
- package/dist/api-test/types.d.ts +174 -0
- package/dist/api-test/types.d.ts.map +1 -0
- package/dist/api-test/types.js +13 -0
- package/dist/api-test/types.js.map +1 -0
- package/dist/api-test/verify-prepare.d.ts +30 -0
- package/dist/api-test/verify-prepare.d.ts.map +1 -0
- package/dist/api-test/verify-prepare.js +150 -0
- package/dist/api-test/verify-prepare.js.map +1 -0
- package/dist/api-test/verify-write.d.ts +31 -0
- package/dist/api-test/verify-write.d.ts.map +1 -0
- package/dist/api-test/verify-write.js +159 -0
- package/dist/api-test/verify-write.js.map +1 -0
- package/dist/dbquery/dump-schema.d.ts +46 -0
- package/dist/dbquery/dump-schema.d.ts.map +1 -0
- package/dist/dbquery/dump-schema.js +379 -0
- package/dist/dbquery/dump-schema.js.map +1 -0
- package/dist/installer/targets/claude.d.ts +15 -0
- package/dist/installer/targets/claude.d.ts.map +1 -1
- package/dist/installer/targets/claude.js +53 -0
- package/dist/installer/targets/claude.js.map +1 -1
- package/package.json +1 -1
- package/scripts/agent-eval/block-cgraphx-and-gitnexus-cli-hook.sh +0 -43
- package/scripts/agent-eval/block-cgraphx-cli-hook.sh +0 -32
- package/scripts/agent-eval/block-cgraphx-cli-settings.json +0 -16
- package/scripts/agent-eval/cli-vs-mcp-3arm.sh +0 -121
- package/scripts/agent-eval/multi-tool-eval.sh +0 -171
- package/scripts/agent-eval/parse-cli-vs-mcp.mjs +0 -232
- package/scripts/agent-eval/parse-multi-tool.mjs +0 -242
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# PreToolUse hook: deny Bash invocations of the `cgraphx` CLI (or `node .../codegraph.js`).
|
|
3
|
-
# Used by the cli-vs-mcp-3arm eval to enforce "no CLI" in the baseline and mcp-only arms.
|
|
4
|
-
#
|
|
5
|
-
# Match rules (any one → deny):
|
|
6
|
-
# - command starts with `cgraphx ` (e.g. `cgraphx calls Foo#bar`)
|
|
7
|
-
# - command contains token ` cgraphx ` (e.g. `sudo cgraphx ...`)
|
|
8
|
-
# - command contains `codegraph.js` (e.g. `node /path/codegraph.js serve`)
|
|
9
|
-
#
|
|
10
|
-
# Not matched (allowed):
|
|
11
|
-
# - `cat README.md | grep cgraphx` (grep the literal word, not invoke binary)
|
|
12
|
-
# - `ls .cgraphx/` (read dir, not run binary)
|
|
13
|
-
# - `echo "cgraphx"` (literal text)
|
|
14
|
-
#
|
|
15
|
-
# Wire via: claude ... --settings scripts/agent-eval/block-cgraphx-cli-settings.json
|
|
16
|
-
set -uo pipefail
|
|
17
|
-
input="$(cat)"
|
|
18
|
-
cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
|
|
19
|
-
|
|
20
|
-
[ -z "$cmd" ] && exit 0
|
|
21
|
-
|
|
22
|
-
deny=0
|
|
23
|
-
case "$cmd" in
|
|
24
|
-
cgraphx\ *|*\ cgraphx\ *|*codegraph.js*) deny=1 ;;
|
|
25
|
-
esac
|
|
26
|
-
|
|
27
|
-
if [ "$deny" = "1" ]; then
|
|
28
|
-
msg="该 arm 禁用 cgraphx CLI(Bash 调用被 hook 拦截)。改用:Read/Grep/Glob(baseline arm)、codegraph_explore 等 MCP 工具(mcp-only arm)。本评估旨在对比三 arm 在禁/允许 CLI 下的真实表现,请勿尝试绕过。"
|
|
29
|
-
jq -n --arg m "$msg" '{reason:$m, hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$m}}'
|
|
30
|
-
exit 0
|
|
31
|
-
fi
|
|
32
|
-
exit 0
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_comment": "PreToolUse hook config: deny Bash invocations of `cgraphx` CLI. Used by cli-vs-mcp-3arm.sh for the baseline and mcp-only arms. The cli-vs-mcp-3arm.sh script regenerates an absolute-path copy at $OUT/runs/ at runtime, so this static file is mostly for reference / standalone hook testing. If you want to use it directly with claude --settings, edit the path below to your local absolute path.",
|
|
3
|
-
"hooks": {
|
|
4
|
-
"PreToolUse": [
|
|
5
|
-
{
|
|
6
|
-
"matcher": "Bash",
|
|
7
|
-
"hooks": [
|
|
8
|
-
{
|
|
9
|
-
"type": "command",
|
|
10
|
-
"command": "bash /Users/smdnk/objects/ai/codegraph/scripts/agent-eval/block-cgraphx-cli-hook.sh"
|
|
11
|
-
}
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
]
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# CLI-vs-MCP 3-arm eval for ONE indexed repo + ONE flow question, n reps each.
|
|
3
|
-
# ARM baseline : cgraphx CLI禁用(hook) + 空 mcp-config -> Read/Grep baseline
|
|
4
|
-
# ARM cli : cgraphx CLI 允许 + 空 mcp-config + 显式提示用 cgraphx-search skill
|
|
5
|
-
# ARM mcp : cgraphx CLI 禁用(hook) + 完整 cgraphx mcp-config -> MCP codegraph_explore
|
|
6
|
-
# All arms: claude -p sonnet --effort high. One JSON metrics line/run -> $RESULTS.
|
|
7
|
-
#
|
|
8
|
-
# Usage: cli-vs-mcp-3arm.sh <reps> "<question>"
|
|
9
|
-
# Env: TARGET=<indexed-repo path, default /Users/smdnk/objects/java/hx>
|
|
10
|
-
# MODEL=sonnet EFFORT=high RESULTS=<file> AGENT_EVAL_OUT=<scratch dir>
|
|
11
|
-
set -uo pipefail
|
|
12
|
-
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
-
ENGINE="$(cd "$HERE/../.." && pwd)"
|
|
14
|
-
BIN="$ENGINE/dist/bin/codegraph.js"
|
|
15
|
-
HOOK="$HERE/block-cgraphx-cli-hook.sh"
|
|
16
|
-
OUT="${AGENT_EVAL_OUT:-/tmp/cg-cli-vs-mcp}"
|
|
17
|
-
TARGET="${TARGET:-/Users/smdnk/objects/java/hx}"
|
|
18
|
-
REPS="${1:?usage: cli-vs-mcp-3arm.sh <reps> \"<question>\"}"
|
|
19
|
-
Q="${2:?question required}"
|
|
20
|
-
RUNS="$OUT/runs"
|
|
21
|
-
EXTRACT="$HERE/parse-cli-vs-mcp.mjs"
|
|
22
|
-
RESULTS="${RESULTS:-$OUT/results.jsonl}"
|
|
23
|
-
REPO=$(basename "$TARGET")
|
|
24
|
-
mkdir -p "$RUNS"
|
|
25
|
-
command -v claude >/dev/null || { echo "no claude on PATH"; exit 1; }
|
|
26
|
-
[ -d "$TARGET/.cgraphx" ] || { echo "not indexed: $TARGET"; exit 1; }
|
|
27
|
-
[ -f "$BIN" ] || { echo "engine not built: run 'npm run build' in $ENGINE"; exit 1; }
|
|
28
|
-
# Physical path so pkill matches the daemon's real cmdline (macOS /tmp->/private/tmp symlink
|
|
29
|
-
# otherwise makes the kill miss the daemon — contaminating the next arm).
|
|
30
|
-
TARGET=$(cd "$TARGET" && pwd -P)
|
|
31
|
-
REPO=$(basename "$TARGET")
|
|
32
|
-
|
|
33
|
-
# Regenerate the settings.json with the absolute hook path (avoids hard-coding user paths
|
|
34
|
-
# in the checked-in static settings file).
|
|
35
|
-
SETTINGS="$RUNS/block-cgraphx-cli-settings.json"
|
|
36
|
-
cat > "$SETTINGS" <<JSON
|
|
37
|
-
{
|
|
38
|
-
"hooks": {
|
|
39
|
-
"PreToolUse": [
|
|
40
|
-
{
|
|
41
|
-
"matcher": "Bash",
|
|
42
|
-
"hooks": [
|
|
43
|
-
{ "type": "command", "command": "bash $HOOK" }
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
JSON
|
|
50
|
-
|
|
51
|
-
# A2 cli-only system prompt — explicitly steer to invoke the cgraphx-search skill
|
|
52
|
-
# (hx/.claude/skills/cgraphx-search exists; auto-discovery works, but the explicit prompt
|
|
53
|
-
# guarantees the agent loads the skill content). Do NOT list CLI syntax here — that would
|
|
54
|
-
# double up with the skill's own content and bias the measurement.
|
|
55
|
-
CLI_PROMPT="在该项目里理解或定位 Java 代码之前,先用 Skill tool 调用 cgraphx-search skill,获取基于 AST 的代码搜索 CLI 套件(calls / context / impact / related / slice)的完整用法、决策树和 hx 项目实战例子。然后按 skill 指引选择合适的 cgraphx CLI 命令(Bash 调用)做任何代码理解任务,而不是默认用 grep+Read。"
|
|
56
|
-
|
|
57
|
-
# mcp-configs
|
|
58
|
-
CFG_EMPTY="$RUNS/mcp-empty.json"
|
|
59
|
-
CFG_CGRAPHX="$RUNS/mcp-cgraphx.json"
|
|
60
|
-
printf '{"mcpServers":{}}' > "$CFG_EMPTY"
|
|
61
|
-
printf '{"mcpServers":{"cgraphx":{"command":"node","args":["%s","serve","--mcp","--path","%s"]}}}' "$BIN" "$TARGET" > "$CFG_CGRAPHX"
|
|
62
|
-
|
|
63
|
-
prewarm() { # path
|
|
64
|
-
pkill -9 -f "serve --mcp --path $1" 2>/dev/null; rm -f "$1/.cgraphx/daemon.sock" 2>/dev/null; sleep 0.6
|
|
65
|
-
env CGRAPHX_DAEMON_IDLE_TIMEOUT_MS=1800000 CGRAPHX_WASM_RELAUNCHED=1 node "$BIN" serve --mcp --path "$1" </dev/null >/dev/null 2>&1 &
|
|
66
|
-
node -e 'const fs=require("fs");let n=0;const t=setInterval(()=>{if(fs.existsSync(process.argv[1]+"/.cgraphx/daemon.sock")){clearInterval(t);process.exit(0)}if(n++>150){clearInterval(t);process.exit(1)}},100)' "$1" \
|
|
67
|
-
&& echo " daemon warm" || { echo " ERROR daemon never bound"; exit 1; }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
run() { # arm rep mcp-config extra-args...
|
|
71
|
-
local arm="$1" rep="$2" cfg="$3"; shift 3
|
|
72
|
-
local tag="$REPO-$arm-$rep" extra_args=("$@")
|
|
73
|
-
local log="$RUNS/$tag.jsonl" err="$RUNS/$tag.err"
|
|
74
|
-
echo " → [$arm #$rep] running..."
|
|
75
|
-
( cd "$TARGET" && claude -p "$Q" \
|
|
76
|
-
--output-format stream-json --verbose \
|
|
77
|
-
--permission-mode bypassPermissions \
|
|
78
|
-
--model "${MODEL:-sonnet}" --effort "${EFFORT:-high}" --max-budget-usd 4 \
|
|
79
|
-
--strict-mcp-config --mcp-config "$cfg" \
|
|
80
|
-
"${extra_args[@]}" \
|
|
81
|
-
</dev/null > "$log" 2>"$err" )
|
|
82
|
-
local rc=$?
|
|
83
|
-
if [ $rc -ne 0 ]; then
|
|
84
|
-
echo " ← [$arm #$rep] claude exit $rc (err tail:)"; tail -5 "$err" 2>/dev/null
|
|
85
|
-
fi
|
|
86
|
-
# Progress line via jq — robust against missing result event
|
|
87
|
-
if [ -s "$log" ]; then
|
|
88
|
-
local last_line; last_line=$(tail -1 "$log")
|
|
89
|
-
local rtype; rtype=$(printf '%s' "$last_line" | jq -r '.type // "?"' 2>/dev/null)
|
|
90
|
-
if [ "$rtype" = "result" ]; then
|
|
91
|
-
local dur cost sub
|
|
92
|
-
dur=$(printf '%s' "$last_line" | jq -r '(.duration_ms/1000|floor)' 2>/dev/null)
|
|
93
|
-
cost=$(printf '%s' "$last_line" | jq -r '(.total_cost_usd//0|tostring)' 2>/dev/null)
|
|
94
|
-
sub=$(printf '%s' "$last_line" | jq -r '.subtype // "?"' 2>/dev/null)
|
|
95
|
-
echo " ← [$arm #$rep] ${dur}s | \$${cost} | ok=${sub}"
|
|
96
|
-
else
|
|
97
|
-
echo " ← [$arm #$rep] (last event type=${rtype}, no result yet)"
|
|
98
|
-
fi
|
|
99
|
-
fi
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
# REP_START lets a later batch ADD reps without clobbering earlier jsonls.
|
|
103
|
-
START="${REP_START:-1}"; END=$((START + REPS - 1))
|
|
104
|
-
echo "###### repo=$REPO reps=$START..$END model=${MODEL:-sonnet}/${EFFORT:-high}"
|
|
105
|
-
echo "###### Q=$Q"
|
|
106
|
-
echo "== ARM baseline (no cgraphx, hook denies CLI, empty mcp) =="
|
|
107
|
-
for r in $(seq "$START" "$END"); do run baseline "$r" "$CFG_EMPTY" --settings "$SETTINGS"; done
|
|
108
|
-
|
|
109
|
-
echo "== ARM cli-only (cgraphx CLI allowed, empty mcp, explicit skill prompt) =="
|
|
110
|
-
for r in $(seq "$START" "$END"); do run cli "$r" "$CFG_EMPTY" --append-system-prompt "$CLI_PROMPT"; done
|
|
111
|
-
|
|
112
|
-
echo "== ARM mcp-only (cgraphx MCP allowed, hook denies CLI) =="
|
|
113
|
-
prewarm "$TARGET"
|
|
114
|
-
for r in $(seq "$START" "$END"); do run mcp "$r" "$CFG_CGRAPHX" --settings "$SETTINGS"; done
|
|
115
|
-
pkill -9 -f "serve --mcp --path $TARGET" 2>/dev/null; rm -f "$TARGET/.cgraphx/daemon.sock" 2>/dev/null
|
|
116
|
-
|
|
117
|
-
echo "###### DONE $REPO"
|
|
118
|
-
echo "== parsing all transcripts =="
|
|
119
|
-
: > "$RESULTS"
|
|
120
|
-
node "$EXTRACT" "$OUT"
|
|
121
|
-
echo "== results.jsonl: $RESULTS =="
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Multi-tool eval: cgraphx CLI vs cgraphx MCP vs gitnexus MCP, across 3 task types.
|
|
3
|
-
#
|
|
4
|
-
# Task types (each agent gets ONE question, no tool-presetting in the wording):
|
|
5
|
-
# flow-trace — trace a call chain through the codebase
|
|
6
|
-
# onboarding — give a new-engineer overview of a module
|
|
7
|
-
# impact — assess refactor blast radius of a specific symbol
|
|
8
|
-
#
|
|
9
|
-
# Arms:
|
|
10
|
-
# cgraphx-cli : cgraphx CLI allowed + cgraphx-search skill prompt
|
|
11
|
-
# hook denies cgraphx MCP is empty + gitnexus CLI/MCP both empty
|
|
12
|
-
# cgraphx-mcp : cgraphx MCP allowed + hook denies BOTH cgraphx CLI and gitnexus CLI
|
|
13
|
-
# gitnexus-mcp : gitnexus MCP allowed + hook denies BOTH cgraphx CLI and gitnexus CLI
|
|
14
|
-
#
|
|
15
|
-
# Usage: multi-tool-eval.sh <reps> [<task>...]
|
|
16
|
-
# task defaults to "flow onboarding impact" (all three)
|
|
17
|
-
# Env: TARGET=<indexed-repo path, default /Users/smdnk/objects/java/hx>
|
|
18
|
-
# MODEL=sonnet EFFORT=high AGENT_EVAL_OUT=<scratch dir>
|
|
19
|
-
set -uo pipefail
|
|
20
|
-
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
21
|
-
ENGINE="$(cd "$HERE/../.." && pwd)"
|
|
22
|
-
CGRAPHX_BIN="$ENGINE/dist/bin/codegraph.js"
|
|
23
|
-
GITNEXUS_BIN="/Users/smdnk/objects/ai/code-workflow/gitnexus/gitnexus/dist/cli/index.js"
|
|
24
|
-
HOOK="$HERE/block-cgraphx-and-gitnexus-cli-hook.sh"
|
|
25
|
-
OUT="${AGENT_EVAL_OUT:-/tmp/cg-multi-tool}"
|
|
26
|
-
TARGET="${TARGET:-/Users/smdnk/objects/java/hx}"
|
|
27
|
-
REPS="${1:?usage: multi-tool-eval.sh <reps> [task...]}"
|
|
28
|
-
shift
|
|
29
|
-
TASKS="${*:-flow onboarding impact}"
|
|
30
|
-
RUNS="$OUT/runs"
|
|
31
|
-
EXTRACT="$HERE/parse-multi-tool.mjs"
|
|
32
|
-
RESULTS="${RESULTS:-$OUT/results.jsonl}"
|
|
33
|
-
REPO=$(basename "$TARGET")
|
|
34
|
-
mkdir -p "$RUNS"
|
|
35
|
-
command -v claude >/dev/null || { echo "no claude on PATH"; exit 1; }
|
|
36
|
-
[ -d "$TARGET/.cgraphx" ] || { echo "not cgraphx-indexed: $TARGET"; exit 1; }
|
|
37
|
-
[ -d "$TARGET/.gitnexus" ] || { echo "not gitnexus-indexed: $TARGET"; exit 1; }
|
|
38
|
-
[ -f "$CGRAPHX_BIN" ] || { echo "cgraphx engine not built: run 'npm run build' in $ENGINE"; exit 1; }
|
|
39
|
-
[ -f "$GITNEXUS_BIN" ] || { echo "gitnexus binary missing at $GITNEXUS_BIN"; exit 1; }
|
|
40
|
-
TARGET=$(cd "$TARGET" && pwd -P)
|
|
41
|
-
REPO=$(basename "$TARGET")
|
|
42
|
-
|
|
43
|
-
# Regenerate settings.json with absolute hook path.
|
|
44
|
-
SETTINGS="$RUNS/block-cli-settings.json"
|
|
45
|
-
cat > "$SETTINGS" <<JSON
|
|
46
|
-
{
|
|
47
|
-
"hooks": {
|
|
48
|
-
"PreToolUse": [
|
|
49
|
-
{
|
|
50
|
-
"matcher": "Bash",
|
|
51
|
-
"hooks": [
|
|
52
|
-
{ "type": "command", "command": "bash $HOOK" }
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
JSON
|
|
59
|
-
|
|
60
|
-
# A1 cgraphx-cli: explicit skill prompt to invoke cgraphx-search skill
|
|
61
|
-
CGRAPHX_CLI_PROMPT="在该项目里理解或定位 Java 代码之前,先用 Skill tool 调用 cgraphx-search skill,获取基于 AST 的代码搜索 CLI 套件(calls / context / impact / related / slice)的完整用法、决策树和 hx 项目实战例子。然后按 skill 指引选择合适的 cgraphx CLI 命令(Bash 调用)做任何代码理解任务,而不是默认用 grep+Read。"
|
|
62
|
-
|
|
63
|
-
# mcp-configs
|
|
64
|
-
CFG_EMPTY="$RUNS/mcp-empty.json"
|
|
65
|
-
CFG_CGRAPHX="$RUNS/mcp-cgraphx.json"
|
|
66
|
-
CFG_GITNEXUS="$RUNS/mcp-gitnexus.json"
|
|
67
|
-
printf '{"mcpServers":{}}' > "$CFG_EMPTY"
|
|
68
|
-
# cgraphx-only: ONLY cgraphx MCP, NO gitnexus MCP
|
|
69
|
-
printf '{"mcpServers":{"cgraphx":{"command":"node","args":["%s","serve","--mcp","--path","%s"]}}}' "$CGRAPHX_BIN" "$TARGET" > "$CFG_CGRAPHX"
|
|
70
|
-
# gitnexus-only: ONLY gitnexus MCP, NO cgraphx MCP
|
|
71
|
-
printf '{"mcpServers":{"gitnexus":{"command":"node","args":["%s","mcp"]}}}' "$GITNEXUS_BIN" > "$CFG_GITNEXUS"
|
|
72
|
-
|
|
73
|
-
# Task questions (locked wording — DO NOT preset tools)
|
|
74
|
-
# Use a function instead of associative array (macOS bash 3.2 doesn't support declare -A).
|
|
75
|
-
get_q() {
|
|
76
|
-
case "$1" in
|
|
77
|
-
flow)
|
|
78
|
-
echo '在 hx 项目里追踪 so-service 的订单提交流程:从外部接口入口,经过 SMO/BUSI 层业务编排,到调用 order-service 或落库的完整调用链。给出每一步的类#方法和文件路径。'
|
|
79
|
-
;;
|
|
80
|
-
onboarding)
|
|
81
|
-
echo '给一个新接手 hx 项目 so-service 模块的工程师做 onboarding 概览:它有哪些核心业务能力?对外暴露的接口有哪些?内部分哪些子模块?跟其他服务(order-service / inst-service)有什么调用关系?'
|
|
82
|
-
;;
|
|
83
|
-
impact)
|
|
84
|
-
echo '在 hx 项目里,如果要修改 SceneCommitServiceImpl#sceneCommit 方法的签名(增加一个参数),会影响哪些调用方?涉及哪些跨服务调用?需要更新哪些测试?给一个重构风险评估。'
|
|
85
|
-
;;
|
|
86
|
-
*) echo "ERROR: unknown task: $1" >&2; exit 1 ;;
|
|
87
|
-
esac
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
prewarm_cgraphx() {
|
|
91
|
-
pkill -9 -f "serve --mcp --path $1" 2>/dev/null; rm -f "$1/.cgraphx/daemon.sock" 2>/dev/null; sleep 0.6
|
|
92
|
-
env CGRAPHX_DAEMON_IDLE_TIMEOUT_MS=1800000 CGRAPHX_WASM_RELAUNCHED=1 node "$CGRAPHX_BIN" serve --mcp --path "$1" </dev/null >/dev/null 2>&1 &
|
|
93
|
-
node -e 'const fs=require("fs");let n=0;const t=setInterval(()=>{if(fs.existsSync(process.argv[1]+"/.cgraphx/daemon.sock")){clearInterval(t);process.exit(0)}if(n++>150){clearInterval(t);process.exit(1)}},100)' "$1" \
|
|
94
|
-
&& echo " cgraphx daemon warm" || { echo " ERROR cgraphx daemon never bound"; exit 1; }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
prewarm_gitnexus() {
|
|
98
|
-
# gitnexus mcp is stateless HTTP-less stdio; no daemon needed, just verify it starts.
|
|
99
|
-
# The MCP server is invoked per claude-session via stdio, no prewarm needed.
|
|
100
|
-
echo " gitnexus mcp is stdio (no prewarm needed)"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
run() { # task arm rep mcp-config extra-args...
|
|
104
|
-
local task="$1" arm="$2" rep="$3" cfg="$4"; shift 4
|
|
105
|
-
local tag="$REPO-$task-$arm-$rep" extra_args=("$@")
|
|
106
|
-
local log="$RUNS/$tag.jsonl" err="$RUNS/$tag.err"
|
|
107
|
-
local q; q=$(get_q "$task")
|
|
108
|
-
echo " → [$task/$arm #$rep] running..."
|
|
109
|
-
( cd "$TARGET" && claude -p "$q" \
|
|
110
|
-
--output-format stream-json --verbose \
|
|
111
|
-
--permission-mode bypassPermissions \
|
|
112
|
-
--model "${MODEL:-sonnet}" --effort "${EFFORT:-high}" --max-budget-usd 4 \
|
|
113
|
-
--strict-mcp-config --mcp-config "$cfg" \
|
|
114
|
-
"${extra_args[@]}" \
|
|
115
|
-
</dev/null > "$log" 2>"$err" )
|
|
116
|
-
local rc=$?
|
|
117
|
-
if [ $rc -ne 0 ]; then
|
|
118
|
-
echo " ← [$task/$arm #$rep] claude exit $rc (err tail:)"; tail -5 "$err" 2>/dev/null
|
|
119
|
-
fi
|
|
120
|
-
if [ -s "$log" ]; then
|
|
121
|
-
local last_line rtype
|
|
122
|
-
last_line=$(tail -1 "$log")
|
|
123
|
-
rtype=$(printf '%s' "$last_line" | jq -r '.type // "?"' 2>/dev/null)
|
|
124
|
-
if [ "$rtype" = "result" ]; then
|
|
125
|
-
local dur cost sub
|
|
126
|
-
dur=$(printf '%s' "$last_line" | jq -r '(.duration_ms/1000|floor)' 2>/dev/null)
|
|
127
|
-
cost=$(printf '%s' "$last_line" | jq -r '(.total_cost_usd//0*1000|floor)' 2>/dev/null)
|
|
128
|
-
cost=$(printf '%s' "$last_line" | jq -r '(.total_cost_usd//0|tostring)' 2>/dev/null)
|
|
129
|
-
sub=$(printf '%s' "$last_line" | jq -r '.subtype // "?"' 2>/dev/null)
|
|
130
|
-
echo " ← [$task/$arm #$rep] ${dur}s | \$${cost} | ok=${sub}"
|
|
131
|
-
else
|
|
132
|
-
echo " ← [$task/$arm #$rep] (last event type=${rtype}, no result yet)"
|
|
133
|
-
fi
|
|
134
|
-
fi
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
# REP_START lets a later batch ADD reps without clobbering earlier jsonls.
|
|
138
|
-
START="${REP_START:-1}"; END=$((START + REPS - 1))
|
|
139
|
-
echo "###### repo=$REPO tasks=[ $TASKS ] reps=$START..$END model=${MODEL:-sonnet}/${EFFORT:-high}"
|
|
140
|
-
echo "###### arms: cgraphx-cli / cgraphx-mcp / gitnexus-mcp"
|
|
141
|
-
|
|
142
|
-
for task in $TASKS; do
|
|
143
|
-
echo ""
|
|
144
|
-
echo "===== TASK: $task ====="
|
|
145
|
-
echo "Q: $(get_q "$task")"
|
|
146
|
-
echo ""
|
|
147
|
-
|
|
148
|
-
echo "-- ARM cgraphx-cli (cgraphx CLI allowed, both MCPs empty, skill prompt) --"
|
|
149
|
-
for r in $(seq "$START" "$END"); do
|
|
150
|
-
run "$task" cgraphx-cli "$r" "$CFG_EMPTY" --append-system-prompt "$CGRAPHX_CLI_PROMPT"
|
|
151
|
-
done
|
|
152
|
-
|
|
153
|
-
echo "-- ARM cgraphx-mcp (cgraphx MCP, hook denies both CLIs) --"
|
|
154
|
-
prewarm_cgraphx "$TARGET"
|
|
155
|
-
for r in $(seq "$START" "$END"); do
|
|
156
|
-
run "$task" cgraphx-mcp "$r" "$CFG_CGRAPHX" --settings "$SETTINGS"
|
|
157
|
-
done
|
|
158
|
-
pkill -9 -f "serve --mcp --path $TARGET" 2>/dev/null; rm -f "$TARGET/.cgraphx/daemon.sock" 2>/dev/null
|
|
159
|
-
|
|
160
|
-
echo "-- ARM gitnexus-mcp (gitnexus MCP, hook denies both CLIs) --"
|
|
161
|
-
prewarm_gitnexus
|
|
162
|
-
for r in $(seq "$START" "$END"); do
|
|
163
|
-
run "$task" gitnexus-mcp "$r" "$CFG_GITNEXUS" --settings "$SETTINGS"
|
|
164
|
-
done
|
|
165
|
-
done
|
|
166
|
-
|
|
167
|
-
echo ""
|
|
168
|
-
echo "###### DONE — parsing all transcripts"
|
|
169
|
-
: > "$RESULTS"
|
|
170
|
-
node "$EXTRACT" "$OUT"
|
|
171
|
-
echo "== results.jsonl: $RESULTS =="
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Parse cli-vs-mcp-3arm eval transcripts into:
|
|
3
|
-
// 1. one JSON metrics line per transcript -> <out-dir>/results.jsonl
|
|
4
|
-
// 2. stdout: per-arm × per-rep detail table + per-arm aggregate (mean across reps)
|
|
5
|
-
//
|
|
6
|
-
// File-name contract: <repo>-(baseline|cli|mcp)-<rep>.jsonl under <out-dir>/runs/
|
|
7
|
-
//
|
|
8
|
-
// Per-transcript metrics:
|
|
9
|
-
// read / grep (Grep+Glob, NOT Bash) / bash / cliCalls (Bash cgraphx invocations)
|
|
10
|
-
// mcpCalls (mcp__cgraphx__*) / skillInvokes (Skill tool, skill=cgraphx-search)
|
|
11
|
-
// hookDenies (PreToolUse deny events for Bash cgraphx)
|
|
12
|
-
// durationSec / numTurns / costUsdMain / tokGen / tokFresh / tokCached / tokBillable / ok
|
|
13
|
-
import { readFileSync, readdirSync, existsSync, writeFileSync } from 'fs';
|
|
14
|
-
import { join } from 'path';
|
|
15
|
-
|
|
16
|
-
const OUT = process.argv[2] || '/tmp/cg-cli-vs-mcp';
|
|
17
|
-
const RUNS_DIR = join(OUT, 'runs');
|
|
18
|
-
const RESULTS = join(OUT, 'results.jsonl');
|
|
19
|
-
|
|
20
|
-
if (!existsSync(RUNS_DIR)) {
|
|
21
|
-
console.error(`no runs dir at ${RUNS_DIR}`);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function parse(file) {
|
|
26
|
-
const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
27
|
-
const toolCounts = {};
|
|
28
|
-
const cliCommands = [];
|
|
29
|
-
const hookDenies = [];
|
|
30
|
-
let result = null;
|
|
31
|
-
|
|
32
|
-
for (const line of lines) {
|
|
33
|
-
let ev;
|
|
34
|
-
try { ev = JSON.parse(line); } catch { continue; }
|
|
35
|
-
|
|
36
|
-
// NOTE: per-turn assistant.message.usage is placeholder 0s in stream-json;
|
|
37
|
-
// authoritative token totals live only in the final result.usage. So we
|
|
38
|
-
// don't accumulate per-turn — we read result.usage at the end.
|
|
39
|
-
|
|
40
|
-
if (ev.type === 'assistant' && Array.isArray(ev.message?.content)) {
|
|
41
|
-
for (const b of ev.message.content) {
|
|
42
|
-
if (b.type !== 'tool_use') continue;
|
|
43
|
-
toolCounts[b.name] = (toolCounts[b.name] || 0) + 1;
|
|
44
|
-
if (b.name === 'Bash' && typeof b.input?.command === 'string') {
|
|
45
|
-
const cmd = b.input.command;
|
|
46
|
-
// cliCalls = Bash commands that invoke the cgraphx binary (matches hook matcher).
|
|
47
|
-
if (/^cgraphx /.test(cmd) || / cgraphx /.test(cmd) || /codegraph\.js/.test(cmd)) {
|
|
48
|
-
cliCommands.push(cmd.slice(0, 80));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (b.name === 'Skill' && b.input?.skill === 'cgraphx-search') {
|
|
52
|
-
// counted via toolCounts['Skill'] + we filter by skill name below
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Hook denies come back as tool_result with is_error + content matching the deny message,
|
|
58
|
-
// OR as a hookSpecificOutput denial. In stream-json they show up in user messages
|
|
59
|
-
// (tool_result content) when permissionDecision:"deny" — the agent sees the reason text.
|
|
60
|
-
if (ev.type === 'user' && Array.isArray(ev.message?.content)) {
|
|
61
|
-
for (const b of ev.message.content) {
|
|
62
|
-
if (b.type !== 'tool_result') continue;
|
|
63
|
-
const text = typeof b.content === 'string'
|
|
64
|
-
? b.content
|
|
65
|
-
: Array.isArray(b.content)
|
|
66
|
-
? b.content.map(c => (typeof c === 'string' ? c : c?.text || '')).join('')
|
|
67
|
-
: '';
|
|
68
|
-
if (/该 arm 禁用 cgraphx CLI/.test(text)) {
|
|
69
|
-
hookDenies.push(text.slice(0, 100));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (ev.type === 'result') result = ev;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Skill invokes: count tool_use blocks where name=Skill AND input.skill=cgraphx-search.
|
|
78
|
-
// Re-scan to be precise (toolCounts['Skill'] would count ALL skill invokes).
|
|
79
|
-
let skillInvokes = 0;
|
|
80
|
-
for (const line of lines) {
|
|
81
|
-
let ev;
|
|
82
|
-
try { ev = JSON.parse(line); } catch { continue; }
|
|
83
|
-
if (ev.type === 'assistant' && Array.isArray(ev.message?.content)) {
|
|
84
|
-
for (const b of ev.message.content) {
|
|
85
|
-
if (b.type === 'tool_use' && b.name === 'Skill' && b.input?.skill === 'cgraphx-search') {
|
|
86
|
-
skillInvokes++;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const get = (n) => toolCounts[n] || 0;
|
|
93
|
-
// Token usage from the final result event (authoritative — see note above).
|
|
94
|
-
const u = result?.usage || {};
|
|
95
|
-
const tokGen = u.output_tokens || 0;
|
|
96
|
-
const tokFresh = (u.input_tokens || 0) + (u.cache_creation_input_tokens || 0);
|
|
97
|
-
const tokCached = u.cache_read_input_tokens || 0;
|
|
98
|
-
return {
|
|
99
|
-
read: get('Read'),
|
|
100
|
-
grep: get('Grep') + get('Glob'),
|
|
101
|
-
bash: get('Bash'),
|
|
102
|
-
cliCalls: cliCommands.length,
|
|
103
|
-
cliCommands,
|
|
104
|
-
mcpCalls: Object.keys(toolCounts)
|
|
105
|
-
.filter(k => /mcp__cgraphx__/.test(k))
|
|
106
|
-
.reduce((s, k) => s + toolCounts[k], 0),
|
|
107
|
-
skillInvokes,
|
|
108
|
-
hookDenies: hookDenies.length,
|
|
109
|
-
durationSec: result ? Math.floor(result.duration_ms / 1000) : null,
|
|
110
|
-
numTurns: result?.num_turns ?? null,
|
|
111
|
-
costUsdMain: result ? +(result.total_cost_usd || 0).toFixed(4) : null,
|
|
112
|
-
tokGen,
|
|
113
|
-
tokFresh,
|
|
114
|
-
tokCached,
|
|
115
|
-
tokBillable: tokGen + tokFresh,
|
|
116
|
-
ok: result?.subtype === 'success',
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// repo -> arm -> [runs]
|
|
121
|
-
const data = {};
|
|
122
|
-
const allMetrics = [];
|
|
123
|
-
for (const f of readdirSync(RUNS_DIR)) {
|
|
124
|
-
const m = f.match(/^(.+)-(baseline|cli|mcp)-(\d+)\.jsonl$/);
|
|
125
|
-
if (!m) continue;
|
|
126
|
-
const [, repo, arm, repStr] = m;
|
|
127
|
-
const parsed = parse(join(RUNS_DIR, f));
|
|
128
|
-
const rep = Number(repStr);
|
|
129
|
-
const record = { repo, arm, rep, file: f, ...parsed };
|
|
130
|
-
allMetrics.push(record);
|
|
131
|
-
(((data[repo] ??= {})[arm]) ??= []).push(record);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Write all metrics to results.jsonl
|
|
135
|
-
writeFileSync(RESULTS, allMetrics
|
|
136
|
-
.sort((a, b) => a.repo.localeCompare(b.repo) || a.arm.localeCompare(b.arm) || a.rep - b.rep)
|
|
137
|
-
.map(r => JSON.stringify(r))
|
|
138
|
-
.join('\n') + '\n'
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// ---- Detail table ----
|
|
142
|
-
const pad = (s, n) => String(s).padEnd(n);
|
|
143
|
-
const ARMS = ['baseline', 'cli', 'mcp'];
|
|
144
|
-
const LABEL = { baseline: 'A1 baseline', cli: 'A2 cli-only', mcp: 'A3 mcp-only' };
|
|
145
|
-
|
|
146
|
-
console.log('\n=== PER ARM × REP (detail) ===');
|
|
147
|
-
console.log(
|
|
148
|
-
pad('arm', 14), pad('rep', 4),
|
|
149
|
-
pad('dur', 6), pad('read', 5), pad('grep', 5), pad('bash', 5),
|
|
150
|
-
pad('cli', 4), pad('mcp', 4), pad('skill', 5), pad('deny', 5),
|
|
151
|
-
pad('tokBill', 9), pad('cost', 8), 'ok'
|
|
152
|
-
);
|
|
153
|
-
for (const repo of Object.keys(data).sort()) {
|
|
154
|
-
for (const arm of ARMS) {
|
|
155
|
-
const runs = data[repo][arm] || [];
|
|
156
|
-
for (const r of runs) {
|
|
157
|
-
console.log(
|
|
158
|
-
pad(LABEL[arm], 14), pad(r.rep, 4),
|
|
159
|
-
pad(r.durationSec ?? '?', 6),
|
|
160
|
-
pad(r.read, 5), pad(r.grep, 5), pad(r.bash, 5),
|
|
161
|
-
pad(r.cliCalls, 4), pad(r.mcpCalls, 4), pad(r.skillInvokes, 5), pad(r.hookDenies, 5),
|
|
162
|
-
pad(r.tokBillable, 9),
|
|
163
|
-
pad('$' + (r.costUsdMain ?? 0).toFixed(4), 8),
|
|
164
|
-
r.ok ? '✓' : '✗'
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ---- Aggregate table (full mean across all reps) ----
|
|
171
|
-
console.log('\n=== AGGREGATE PER ARM (mean across ALL reps) ===');
|
|
172
|
-
const avg = (a, f) => a.length ? a.reduce((s, x) => s + (f(x) || 0), 0) / a.length : 0;
|
|
173
|
-
console.log(
|
|
174
|
-
pad('arm', 14),
|
|
175
|
-
pad('reads', 7), pad('grep', 7), pad('bash', 7),
|
|
176
|
-
pad('cli', 7), pad('mcp', 7), pad('skill', 7), pad('deny', 7),
|
|
177
|
-
pad('dur', 7), pad('tokBill', 9), pad('cost', 8), 'n'
|
|
178
|
-
);
|
|
179
|
-
for (const arm of ARMS) {
|
|
180
|
-
const all = [];
|
|
181
|
-
for (const repo of Object.keys(data)) for (const r of (data[repo][arm] || [])) all.push(r);
|
|
182
|
-
if (!all.length) continue;
|
|
183
|
-
console.log(
|
|
184
|
-
pad(LABEL[arm], 14),
|
|
185
|
-
pad(avg(all, r => r.read).toFixed(1), 7),
|
|
186
|
-
pad(avg(all, r => r.grep).toFixed(1), 7),
|
|
187
|
-
pad(avg(all, r => r.bash).toFixed(1), 7),
|
|
188
|
-
pad(avg(all, r => r.cliCalls).toFixed(1), 7),
|
|
189
|
-
pad(avg(all, r => r.mcpCalls).toFixed(1), 7),
|
|
190
|
-
pad(avg(all, r => r.skillInvokes).toFixed(1), 7),
|
|
191
|
-
pad(avg(all, r => r.hookDenies).toFixed(1), 7),
|
|
192
|
-
pad(avg(all, r => r.durationSec).toFixed(0) + 's', 7),
|
|
193
|
-
pad(avg(all, r => r.tokBillable).toFixed(0), 9),
|
|
194
|
-
pad('$' + avg(all, r => r.costUsdMain).toFixed(4), 8),
|
|
195
|
-
all.length
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ---- Stability table: mean for each n-prefix (n=2, n=3, n=4 if data permits) ----
|
|
200
|
-
// Shows how the mean shifts as we add more reps. If a metric is stable across n-prefixes,
|
|
201
|
-
// n=2 was already enough; if it shifts a lot, more reps are needed.
|
|
202
|
-
console.log('\n=== STABILITY BY N-PREFIX (mean of first N reps per arm) ===');
|
|
203
|
-
console.log(pad('arm', 14) + pad('metric', 10) + pad('n=2', 12) + pad('n=3', 12) + pad('n=4', 12));
|
|
204
|
-
const METRICS = [
|
|
205
|
-
['cost', r => r.costUsdMain, v => '$' + v.toFixed(4)],
|
|
206
|
-
['tokBill', r => r.tokBillable, v => v.toFixed(0)],
|
|
207
|
-
['dur', r => r.durationSec, v => v.toFixed(0) + 's'],
|
|
208
|
-
['reads', r => r.read, v => v.toFixed(1)],
|
|
209
|
-
['cli', r => r.cliCalls, v => v.toFixed(1)],
|
|
210
|
-
['mcp', r => r.mcpCalls, v => v.toFixed(1)],
|
|
211
|
-
];
|
|
212
|
-
for (const arm of ARMS) {
|
|
213
|
-
const runs = [];
|
|
214
|
-
for (const repo of Object.keys(data)) for (const r of (data[repo][arm] || [])) runs.push(r);
|
|
215
|
-
runs.sort((a, b) => a.rep - b.rep);
|
|
216
|
-
if (!runs.length) continue;
|
|
217
|
-
for (const [name, fn, fmt] of METRICS) {
|
|
218
|
-
const line = pad(LABEL[arm], 14) + pad(name, 10);
|
|
219
|
-
const cells = [];
|
|
220
|
-
for (const n of [2, 3, 4]) {
|
|
221
|
-
if (runs.length >= n) {
|
|
222
|
-
const sub = runs.slice(0, n);
|
|
223
|
-
cells.push(pad(fmt(avg(sub, fn)), 12));
|
|
224
|
-
} else {
|
|
225
|
-
cells.push(pad('-', 12));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
console.log(line + cells.join(''));
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
console.log(`\nresults.jsonl: ${RESULTS}`);
|