Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .claude/hooks/session-start-build-ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ if ! command -v cargo >/dev/null 2>&1; then
exit 0
fi

# Resolve any unresolved Git LFS pointer stubs (assets/potion-code-16m/* —
# the vendored embedding model — and .ci-bin/**/ci) BEFORE building. Real
# incident this guards against: a checkout without git-lfs installed leaves
# ~130-byte pointer text in place of real file content; `cargo build` still
# succeeds (it just bakes that pointer text into the binary via
# `include_bytes!`), and the failure only surfaces later, silently, as
# `embeddings_status: "failed"` at runtime — not a build error, so nothing
# here would have caught it otherwise. Every step is best-effort and
# non-fatal (`|| true`) — this hook must degrade, not break the session, and
# `Embedder::load`'s own runtime fallback (network download) plus
# `embeddings_status: "offline_unavailable"` messaging are the safety net if
# this doesn't fully resolve it (e.g. no apt, no network, git-lfs install
# blocked). See docs/cloud-environment-setup.md for the full picture.
if command -v git >/dev/null 2>&1 && grep -q 'filter=lfs' .gitattributes 2>/dev/null; then
if ! git lfs version >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1; then
apt-get install -y git-lfs >/dev/null 2>&1 || true
fi
if git lfs version >/dev/null 2>&1; then
git lfs pull >/dev/null 2>&1 || true
fi
fi

build_output=$(cargo build --quiet -p ci-cli 2>&1)
build_status=$?

Expand Down
5 changes: 5 additions & 0 deletions .codegraph/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CodeGraph data files — local to each machine, not for committing.
# Ignore everything in .codegraph/ except this file itself, so transient
# files (the database, daemon.pid, sockets, logs) never show up in git.
*
!.gitignore
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
"command": "uvx",
"args": ["--from", "semble[mcp]", "semble"]
},
"codegraph": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@colbymchenry/codegraph", "serve", "--mcp"],
"env": {
"CODEGRAPH_MCP_TOOLS": "explore,node,search,callers,callees,impact,files,status"
}
},
"ci": {
"type": "stdio",
"command": "bash",
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,15 @@ agent: "tôi cần sửa hàm getUserByEmail"
còn phụ thuộc extension C nào, nên hoạt động giống hệt trên mọi platform release (trước đây
`sqlite-vec` không compile được trên musl libc, khiến bản Linux/Docker bị tắt semantic). Model mặc
định (`minishlab/potion-code-16M`, MIT license) được vendor sẵn vào binary lúc compile
(`crates/ci-core/assets/potion-code-16m/`, qua Git LFS) — load model mặc định không cần mạng, chỉ
model tuỳ biến qua `semantic_search.model` mới tải từ HuggingFace Hub.
(`crates/ci-core/assets/potion-code-16m/`, qua Git LFS) — load model mặc định thường không cần
mạng. Nếu asset vendor bị hỏng/thiếu (vd checkout thiếu `git-lfs` nên còn nguyên LFS pointer thay
vì nội dung thật — không giả định, đã xảy ra thật), `Embedder::load` tự fallback sang tải model
mặc định đó qua HuggingFace Hub 1 lần rồi cache local, thay vì `embeddings_status` treo ở
`"failed"` vĩnh viễn; set `semantic_search.allow_network_fallback: false` để tắt hẳn fallback này
và giữ đúng zero-network tuyệt đối (lúc đó status báo `"offline_unavailable"` thay vì mập mờ). Model
tuỳ biến qua `semantic_search.model` luôn tải từ HuggingFace Hub như trước, không đổi. Lưu ý: đây
chỉ là tải 1 file model tĩnh, công khai — không liên quan tới cam kết "không gọi ra ngoài" của `ci`
(cam kết đó là về code/dữ liệu repo, không phải về việc tải asset).
- **Grep/glob thật, quét trực tiếp trên đĩa** — `search(kind="grep")` dùng regex thật (crate `regex`)
+ glob filter (`globset`) qua walker tôn trọng `.gitignore`/`.git/info/exclude` thật (crate
`ignore`), không qua FTS/DB nên phủ được cả file indexer không parse (`Cargo.toml`, `docs/*.md`).
Expand Down
27 changes: 22 additions & 5 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@ thật; khi số đo ra ngoài kỳ vọng (vd B6 `find_callers` = 0%), báo cá
| B6 | Tool-Call Efficiency | Số round-trip naive vs 1 MCP call (ý tưởng từ CodeGraph) | **Implemented** — [`b6_tool_call_efficiency/`](b6_tool_call_efficiency/) |
| B7 | Task Correctness / Regression | Agent thật làm refactor, có/không `edit_context`+`diff_impact`, đếm callsite bị bỏ sót (ý tưởng từ Serena) | Planned |
| B8 | Model-Tier Leveling | Model rẻ + ci tools vs model đắt không có tools, cùng task (ý tưởng từ GitNexus) | Planned |
| B9 | Scaling Curve | Lợi thế `ci` co giãn theo quy mô repo (nhỏ → lớn) | Planned |
| B10 | Real Competitor A/B | `ci` vs CodeGraph vs Semble — tool call thật trên cả 3 MCP server thật (không phải số tự báo cáo) | **Implemented** — [`b10_real_competitor_ab/`](b10_real_competitor_ab/) |

Nguồn cảm hứng B6-B8: xem phần "Nghiên cứu competitor" bên dưới.
Nguồn cảm hứng B6-B9: xem phần "Nghiên cứu competitor" bên dưới. Khác với B6 (dùng ý tưởng đo của
CodeGraph nhưng chỉ chạy `ci`), B10 cài thật CodeGraph + Semble và gọi tool thật của cả 3 — xem
B10's README cho lý do vì sao ratio thô không nên đọc như bảng xếp hạng.

## Hạ tầng dùng chung — `lib/`

`mcp_client.py` (MCP stdio client), `tasks.yaml` (task definitions cho B4/B6), `naive_workflow.py`
(mô phỏng naive cat/grep + đếm call, cộng `naive_grep_ranked_files` cho baseline ranking của B3)
nằm ở `benchmarks/lib/`, dùng chung — không định nghĩa lại task hay logic mô phỏng ở mỗi benchmark.
`mcp_client.py` (MCP stdio client cho `ci`), `generic_mcp_client.py` (client tổng quát cho MCP
server bất kỳ — CodeGraph, Semble, dùng ở B10), `tasks.yaml` (task definitions cho B4/B6/B10),
`competitor_tasks.yaml` (mapping cùng task id đó sang tool call của CodeGraph/Semble, dùng ở B10),
`naive_workflow.py` (mô phỏng naive cat/grep + đếm call, cộng `naive_grep_ranked_files` cho
baseline ranking của B3) nằm ở `benchmarks/lib/`, dùng chung — không định nghĩa lại task hay logic
mô phỏng ở mỗi benchmark.

## Chạy benchmark

Expand All @@ -36,6 +43,10 @@ benchmarks/.venv/bin/python benchmarks/b2_call_graph_quality/run_benchmark.py
benchmarks/.venv/bin/python benchmarks/b3_search_quality/run_benchmark.py
benchmarks/.venv/bin/python benchmarks/b4_token_efficiency/run_benchmark.py
benchmarks/.venv/bin/python benchmarks/b6_tool_call_efficiency/run_benchmark.py

# B10 cần thêm CodeGraph cài thật + index build sẵn (Semble tự tải qua uvx, không cần bước riêng):
npm i -g @colbymchenry/codegraph && codegraph init
benchmarks/.venv/bin/python benchmarks/b10_real_competitor_ab/run_benchmark.py
```

`benchmarks/.venv/` và `results.json` không commit (xem `.gitignore`) — kết quả phụ thuộc vào
Expand All @@ -50,7 +61,13 @@ trạng thái index tại thời điểm chạy, chạy lại để lấy số m
bước thủ công dễ sai → 1 call) → nguồn gốc B7.
- **GitNexus** — nhấn mạnh model yếu vẫn dùng được nhờ tool đã tiền xử lý cấu trúc → nguồn gốc B8.
- **Semgrep** — bài học về minh bạch: số official (250% true-positive) bị audit độc lập chỉ ra chỉ
50-71%. Áp dụng: không che số xấu (B6 `find_callers` = 0% được giữ nguyên, không loại khỏi báo cáo).
50-71%. Áp dụng: không che số xấu (B6 `find_callers` = 0% được giữ nguyên, không loại khỏi báo cáo;
B10 Semble 2/4 task đánh dấu `unsupported` nhưng vẫn đo, không loại khỏi bảng).

B6-B9 dùng số liệu công khai của competitor làm nguồn cảm hứng phương pháp, không phải A/B trực
tiếp. **B10 là A/B trực tiếp thật** — cài CodeGraph + Semble thật, gọi tool thật, trên cùng self-repo
corpus với B4/B6 — nên đọc B10 khi cần số so sánh thật giữa `ci` và 2 tool kia, đọc B6 khi chỉ cần
hiểu ý tưởng đo tool-call efficiency.

## Phạm vi hiện tại

Expand Down
95 changes: 95 additions & 0 deletions benchmarks/b10_real_competitor_ab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# B10 — Real Competitor A/B (`ci` vs CodeGraph vs Semble)

Khác với `docs/comparison.md` (dựa trên tài liệu công khai của từng dự án) và B6 (lấy "ý tưởng"
từ cách CodeGraph báo cáo số của họ), benchmark này chạy **tool call thật** trên **cả 3 MCP
server thật** (`ci`, [CodeGraph](https://github.com/colbymchenry/codegraph) v1.2.0, Semble) —
cùng self-repo corpus, cùng 4 task với B4/B6 (`../lib/tasks.yaml` + `../lib/competitor_tasks.yaml`),
đo 3 chiều: token cost, tool-call count, và độ chính xác trên task `find_callers`.

## Chạy

```bash
npm i -g @colbymchenry/codegraph # 1 lần
codegraph init # build .codegraph/ tại repo root — 1 lần, ~1s cho self-repo
cargo build --release -p ci-cli # nếu chưa build
benchmarks/.venv/bin/python benchmarks/b10_real_competitor_ab/run_benchmark.py
```

Semble không cần cài riêng — `uvx --from semble[mcp] semble` tự tải + cache môi trường ở lần
chạy đầu (giống cách `.mcp.json` khai báo server `semble` trong repo này).

## Kết quả (self-repo, 1 run — xem giới hạn bên dưới)

| Task | naive tok | `ci` tok (ratio) | CodeGraph tok (ratio) | Semble tok (ratio) |
|---|---|---|---|---|
| read_one_function | 18,543 | 962 (19.3x) | 1,470 (12.6x) | 214 (86.6x) |
| find_callers | 149 | 302 (0.5x) | 54 (2.8x) | 863 (0.2x) *unsupported* |
| pre_edit_blast_radius | 43,476 | 2,366 (18.4x) | 77 (564.6x) | 735 (59.2x) *unsupported* |
| locate_and_inspect | 27,423 | 5,660 (4.8x) | 3,311 (8.3x) | 482 (56.9x) |

median ratio: `ci` 11.6x · CodeGraph 10.4x · Semble 58.0x
mean ratio: `ci` 10.7x · CodeGraph 147.1x · Semble 50.7x

### Accuracy — `find_callers` (collect_source_files) vs grep oracle

Oracle (đếm call site thật bằng `grep -rn 'collect_source_files(' crates --include=*.rs`, loại
dòng định nghĩa/comment): 2 file gọi — `crates/ci-core/src/indexer/pipeline.rs` và
`crates/ci-server/src/tools/recover.rs` (khác crate, gọi qua fully-qualified path
`ci_core::indexer::pipeline::collect_source_files`).

| Tool | Recall | Ghi chú |
|---|---|---|
| `ci` | 2/2 | Bắt được cả caller khác-crate qua fully-qualified path |
| CodeGraph | 1/2 | **Bỏ sót** `recover.rs` — cross-crate call qua fully-qualified path không được resolve |
| Semble | N/A | Không có khái niệm "callers" (embedding search thuần) — task đánh dấu `unsupported`, vẫn đo token/call nhưng không tính vào accuracy |

Đây là điểm khác biệt thật, verify được, không phải suy diễn từ tài liệu marketing: CodeGraph
matching theo tên trong cùng ngữ cảnh gần thì tốt, nhưng bỏ sót lời gọi qua đường dẫn
fully-qualified xuyên crate boundary trong trường hợp cụ thể này.

## Đọc số liệu này thế nào cho đúng — đừng chỉ nhìn ratio

Ratio token **không tự nó nói lên "tool nào tốt hơn"** — 3 tool trả lời 3 mức độ khác nhau cho
cùng câu hỏi:

- **`pre_edit_blast_radius`**: CodeGraph ratio 564.6x trông như thắng áp đảo, nhưng
`codegraph_impact` trả về **danh sách symbol bị ảnh hưởng** (4 symbol, 77 token) — không kèm
source, không risk assessment, không khuyến nghị hành động. `ci`'s `edit_context` trả về
**source đầy đủ + danh sách caller + `is_hub` + `risk_assessment` + `suggested_next`** (2,366
token) — nhiều hơn vì làm nhiều việc hơn (đây chính là hard-gate trước khi sửa mà
`docs/comparison.md` ghi nhận CodeGraph không có: "read-only, không sửa file"). So ratio thô ở
đây là so token của hai loại output khác nhau về bản chất, không phải so "ai nén tốt hơn cùng
một câu trả lời".
- **`find_callers`**: cả `ci` và CodeGraph đều ratio <1x (tốn token hơn naive `grep`) — vì `grep`
không cần mở file nào (naive.type=grep, không phải grep_then_cat_matches), nên baseline đã rẻ
sẵn. Giống nhận xét trong B6: lợi thế chỉ lộ rõ khi naive cần mở nhiều file.
- **Semble** cho 2 task `unsupported` (`find_callers`, `pre_edit_blast_radius`) trả lời bằng
embedding search — có thể trông "rẻ" (735 token, ratio 59.2x) nhưng **không xác nhận được quan
hệ gọi hàm thật**, nên ratio cao ở đây không phải "hiệu quả hơn", mà là "trả lời một câu hỏi
khác, dễ hơn". Giữ nguyên trong bảng theo đúng chính sách của repo (không ẩn số xấu/số
không-so-sánh-được), nhưng đọc kèm chú thích `unsupported`.

## Giới hạn

- **N=1 run, N=4 task, self-repo only** — cùng giới hạn với B4/B6. Không có median-of-N như
phương pháp CodeGraph tự công bố (N=4 run/repo); wall-clock/cost không đo (chỉ token + call
count + accuracy).
- **Semble base image thiếu tree-sitter grammar** cho rust/python/typescript/json/bash trong môi
trường chạy benchmark này (`Language rust not found, falling back to line chunking` — xem log
stderr khi chạy `semble search` trực tiếp) — search vẫn ra kết quả đúng vị trí, nhưng chunk
boundary là line-based thay vì AST-based, có thể ảnh hưởng chất lượng snippet ở repo khác/pattern
phức tạp hơn. Không kết luận đây là giới hạn chung của Semble — có thể chỉ là thiếu dependency
trong container này.
- **`find_callers` accuracy oracle** dùng grep đơn giản (loại dòng `fn `/comment) — đủ cho 1
symbol nhỏ trong Rust, không phải oracle tổng quát (không tính polymorphism, macro, re-export).
- CodeGraph's 7 tool phụ (`node`/`search`/`callers`/`callees`/`impact`/`files`/`status`) mặc định
**ẩn** trừ khi set `CODEGRAPH_MCP_TOOLS` — benchmark này bật hết để so 1-1 với tool tương ứng
của `ci`; một agent dùng CodeGraph mặc định (chỉ `codegraph_explore`) sẽ có số khác.

## File liên quan

- `../lib/generic_mcp_client.py` — MCP stdio client tổng quát (không hardcode `ci serve`), dùng
cho cả CodeGraph và Semble.
- `../lib/competitor_tasks.yaml` — mapping task → tool call cho CodeGraph/Semble, cùng task id với
`../lib/tasks.yaml`.
- `results.json` — không commit (xem `.gitignore`), chạy lại để lấy số mới nhất.
Loading
Loading