GREAT LOTUS · NIRVANA FRAMEWORK · HẠ TẦNG TRA CỨU MÃ NGUỒN CHO AI AGENT
B38 LOTUS CODE GRAPH
GIÁO TRÌNH QUẢN TRỊ & VẬN HÀNH
Đồ
thị ký hiệu AST cho toàn bộ kho mã Great Lotus và CMEV
Máy
chủ MCP cho Claude Code và Gemini Worker · REST API · Cổng
quản trị Web
Mổ xẻ từng thành phần giao diện ·
Quét và bảo trì chỉ mục · Bẫy đã trả giá và cách
chữa
Trang Dashboard của B38 tại http://172.16.10.220:20380/ — chụp bằng Chrome thật trên B35 lúc 23 giờ 29 ngày 19/09/2026.
Hạng mục |
Nội dung |
Đối tượng đọc |
Người vận hành hệ Great Lotus; kỹ sư muốn tra cứu mã nguồn nhanh mà không đọc cả file; người điều phối AI Agent (Claude Code, Gemini Worker qua agy); học viên đào tạo nội bộ |
Phiên bản tài liệu |
Bản 1 — biên soạn ngày 19–20/09/2026, đối chiếu trực tiếp mã nguồn và hệ thống đang chạy trên HP1 |
Đối tượng mô tả |
Container lotus_code_graph (B38) · IP 114.114.114.38 · cổng máy chủ 20380 · image lotus/code_graph:1.0 · API tự khai version="2.0.0" |
Nguồn sự thật |
B38_Code_Graph/app/{server,graph_engine,scanner,mcp_stdio,cli}.py (3 426 dòng), Dockerfile, docker-compose.yml, data/code_graph.sqlite, data/usage.jsonl, và ảnh chụp hệ thống sống qua Chrome điều khiển bằng B35 |
Nơi lưu tài liệu |
DCP_PRODUCTION/B38_Code_Graph/Giao_Trinh/ — .docx + .pdf + html/, cùng bố cục với giáo trình B36, B30, C11, C12, C13 |
GHI CHÚ · CÁCH ĐỌC GIÁO TRÌNH NÀY Mỗi màn hình được chụp từ hệ thống ĐANG CHẠY và đánh số bằng vòng tròn chỉ tô viền, đặt bên ngoài phần tử để không che chi tiết. Ngay sau mỗi hình là bảng giải thích từng thành phần mang số trên hình đó — không bỏ sót nút, ô, cột hay thẻ nào. ▸ Hộp NGUY HIỂM màu đỏ = thao tác làm mất dữ liệu hoặc mở đường cho mất dữ liệu. ▸ Hộp ĐÚNG THIẾT KẾ màu xanh lá = hành vi trông như lỗi nhưng là chủ ý của người viết. ▸ Hộp LƯU Ý màu vàng = chỗ dễ hiểu sai, đã có người vấp. ▸ Mọi giờ trong sách là giờ Việt Nam (UTC+7) — cũng là múi giờ container B38 đang chạy. |
B38 sinh ra để trả lời một câu hỏi rất cụ thể: khi một AI Agent (hoặc một người) cần biết save_file_graph nằm ở đâu, ai gọi nó, nó gọi những ai — thì phải đọc bao nhiêu dòng mã? Với grep, câu trả lời là hàng nghìn dòng đổ vào cửa sổ ngữ cảnh, phần lớn là chú thích và chuỗi log trùng chữ. Với B38, câu trả lời là vài dòng chữ ký hàm lấy thẳng từ cây cú pháp trừu tượng (AST) đã được lập chỉ mục sẵn trong SQLite.
Cuốn sách này mô tả B38 đúng như nó đang chạy, không phải như nó nên chạy. Nghĩa là sách nói cả những chỗ hệ thống làm tốt (một tầng dữ liệu duy nhất cho ba cửa vào; chỉ mục 2 222 file trả lời dưới 15 ms) lẫn những chỗ nó hớ (chỉ mục không tự cập nhật; nút Sao Chép trên trang Web không hoạt động khi mở bằng địa chỉ IP; một lời gọi MCP thiếu tham số có thể xoá sạch chỉ mục của một dự án).
Mỗi con số, mỗi tên thành phần trong giáo trình này truy được về một trong ba nguồn:
Mã nguồn đang chạy — đọc thẳng file app/*.py trong thư mục B38_Code_Graph tại thời điểm biên soạn, kèm số dòng, chứ không nhớ lại theo trí nhớ.
Số đo trên hệ thống sống — gọi curl vào REST API, truy vấn chỉ đọc file SQLite, bấm nút thật trên giao diện và bấm đồng hồ.
Ảnh chụp hệ thống sống — chụp bằng Chrome thật trên B35 trong hai khung giờ 23:29 và 23:36–23:40 ngày 19/09/2026.
Chỗ nào không truy được về một trong ba nguồn ấy thì bỏ hẳn, không viết cho đủ trang. Vài kết luận trong sách được rút ra bằng thí nghiệm có kiểm soát — nói rõ ở chỗ đó là thí nghiệm gì, chạy trên bản sao nào, kết quả ra sao (xem Chương 19 và Phụ lục A).
ĐÚNG THIẾT KẾ · NHỮNG THAO TÁC THẬT ĐÃ THỰC HIỆN KHI BIÊN SOẠN Giáo trình này không chỉ đọc mã — nó bấm thử. Trong quá trình biên soạn, các thao tác sau đã chạy thật trên hệ thống sản xuất và đều đã trả hệ thống về nguyên trạng: ▸ Thêm một dự án thử B38_TU_QUET (5 file), quét, rồi xoá hẳn — cuối buổi hệ thống lại còn đúng 2 dự án như trước. ▸ Bấm Quét Nhanh thật cho GreatLotus_Workspace (chỉ mục khi đó đã cũ 6 ngày): 2 110 file, 13 file mới, 24 file cập nhật, hết 2,45 giây. ▸ Các hộp thoại phá huỷ (Xoá dự án, Full Scan) được chụp ở trạng thái đang hỏi rồi bấm Hủy — window.fetch đã bị chặn trước đó nên dù bấm nhầm cũng không có lệnh nào đi ra. ▸ Thí nghiệm về bẫy code_graph_scan_project chạy trên bản sao cơ sở dữ liệu trong thư mục tạm, không đụng file thật. |
Bảng dưới là ảnh chụp trạng thái ngay trước khi chụp màn hình (23:25 ngày 19/09/2026) và ngay sau lần Quét Nhanh (23:39). Ảnh trong Phần III mang những con số của cột trước khi quét; đó là chủ ý, để Chương 15 minh hoạ được sự khác biệt.
Chỉ số |
Trước Quét Nhanh (23:25) |
Sau Quét Nhanh (23:39) |
Nguồn |
Số dự án |
2 |
2 |
GET /api/v1/stats |
Tổng số file trong chỉ mục |
2 209 |
2 222 |
GET /api/v1/stats |
Tổng số ký hiệu (symbols) |
15 606 |
15 887 |
GET /api/v1/stats |
Tổng số cạnh (edges) |
128 270 |
130 213 |
GET /api/v1/stats |
GreatLotus_Workspace |
2 097 file · quét 13/09 20:37 |
2 110 file · quét 19/09 23:39 |
GET /api/v1/projects |
CMEV_Workspace |
112 file · quét 13/09 20:22 |
không đổi |
GET /api/v1/projects |
Thời gian container đã chạy |
529 601 giây ≈ 6,1 ngày |
như cũ |
GET /health |
Bộ nhớ container |
62,93 MiB / 15,51 GiB (0,13 % CPU) |
như cũ |
docker stats |
Kích thước code_graph.sqlite |
30 MB (+ WAL 6,3 MB) |
như cũ |
ls -la data/ |
Phiên bản chạy trong container |
Python 3.12.14 · FastAPI 0.141.1 · uvicorn 0.52.3 |
như cũ |
docker exec lotus_code_graph python -c … |
Không phải tài liệu chuẩn MCP — sách chỉ mô tả đúng phần giao thức mà B38 hiện thực (initialize, tools/list, tools/call, ping).
Không phải giáo trình về AST Python — sách mô tả B38 dùng ast như thế nào, không dạy lại module ast.
Không thay thế giáo trình các hệ khác — B43 (máy ảo), B36 (profile), C11/C12/C13 (thư viện n8n) có giáo trình riêng; xem docs/html_doc/index.html.
⟳ Mở bằng Microsoft Word rồi bấm Ctrl+A → F9 để sinh mục lục kèm số trang.
PHẦN I
TỔNG QUAN
B38 là một dịch vụ Docker chạy thường trực trên HP1, làm đúng ba việc:
1. Quét toàn bộ mã nguồn của các thư mục được giao (hiện là repo Great Lotus và workspace CMEV), phân tích cú pháp từng file và ghi ra một đồ thị: các ký hiệu (class, hàm, phương thức) và các cạnh nối chúng (gọi hàm, import, kế thừa).
2. Lưu đồ thị đó vào một file SQLite duy nhất, có chỉ mục, để tra cứu mất vài mili-giây.
3. Phục vụ việc tra cứu qua ba cửa: REST API cho script, MCP cho AI Agent, và một trang Web quản trị cho người.
Tên gọi trong hệ thống: container lotus_code_graph, hostname CODE_GRAPH, IP nội bộ 114.114.114.38, cổng máy chủ 20380 — đúng quy ước Bxx ⇒ 114.114.114.xx ⇒ 20xx0 của Great Lotus.
Hình 1.1. Kiến trúc tổng thể: một container, một file SQLite, ba cửa vào. Đường màu đỏ là lối đi đặc biệt — Gemini Worker chạy tiến trình MCP stdio NGAY TRÊN HOST và mở thẳng file SQLite, không đi qua container. Nguồn: docker-compose.yml, app/server.py, app/mcp_stdio.py, ~/.gemini/config/mcp_config.json.
Repo Great Lotus có hơn 1 300 file Python. Khi một AI Agent phải trả lời “hàm này ai gọi?” mà chỉ có grep, nó phải kéo về hàng trăm dòng văn bản rồi tự đoán dòng nào là lời gọi thật, dòng nào là chú thích hay chuỗi log. Mỗi lần như vậy đốt vài nghìn token ngữ cảnh — thứ đắt nhất trong một phiên làm việc của agent.
Bảng dưới là số đo thật, chạy ngày 20/09/2026 trên chính HP1: mỗi truy vấn REST lặp 5 lần lấy trung vị, còn grep -rn … --include=*.py chạy từ gốc repo.
Câu hỏi |
grep: thời gian · kết quả · dung lượng |
B38: thời gian · kết quả · dung lượng |
Chênh lệch |
Ai gọi save_file_graph? |
697 ms · 6 dòng · 878 byte (gồm cả dòng định nghĩa và dòng chú thích) |
59 ms · 1 lời gọi thật · 613 byte |
ít hơn 6 lần dữ liệu, không lẫn định nghĩa |
Ai gọi scan? |
702 ms · 635 dòng · 97 298 byte |
61 ms · 7 lời gọi · 3 199 byte |
ít hơn 30 lần dung lượng |
WorkspaceScanner định nghĩa ở đâu? |
691 ms · 23 dòng · 3 815 byte |
10 ms · 5 ký hiệu kèm chữ ký hàm · 2 429 byte |
kèm luôn chữ ký và số dòng |
File server.py có gì bên trong? |
phải mở cả file: 1 590 dòng |
6 ms · 25 ký hiệu (outline) · 11 525 byte |
không phải đọc cả file |
LƯU Ý · CON SỐ 97 KB KIA NÓI LÊN ĐIỀU GÌ grep scan trả về 635 dòng vì chữ scan xuất hiện trong tên biến, chú thích, chuỗi log, tên file và cả trong thư viện youtube-dl nhúng sẵn. B38 trả 7 vì nó chỉ đếm cạnh calls có đích khớp tên hàm trong cây cú pháp. Đây chính là lý do CLAUDE.md và GEMINI.md đều có luật ưu tiên B38 hơn grep. |
Câu hỏi |
Công cụ MCP |
REST tương đương |
Trả về gì |
Ký hiệu tên X định nghĩa ở đâu? File tên X ở đâu? |
code_graph_search_symbol |
GET /api/v1/search |
loại, tên đầy đủ, chữ ký, docstring, file và khoảng dòng |
Ai gọi hàm X? |
code_graph_get_callers |
GET /api/v1/callers |
file, số dòng, ký hiệu chứa lời gọi |
Hàm X gọi những ai? |
code_graph_get_callees |
GET /api/v1/callees |
danh sách lời gọi bên trong, theo dòng |
File X import gì, và ai import X? |
code_graph_get_dependencies |
GET /api/v1/dependencies |
hai chiều import |
File X có cấu trúc thế nào? |
code_graph_get_structure |
GET /api/v1/structure |
cây class/hàm/phương thức theo thứ tự dòng |
NGUY HIỂM · B38 KHÔNG PHẢI CÔNG CỤ TÌM CHỮ B38 chỉ biết những gì bộ phân tích cú pháp hiểu được: class, hàm, phương thức, import, kế thừa, lời gọi. Nó không tìm được chuỗi trong chú thích, khoá trong file cấu hình, thông điệp log, biến toàn cục, hay bất cứ thứ gì trong file .md, .txt, .html, .css. Những việc đó vẫn là của grep. ▸ Trên máy này grep là ugrep --ignore-files nên bỏ qua file bị gitignore; phần lớn mã trong Application/ và Library/ là gitignored ⇒ phải gõ command grep -rn mới thấy. |
Hình 1.2. Sơ đồ quyết định, rút từ giới hạn thật của bộ trích xuất (Chương 8) chứ không từ khẩu hiệu. Ô đỏ bên dưới là luật quan trọng nhất khi làm việc với B38.
Dự án |
Thư mục trong container |
Thư mục thật trên HP1 |
Quy mô (19/09/2026) |
GreatLotus_Workspace |
/workspace (chỉ đọc) |
/root/BUDDHA/GREAT_LOTUS/PRODUCTION/PRODUCTION_HOST_PC |
2 110 file · 14 545 ký hiệu · 117 649 cạnh |
CMEV_Workspace |
/workspace_cmev (chỉ đọc) |
/root/BUDDHA/000_CMEV |
112 file · 1 342 ký hiệu · 12 564 cạnh |
Dự án CMEV_Workspace được thêm ngày 13/09/2026, khi workspace CMEV tách khỏi repo Great Lotus. Thêm một dự án nằm ngoài REPO_ROOT luôn cần hai việc: thêm một volumes: vào docker-compose.yml rồi up -d (để tạo lại container), sau đó mới khai báo dự án trên trang Web. Chi tiết ở Chương 15.
Người dùng |
Đi vào bằng đường nào |
Cấu hình ở đâu |
Dùng để làm gì |
Claude Code (Leader trên HP1) |
MCP SSE http://127.0.0.1:20380/mcp/sse |
~/.claude.json → mcpServers.lotus-code-graph |
tra ký hiệu / callers trước khi sửa mã |
Gemini Worker (qua agy) |
MCP stdio: python3 app/mcp_stdio.py chạy trên host |
~/.gemini/config/mcp_config.json |
worker tự tra cứu khi nhận task trong repo này |
Người vận hành |
trình duyệt http://172.16.10.220:20380/ |
không cần gì |
xem thống kê, thêm/quét/xoá dự án, tra cứu bằng tay |
Script / CI |
REST GET /api/v1/... hoặc python3 app/cli.py |
không cần gì |
tự động hoá, kiểm tra, xuất số liệu |
Hình 2.1. Ba cửa vào cùng gọi xuống một lớp CodeGraphDB. Đây là quyết định kiến trúc quan trọng nhất của B38: sửa một chỗ ở graph_engine.py là mọi client đều được hưởng, kể cả tiến trình stdio chạy ngoài container. Nguồn: server.py (REST và SSE) và mcp_stdio.py cùng import CodeGraphDB.
B38 chịu trách nhiệm |
B38 KHÔNG chịu trách nhiệm |
Lập chỉ mục AST và trả lời truy vấn |
Sửa mã nguồn — mọi mount workspace đều gắn cờ :ro (chỉ đọc) |
Giữ chỉ mục đúng tại thời điểm quét gần nhất |
Tự phát hiện mã vừa đổi — không có theo dõi file, không có lịch quét |
Ghi nhật ký số lượt tra cứu (usage.jsonl) |
Ghi nhật ký ai sửa gì, phiên bản mã, hay lịch sử git |
Phục vụ trong LAN |
Xác thực người dùng — không có đăng nhập, ai vào được cổng 20380 là làm được mọi thứ |
Phân tích Python đầy đủ, JS/TS ở mức nông |
Ngôn ngữ khác (C, Java, Go…) — không có bộ phân tích |
NGUY HIỂM · KHÔNG CÓ XÁC THỰC — ĐỪNG MỞ RA INTERNET Cổng 20380 mở trên 0.0.0.0 và [::], CORS đặt allow_origins=["*"], và không có bất kỳ lớp đăng nhập nào. Bất cứ ai gọi được cổng này đều có thể xoá vĩnh viễn chỉ mục của một dự án bằng một lệnh DELETE. Hiện B38 không có tuyến Caddy công khai (khác với B36 và B43) — hãy giữ nguyên như vậy. |
Hình 2.2. Vị trí B38 trong mạng HP1. Quy ước cổng và IP giống mọi Bxx khác; điểm khác biệt duy nhất là B38 không có tuyến công khai. Nguồn: docker-compose.yml, lệnh docker port lotus_code_graph, docs/network/network-overview.md.
B38 là hạ tầng hỗ trợ phát triển, không nằm trên đường chạy nghiệp vụ. Nếu B38 chết, workflow n8n, worker GPM, IP Pool… vẫn chạy bình thường; chỉ có AI Agent và người phải quay về grep. Đây là lý do B38 không được đặt trong bất kỳ chuỗi phụ thuộc depends_on nào.
B38 có repo git riêng (github.com/githublotus/B38_Code_Graph), tách khỏi repo Great Lotus. Toàn bộ lịch sử chỉ có 6 lần ghi nhận — một dịch vụ nhỏ, nhưng mỗi mốc đều gắn với một bài học.
Ngày |
Mốc |
Nội dung |
15/08/2026 |
af94dae · cd65fbb V0.0.1 |
Dựng bộ khung: graph_engine, scanner, server, mcp_stdio, cli. Lần quét đầu tiên ghi dấu indexed_at sớm nhất còn lại trong CSDL: 15/08 21:00 |
16/08/2026 |
45abb0c |
Bỏ theo dõi file .pyc |
17/08/2026 |
9c65cb5 |
Hoàn thiện bộ trích xuất AST; thêm khả năng tìm theo TÊN FILE/MODULE (search_files, kind="module") — trước đó gõ đúng tên module vẫn ra 0 kết quả |
17/08/2026 |
(cấu hình, không nằm trong git) |
Phát hiện MCP chưa từng được đăng ký trong ~/.claude.json dù CLAUDE.md đã bắt ưu tiên dùng — đăng ký xong phải khởi động lại phiên Claude Code |
18–20/08/2026 |
4de987e |
Thêm bộ đếm lượt truy vấn @_count_query ở tầng CodeGraphDB — đếm được cả ba cửa vào, ghi ra data/usage.jsonl |
13/09/2026 |
883ccaa + cấu hình compose |
Tách workspace CMEV: thêm mount /workspace_cmev và dự án CMEV_Workspace (112 file, quét hết 2,578 giây) |
MẸO · SỬA MÃ B38 KHÔNG CẦN BUILD LẠI IMAGE Thư mục app/ được bind-mount vào container ở chế độ chỉ đọc (${DCP_ROOT}/B38_Code_Graph/app:/app/app:ro). Sửa file .py trên host rồi docker restart lotus_code_graph là đủ. Chỉ khi đổi requirements.txt hoặc Dockerfile mới cần docker compose build. |
Bộ đếm bật từ 20/08/2026. Đến trước lúc biên soạn (23:25 ngày 19/09), usage.jsonl có đúng 24 bản ghi, trong đó 22 bản ghi là get_stats (14 lượt) và list_projects (8 lượt) — tức là do chính trang Dashboard tự gọi mỗi khi có người mở trang. Chỉ 2 lượt là tra cứu ký hiệu thật, cả hai vào ngày 20/08.
Hình 3.1. Một tháng nhật ký usage.jsonl (24 lượt). Cột xanh đậm là tra cứu thật; phần xám là trang Dashboard tự gọi khi mở. Nguồn: B38_Code_Graph/data/usage.jsonl, các bản ghi trước 19/09/2026 23:25.
Riêng phiên biên soạn giáo trình này (19–20/09) đã ghi thêm 147 lượt, trong đó 28 lượt search_symbols, 9 lượt get_callers, 4 lượt get_callees, 3 lượt get_dependencies, 3 lượt get_structure. Nói cách khác: một buổi dựng tài liệu dùng B38 nhiều gấp nhiều lần cả tháng trước đó.
LƯU Ý · VÌ SAO ĐIỀU NÀY QUAN TRỌNG Luật “ưu tiên B38 hơn grep” đã nằm trong CLAUDE.md, GEMINI.md, AGENTS.md và cả system_prompt của Gemini Worker Pool. Nhưng nhật ký nói rằng luật đó hầu như không được thi hành — và đặc biệt, chưa có lượt host_stdio nào kể từ 20/08, nghĩa là Gemini Worker chưa thật sự gọi B38 lần nào trong tháng qua. ▸ Khi nghiệm thu một rule mới, hãy đo bằng nhật ký, đừng tin vào việc đã viết rule. ▸ usage.jsonl là công cụ đo sẵn có: python3 -c "..." đọc vài dòng JSONL là ra ngay (Chương 16). |
PHẦN II
KIẾN TRÚC VÀ HẠ TẦNG
Toàn bộ B38 gói gọn trong một service. Đây là khai báo thật, trích từ Application/APP_1_GREAT_LOTUS_PROJECT/App/A0_DCP_GREATE_LOTUS_NETWORK/DCP_PRODUCTION/docker-compose.yml (dòng 1268–1295):
lotus_code_graph: container_name: lotus_code_graph build: context: ${DCP_ROOT}/B38_Code_Graph image: lotus/code_graph:1.0 hostname: CODE_GRAPH volumes: - /usr/share/zoneinfo/Asia/Ho_Chi_Minh:/etc/localtime:ro - /usr/share/zoneinfo:/usr/share/zoneinfo:ro - ${DCP_ROOT}/B38_Code_Graph/app:/app/app:ro # code sua khong can build lai - ${DCP_ROOT}/B38_Code_Graph/data:/app/data # SQLite + usage.jsonl (DOC-GHI) - ${REPO_ROOT}:/workspace:ro # ma nguon Great Lotus (CHI DOC) - /root/BUDDHA/000_CMEV:/workspace_cmev:ro # workspace CMEV (tach 13/09/2026) environment: - TZ=Asia/Ho_Chi_Minh - CODE_GRAPH_DB=/app/data/code_graph.sqlite - WORKSPACE_DIR=/workspace - PROJECT_NAME=GreatLotus_Workspace - PYTHONUNBUFFERED=1 ports: - "20380:8080" restart: unless-stopped networks: GreatLotus_Net: ipv4_address: '114.114.114.38' |
Khai báo |
Ý nghĩa |
Hệ quả khi vận hành |
app:/app/app:ro |
mã nguồn nằm trên host, gắn vào chỉ đọc |
sửa .py xong chỉ cần docker restart lotus_code_graph; không cần build lại image |
data:/app/data |
thư mục dữ liệu ĐỌC-GHI duy nhất |
chứa code_graph.sqlite, -wal, -shm, usage.jsonl; tiến trình trên host và trong container ghi chung |
${REPO_ROOT}:/workspace:ro |
mã nguồn gắn CHỈ ĐỌC |
B38 không thể sửa mã, kể cả khi bị lạm dụng |
TZ + 2 mount zoneinfo |
quy định UTC+7 bắt buộc của Great Lotus |
last_scan_at, indexed_at, giờ trong Dashboard đều là giờ Việt Nam |
PROJECT_NAME |
tên dự án mặc định khi quét mà không nói rõ |
mặc định là GreatLotus_Workspace |
restart: unless-stopped |
tự bật lại khi HP1 khởi động lại |
không cần thao tác tay sau khi reboot |
ports: "20380:8080" |
ánh xạ cổng |
docker port cho thấy mở trên 0.0.0.0 và [::] |
LƯU Ý · KHÔNG CÓ HEALTHCHECK, KHÔNG CÓ DEPENDS_ON Service này không khai báo healthcheck: — docker ps chỉ nói Up, không nói healthy. Muốn biết B38 sống thật hay không thì gọi GET /health (xem Chương 16). Cũng không có service nào depends_on B38, nên tắt B38 không kéo đổ thứ gì khác. |
Thành phần |
Giá trị thật (đo trong container 19/09/2026) |
Ảnh nền |
python:3.12-slim (Dockerfile) |
Python |
3.12.14 |
FastAPI |
0.141.1 (yêu cầu >=0.110.0) |
uvicorn |
0.52.3 (yêu cầu >=0.28.0) |
Thư viện khác |
pydantic>=2.6.0, python-multipart>=0.0.9, aiofiles>=23.2.1 |
Lệnh khởi động |
uvicorn server:app --host 0.0.0.0 --port 8080 (một tiến trình, một worker) |
Bộ nhớ dùng thật |
62,93 MiB trên 15,51 GiB của HP1 — 0,4 % |
Một tiến trình, một worker uvicorn: đây là chi tiết quyết định hành vi ở Chương 7 — trong lúc quét, cả máy chủ đứng im.
Toàn bộ B38 là 3 426 dòng Python chia thành 5 file, không có framework nào ngoài FastAPI.
File |
Dòng |
Vai trò |
Thành phần chính |
app/graph_engine.py |
972 |
trái tim: CSDL + hai bộ trích xuất |
SymbolNode, EdgeRelation, PythonASTExtractor, JSTSExtractor, CodeGraphDB, _count_query |
app/scanner.py |
290 |
duyệt thư mục và quyết định quét lại file nào |
IGNORED_DIRS, IGNORED_EXTENSIONS, SUPPORTED_LANGUAGES, compute_file_hash, WorkspaceScanner |
app/server.py |
1 590 |
REST + MCP SSE + trang Web (HTML nhúng thẳng trong chuỗi PORTAL_HTML) |
19 tuyến đường, record_query, normalize_container_path, PORTAL_HTML |
app/mcp_stdio.py |
428 |
máy chủ MCP chuẩn stdio + định nghĩa 9 công cụ |
TOOLS_DEFINITION, handle_tool_call, run_stdio_server |
app/cli.py |
146 |
dòng lệnh cho người và script |
scan, search, callers, deps, struct, projects, delete-project, stats |
ĐÚNG THIẾT KẾ · VÌ SAO SERVER.PY LẠI IMPORT MCP_STDIO Nhìn qua thì lạ: máy chủ HTTP đi import máy chủ stdio. Nhưng đây là chủ ý — TOOLS_DEFINITION và handle_tool_call được viết một lần rồi dùng cho cả hai giao thức MCP. Nhờ vậy danh sách công cụ mà Claude (qua SSE) và Gemini Worker (qua stdio) nhìn thấy không thể lệch nhau. |
Hàm |
Tham số |
Trả về |
Đếm lượt |
search_symbols |
query, kind, file_filter, project_name, limit, include_modules |
danh sách ký hiệu (và cả module nếu khớp tên file) |
có |
search_files |
query, project_name, limit |
file khớp theo basename, kind="module" |
không (được gọi bên trong search_symbols) |
get_callers |
symbol_name, limit |
các cạnh calls có đích khớp tên |
có |
get_callees |
file_path, symbol_full_name |
các lời gọi bên trong một ký hiệu |
có |
get_file_dependencies |
file_path |
imports của file + imported_by |
có |
get_file_structure |
file_path |
toàn bộ ký hiệu của file, theo thứ tự dòng |
có |
get_stats |
— |
tổng hợp toàn CSDL + top 10 hàm bị gọi nhiều nhất |
có |
list_projects |
— |
danh sách dự án kèm số liệu |
có |
delete_project |
name hoặc id |
số bản ghi đã xoá |
không — thao tác phá huỷ lại không ghi nhật ký |
Hình 6.1. Bốn bảng và 10 chỉ mục, tạo bởi CodeGraphDB.init_db() (graph_engine.py, dòng 441–504). Xoá theo tầng: xoá dự án thì xoá file, xoá file thì xoá ký hiệu và cạnh.
Bảng.cột |
Ý nghĩa |
Điều cần nhớ |
projects.root_path |
thư mục gốc để tính đường dẫn tương đối |
bị ghi đè mỗi lần WorkspaceScanner khởi tạo — nguồn gốc của bẫy ở Chương 19 |
projects.last_scan_at |
thời điểm quét gần nhất, giờ UTC+7 |
chỉ đổi khi quét xong; không có nghĩa là chỉ mục đúng đến giờ đó |
files.path |
đường dẫn tương đối so với root_path |
mọi truy vấn structure / dependencies phải truyền đúng dạng này; truyền đường dẫn tuyệt đối trả về rỗng |
files.mtime + files.size |
so sánh nhanh để bỏ qua file không đổi |
đây là bước 1 của Quét Nhanh |
files.hash |
md5 toàn file |
bước 2: mtime đổi nhưng nội dung không đổi thì vẫn bỏ qua |
symbols.full_name |
tên có ngữ cảnh: Lop.phuong_thuc, ham_ngoai.ham_trong |
khoá để nối với edges.source_symbol |
symbols.kind |
class function method async_function interface |
không có variable dù dataclass có khai — bộ trích xuất không sinh loại này |
edges.target_name |
đích của lời gọi/import, để nguyên dạng chuỗi |
self._copy, os.path.join, store.pull — chưa phân giải về định nghĩa thật |
edges.source_symbol |
ký hiệu chứa lời gọi, hoặc <module> nếu ở cấp file |
lời gọi viết ở thân module đều mang tên <module> |
Hạng mục |
Số đo 19/09/2026 |
code_graph.sqlite |
30 MB (7 468 trang × 4 096 byte) |
code_graph.sqlite-wal |
6,3 MB — phần thay đổi chưa gộp vào file chính |
code_graph.sqlite-shm |
32 KB |
usage.jsonl |
2 KB trước phiên biên soạn, 14 KB sau |
Tổng cả thư mục data/ |
36 MB |
NGUY HIỂM · CHÉP MỖI FILE .SQLITE LÀ SAO LƯU THIẾU SQLite ở chế độ journal_mode=WAL: những thay đổi mới nhất nằm trong file -wal chứ chưa nằm trong .sqlite. Khi biên soạn sách này, một bản sao chép bằng cp code_graph.sqlite vẫn còn dự án thử đã xoá và vẫn ghi GreatLotus_Workspace có 2 097 file (số cũ) — đúng như sự cố sao lưu profile từng gặp trước đây. ▸ Sao lưu đúng: sqlite3 data/code_graph.sqlite ".backup /noi/luu/ban_sao.sqlite", hoặc trong Python dùng conn.backup(dest). Cả hai đều gộp WAL. ▸ Hoặc chép cả ba file .sqlite, -wal, -shm khi dịch vụ đang dừng. |
Hình 7.1. Bảy bước của một lần quét, theo WorkspaceScanner.scan() (scanner.py, dòng 139–274).
Loại bỏ qua |
Danh sách thật trong mã |
Hệ quả |
Thư mục |
.git .github .gemini .claude node_modules __pycache__ .pytest_cache .mypy_cache .ruff_cache .venv venv env .idea .vscode dist build target .next .nuxt .output tmp temp coverage .turbo .cache data và MỌI thư mục bắt đầu bằng dấu chấm |
mã nằm trong thư mục tên tmp/, build/, data/ hay env/ không bao giờ vào chỉ mục — kể cả khi đó là mã thật |
Đuôi file |
chỉ nhận .py .js .jsx .mjs .cjs .ts .tsx .sh .bash .json .yaml .yml .sql |
.md, .html, .css, .txt, .env, Dockerfile, Makefile không có trong chỉ mục |
File ẩn |
tên bắt đầu bằng dấu chấm (trừ đuôi .env, mà .env lại không nằm trong danh sách đuôi) |
trên thực tế mọi dotfile đều bị bỏ |
LƯU Ý · THƯ MỤC TMP/ CỦA CHÍNH DỰ ÁN CŨNG BỊ BỎ QUA Kịch bản dựng giáo trình này nằm ở tmp/b38_gt/ nên không có trong chỉ mục. Đó là chủ ý (tránh rác), nhưng phải nhớ khi thấy search trả 0 kết quả cho một file mình vừa viết trong tmp/. |
|
Quét Nhanh (Incremental) |
Full Scan (force_full=True) |
So sánh |
mtime + size, rồi md5 |
bỏ qua mọi so sánh |
Đọc file |
chỉ file đã đổi |
mọi file |
Phân tích AST |
chỉ file đã đổi |
mọi file |
Xoá file biến mất |
có |
có |
Khi nào dùng |
thường ngày, sau khi sửa mã |
khi nghi chỉ mục sai, sau khi đổi bộ trích xuất, hoặc dựng lại từ đầu |
Phép quét |
Quy mô |
Thời gian |
Nguồn số đo |
Quét Nhanh, không có gì đổi |
2 110 file |
0,507 giây |
bấm nút trên giao diện, 19/09 23:38 |
Quét Nhanh, có 13 file mới + 24 file sửa |
2 110 file |
2,45 giây |
bấm nút trên giao diện, 19/09 23:36 |
Quét lần đầu một dự án nhỏ |
5 file (thư mục B38_Code_Graph) |
dưới 0,1 giây |
thêm dự án thử, 19/09 23:40 |
Quét lần đầu CMEV_Workspace |
112 file |
2,578 giây |
nhật ký tách workspace, 13/09 |
Phân tích lại toàn bộ (2 110 file mới hoàn toàn) |
2 110 file |
25,3 giây |
thí nghiệm trên bản sao CSDL, chạy bằng Python 3.10 của host |
NGUY HIỂM · TRONG LÚC QUÉT, B38 KHÔNG TRẢ LỜI ĐƯỢC AI scan() là hàm đồng bộ nhưng lại được gọi thẳng trong một hàm async của FastAPI (scan_project, add_project, trigger_scan). Cả vòng lặp sự kiện bị chặn: mọi truy vấn khác — kể cả /health — phải xếp hàng chờ quét xong. |
Hình 7.2. Đo thật lúc 23:36 ngày 19/09/2026: trong khi Quét Nhanh chạy 2,45 giây, một luồng phụ gọi /health mỗi 0,25 giây. Lượt gọi rơi trúng lúc quét phải chờ 2,23 giây; các lượt khác chỉ vài mili-giây.
Với 2,45 giây thì đây là phiền toái nhỏ. Nhưng một Full Scan trên kho lớn có thể mất hàng chục giây, và trong suốt thời gian đó mọi AI Agent đang hỏi B38 sẽ treo. Hãy chọn giờ vắng để Full Scan, và đừng bấm Full Scan khi một lô worker đang chạy.
Hình 8.1. Đầu ra THẬT của PythonASTExtractor và JSTSExtractor chạy trên đoạn mã minh hoạ, ngày 19/09/2026. Với Python: 3 ký hiệu và 7 cạnh. Cùng ý đó bằng JavaScript chỉ ra 3 ký hiệu và 2 cạnh — không có cạnh calls nào.
Chữ ký hàm — dựng lại đầy đủ: tham số chỉ-vị-trí, tham số thường, giá trị mặc định, *args, tham số chỉ-từ-khoá, **kwargs, chú thích kiểu và kiểu trả về. Ví dụ thật: (self, force_full: bool=False, progress_callback: Optional[Callable[[str], None]]=None) -> Dict[str, Any].
Docstring — lấy nguyên văn, cắt còn 120 ký tự khi in ra cho MCP. Trong chỉ mục hiện có 3 445 ký hiệu có docstring.
Decorator — lưu lại dạng chuỗi @ten; hiện có 964 ký hiệu mang decorator.
Kế thừa — mỗi lớp cha sinh một cạnh inherits (hiện có 1 999 cạnh loại này).
Lời gọi — mỗi ast.Call sinh một cạnh calls, kể cả gọi hàm dựng sẵn như len, str, print.
Import — import x và from a.b import c đều thành cạnh imports, giữ nguyên số dấu chấm của import tương đối.
LƯU Ý · HAI GIỚI HẠN CẦN BIẾT CỦA PHÍA PYTHON Thứ nhất: file lỗi cú pháp bị bỏ qua im lặng — except SyntaxError: pass, file vẫn được ghi vào bảng files nhưng không có ký hiệu nào. Thấy một file Python có 0 ký hiệu thì nghi ngay lỗi cú pháp (hoặc file dùng cú pháp mới hơn Python 3.12). ▸ Thứ hai: phương thức async vẫn bị ghi là method, không phải async_function — cách xác định loại chỉ xét việc nằm trong lớp hay không (graph_engine.py dòng 172–174). ▸ Biến toàn cục và hằng số không được ghi nhận, dù SymbolNode có khai loại variable. |
JSTSExtractor đọc từng dòng và dò bằng regex. Hệ quả đo được trên chỉ mục thật:
Hiện tượng |
Số đo thật |
Vì sao |
Không có cạnh calls nào từ file JS/TS |
0 cạnh calls trên 119 file .js + 4 file .ts |
mẫu regex call_pat có được khai báo nhưng không bao giờ được dùng |
line_end luôn bằng line_start |
toàn bộ 2 831 ký hiệu JS/TS |
bộ dò không biết khối lệnh kết thúc ở đâu |
Không có phương thức trong lớp |
chỉ bắt được class, function, hàm mũi tên gán biến, interface |
regex chỉ khớp các dạng khai báo trên một dòng |
Có cạnh imports và inherits |
20 cạnh imports (JS) + 10 (TS) |
hai mẫu này có được dùng thật |
ĐÚNG THIẾT KẾ · HỎI CALLERS CHO HÀM JAVASCRIPT MÀ RA RỖNG LÀ ĐÚNG THIẾT KẾ Không phải lỗi chỉ mục: B38 chưa từng sinh cạnh calls cho JS/TS. Giao diện B43 và B36 viết bằng JavaScript thuần — muốn biết ai gọi một hàm trong app.js thì vẫn phải command grep -rn. |
.json, .yaml, .yml, .sh, .sql được ghi vào bảng files nhưng không có bộ trích xuất nào, nên không sinh ký hiệu. Cụ thể trong chỉ mục hiện nay: 184 file JSON, 89 file shell, 53 file YAML — tất cả đều 0 ký hiệu. Chúng vẫn có ích: tìm theo tên file (kind="module") vẫn thấy chúng, ví dụ gõ docker-compose sẽ ra đúng file compose.
PHẦN III
GIAO DIỆN WEB — MỔ XẺ TỪNG TRANG
Cổng quản trị của B38 là một trang HTML duy nhất nhúng thẳng trong server.py (biến PORTAL_HTML, dòng 395–1585). Ba đường /, /dashboard, /explorer trả về cùng một trang; việc chuyển tab hoàn toàn do JavaScript phía trình duyệt. Không có bước đăng nhập.
Hình 9.1. Thanh đầu trang và bốn thẻ số tổng quan. Ảnh chụp 23:29 ngày 19/09/2026, trước lần Quét Nhanh — vì vậy số file là 2 209 chứ chưa phải 2 222.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Ô biểu trưng (logo gradient) |
Bấm vào quay về tab Dashboard (onclick="switchTab('dashboard')") |
cả cụm biểu trưng + tên đều bấm được, không chỉ riêng ô vuông |
2 |
Tên hệ thống Lotus Code Graph (B38) |
Nhãn nhận diện |
— |
3 |
Dòng mô tả Enterprise AST Symbol Graph & AI Agent MCP Hub |
Câu định vị sản phẩm |
— |
4 |
Tab Dashboard |
Số liệu tổng quan, danh sách dự án, nhật ký truy vấn |
mỗi lần bấm sẽ gọi lại 3 API (stats, metrics, projects) |
5 |
Tab Cấu Hình & Dự Án |
Thêm / quét / xoá dự án |
bấm vào là gọi lại GET /api/v1/projects |
6 |
Tab Hướng Dẫn & AI Prompts |
Giải thích vì sao nên dùng B38 + các đoạn cấu hình chép sẵn |
trang tĩnh, không gọi API |
7 |
Tab Tra Cứu AST |
Ô tìm kiếm ký hiệu và xem quan hệ |
trang tĩnh cho đến khi bấm Tra Cứu |
8 |
Huy hiệu CỔNG 20380 ONLINE |
Chấm xanh nhấp nháy cạnh chữ |
QUAN TRỌNG — chữ này là tĩnh — được viết cứng trong HTML, không kiểm tra gì cả. Trang còn hiện ra nghĩa là máy chủ sống, nhưng huy hiệu vẫn xanh kể cả khi API lỗi |
LƯU Ý · HUY HIỆU ONLINE KHÔNG PHẢI CHỈ BÁO SỨC KHOẺ Muốn biết B38 có thật sự khoẻ không, dùng GET /health (trả uptime_seconds, đường dẫn CSDL, giờ máy chủ) hoặc nhìn ô Tập Tin Nguồn có ra số hay không. Huy hiệu chỉ là chữ trang trí. |
Số |
Thẻ |
Lấy từ đâu |
Ý nghĩa thực tế |
9 |
Dự Án Đang Quản Lý |
stats.projects_count |
đếm số bản ghi trong bảng projects |
10 |
Tập Tin Nguồn (Files) |
stats.files_count |
đếm toàn bộ bảng files, gộp mọi dự án |
11 |
Ký Hiệu AST (Symbols) |
stats.symbols_count |
class + function + method + async_function + interface |
12 |
Quan Hệ Gọi & Imports (Edges) |
stats.edges_count |
calls + imports + inherits |
13 |
Nhãn thẻ (chữ nhỏ in hoa) |
HTML tĩnh |
tên chỉ số |
14 |
Giá trị (số lớn, phông JetBrains Mono) |
API |
có dấu phân cách hàng nghìn theo toLocaleString() của trình duyệt — hiện ra dấu phẩy kiểu Anh–Mỹ |
15 |
Dòng chú thích dưới thẻ |
HTML tĩnh |
nhắc loại dữ liệu; đây là chỗ duy nhất trong trang có biểu tượng emoji |
Trang có một điểm ngắt duy nhất ở 960 px (@media (max-width: 960px)): thanh đầu trang chuyển sang xếp dọc, dải tab cho phép cuộn ngang, lưới hai cột gộp thành một cột.
Hình 9.2. Chính trang đó ở bề ngang 390 px (khung iPhone), chụp bằng cách nhúng trang vào một iframe rộng 390 px. Bốn tab bị bóp lại và phải cuộn ngang mới thấy hết; các thẻ số vẫn đọc tốt.
ĐÚNG THIẾT KẾ · DÙNG ĐƯỢC TRÊN ĐIỆN THOẠI, NHƯNG KHÔNG PHẢI ĐỂ TRA CỨU Thẻ số và bảng dự án đọc được. Ngược lại, bảng kết quả tra cứu AST có 5 cột với đường dẫn file rất dài nên trên điện thoại phải cuộn ngang liên tục — tra cứu nên làm trên máy tính, hoặc gọi thẳng REST. |
Hình 10.1. Bảng dự án trên tab Dashboard. Các nút thao tác được đánh số ở dòng thứ hai cho dễ nhìn; dòng nào cũng có đủ hai nút như vậy.
Số |
Thành phần |
Công dụng / nội dung |
Lưu ý |
1 |
Tiêu đề Danh Sách Dự Án Đang Giám Sát |
nhãn khối |
— |
2 |
Phụ đề |
nhắc bảng này theo dõi trạng thái quét |
— |
3 |
Nút Quản Lý Cấu Hình Dự Án |
nhảy sang tab Cấu Hình & Dự Án |
chỉ chuyển tab, không gọi API |
4 |
Cột TÊN DỰ ÁN |
khoá duy nhất của dự án (projects.name) |
chính là tham số project_name khi gọi API hay MCP |
5 |
Cột ĐƯỜNG DẪN THƯ MỤC |
projects.root_path — đường dẫn bên trong container |
thấy đường dẫn kiểu /root/BUDDHA/... ở đây là dấu hiệu có ai đó vừa quét bằng tiến trình trên host (xem Chương 19) |
6 |
Cột FILES |
projects.total_files |
số file đã lập chỉ mục, không phải số file trong thư mục |
7 |
Cột SYMBOLS |
projects.total_symbols |
— |
8 |
Cột EDGES |
projects.total_edges |
thường gấp 8–10 lần số ký hiệu |
9 |
Cột LẦN QUÉT GẦN NHẤT (UTC+7) |
projects.last_scan_at |
QUAN TRỌNG — đây là chỉ số quan trọng nhất trên trang: chỉ mục cũ hơn lần sửa mã cuối cùng thì kết quả tra cứu có thể sai |
10 |
Cột THAO TÁC NHANH |
chứa hai nút bên dưới |
— |
11 |
Dòng dự án CMEV_Workspace |
một dòng cho mỗi dự án |
sắp xếp theo tên, không theo ngày quét |
12 |
Dòng dự án GreatLotus_Workspace |
— |
— |
13 |
Nút Quét Nhanh |
hỏi xác nhận rồi gọi POST /api/v1/projects/<tên>/scan với force_full=false |
trang đứng im cho đến khi quét xong rồi mới hiện hộp kết quả |
14 |
Nút Full Scan |
như trên nhưng force_full=true |
đọc và phân tích lại mọi file — chậm hơn nhiều lần, xem Chương 7 |
Hình 10.2. Hai khối nằm cạnh nhau. Bên trái là tỉ lệ file theo ngôn ngữ; bên phải là 10 tên hàm xuất hiện nhiều nhất ở vị trí *bị gọi*.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Tiêu đề Phân Bố Ngôn Ngữ & Quan Hệ Graph |
nhãn khối |
tên nói Quan Hệ Graph nhưng khối này chỉ vẽ ngôn ngữ; phân bố cạnh nằm ở API stats, không hiện ra đây |
2 |
Dòng ngôn ngữ (ví dụ PYTHON) |
tên ngôn ngữ, số file, phần trăm |
phần trăm tính trên tổng file của mọi dự án, làm tròn |
3 |
Thanh tỉ lệ |
biểu diễn phần trăm |
cùng một màu xanh cho mọi ngôn ngữ — chỉ đọc theo độ dài |
4 |
Dòng cuối (TYPESCRIPT) |
ngôn ngữ ít file nhất |
4 file nên phần trăm làm tròn thành 0 % trong khi thanh vẫn hiện — đúng thiết kế, không phải lỗi |
5 |
Tiêu đề Top 10 Ký Hiệu Được Gọi Nhiều Nhất |
nhãn khối |
— |
6 |
Cột TÊN HÀM / KÝ HIỆU |
edges.target_name nhóm lại |
là chuỗi đích chứ không phải ký hiệu đã phân giải — self.assertEqual và self._search_regex được tính là hai cái tên |
7 |
Cột SỐ LƯỢT GỌI (CALLS) |
số cạnh calls trỏ tới tên đó |
đếm trên toàn bộ các dự án |
8 |
Cột THAO TÁC |
chứa nút bên dưới |
— |
9 |
Hàng đầu tiên (str) |
hàm bị gọi nhiều nhất |
3 573 lượt — là hàm dựng sẵn của Python |
10 |
Nút Xem Callers |
nhảy sang tab Tra Cứu và gọi GET /api/v1/callers?symbol=... |
QUAN TRỌNG — chỉ trả 50 dòng đầu, xem mục 13.6 |
LƯU Ý · VÌ SAO TOP 10 TOÀN HÀM DỰNG SẴN VÀ TÊN LẠ str, print, len, int, isinstance là hàm dựng sẵn của Python — bộ trích xuất ghi mọi ast.Call nên chúng luôn đứng đầu. Ba cái tên lạ int_or_none, self._search_regex, self._match_id đến từ thư viện youtube-dl nhúng sẵn trong Application/…/youtubeDownloader/. |
Hình 10.3. Thành phần chỉ mục của GreatLotus_Workspace sau lần quét 19/09/2026: 908 file (43 %) và 53 344 cạnh (45 %) đến từ thư viện youtube-dl nhúng sẵn — thứ không ai định tra cứu. Nguồn: truy vấn chỉ đọc code_graph.sqlite.
MẸO · MUỐN TOP 10 CÓ ÍCH HƠN Loại youtube-dl ra khỏi chỉ mục bằng cách chuyển thư mục đó vào một tên bị bỏ qua (ví dụ đặt dưới tmp/), hoặc chấp nhận và luôn lọc theo project/file khi tra cứu. Sau khi loại, Top 10 sẽ là: str 3 574 · print 3 386 · len 2 265 · int 2 116 · logger.fatal 978 · isinstance 809 · exit 739 · time.time 692 · range 651 · float 648 (số đo 20/09/2026). |
Hình 10.4. Nhật ký truy vấn theo thời gian thực. Các dòng trong ảnh chính là những lệnh curl dùng để lấy số liệu cho giáo trình này, lúc 23:26–23:29 ngày 19/09/2026.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Tiêu đề khối |
nhãn |
— |
2 |
Phụ đề |
nói rõ khối ghi nhận truy vấn AST, callers và lệnh MCP |
— |
3 |
Huy hiệu N Lượt Hôm Nay |
metrics.total_queries_today |
QUAN TRỌNG — tên gọi sai: đây là bộ đếm từ lúc tiến trình khởi động, không hề reset theo ngày (server.py chỉ cộng thêm, không có mốc ngày) |
4 |
Cột THỜI GIAN (UTC+7) |
giờ ghi nhận |
chỉ có giờ-phút-giây, không có ngày |
5 |
Cột ENDPOINT / NGUỒN |
search callers callees dependencies structure mcp |
mọi lệnh MCP đều gộp chung nhãn MCP, không tách theo tên công cụ |
6 |
Cột TRUY VẤN / CHI TIẾT |
từ khoá hoặc đường dẫn, cắt còn 80 ký tự |
— |
7 |
Cột SỐ KẾT QUẢ |
số bản ghi trả về |
với dependencies là tổng hai chiều import |
8 |
Cột ĐỘ TRỄ |
thời gian xử lý phía máy chủ, mili-giây |
đo quanh lời gọi CSDL, chưa tính thời gian truyền |
9 |
Dòng mới nhất |
danh sách xếp mới nhất lên trên |
giữ tối đa 30 dòng (deque(maxlen=30)) |
NGUY HIỂM · NHẬT KÝ NÀY NẰM TRONG BỘ NHỚ, RESTART LÀ MẤT access_metrics là một biến Python trong tiến trình. docker restart lotus_code_graph ⇒ bộ đếm về 0 và 30 dòng gần nhất biến mất. Lịch sử lâu dài nằm ở file data/usage.jsonl (Chương 16) — hai thứ này khác nhau, đừng nhầm. ▸ usage.jsonl ghi cả lượt gọi từ tiến trình stdio trên host; access_metrics thì không. ▸ Ngược lại access_metrics biết tên endpoint HTTP và từ khoá; usage.jsonl chỉ biết tên hàm CSDL. |
Hình 11.1. Bảng quản lý dự án. So với bảng trên Dashboard, bảng này bỏ cột EDGES và thêm nút Xóa.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Tiêu đề Quản Lý Cấu Hình Dự Án |
nhãn khối |
— |
2 |
Phụ đề |
nhắc có thể thao tác mà không cần dòng lệnh |
— |
3 |
Nút Thêm Dự Án Mới |
mở hộp thoại thêm dự án |
xem mục 11.2 |
4 |
Cột TÊN DỰ ÁN |
projects.name |
— |
5 |
Cột THƯ MỤC MOUNT / NGUỒN |
projects.root_path |
đường dẫn dài sẽ đẩy các cột sau ra ngoài khung nhìn, phải cuộn ngang |
6 |
Cột TẬP TIN |
total_files |
— |
7 |
Cột KÝ HIỆU |
total_symbols |
— |
8 |
Cột LẦN QUÉT CUỐI (UTC+7) |
last_scan_at |
— |
9 |
Cột HÀNH ĐỘNG TRỰC TIẾP |
chứa ba nút |
— |
10 |
Nút Quét Nhanh |
quét gia tăng |
hỏi xác nhận trước |
11 |
Nút Full Scan |
quét lại toàn bộ |
hỏi xác nhận trước |
12 |
Nút Xóa (đỏ) |
xoá dự án khỏi Code Graph |
QUAN TRỌNG — xoá vĩnh viễn bản ghi trong SQLite; mã nguồn trên đĩa không bị động tới |
Hình 11.2. Khối trợ giúp nằm dưới bảng dự án: nhắc rằng thư mục ngoài /workspace phải được mount trước.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Tiêu đề khối |
nhãn |
— |
2 |
Phụ đề |
nhắc khi nào cần thêm volume |
— |
3 |
Đoạn giải thích |
nói rõ workspace đã mount ở /workspace |
— |
4 |
Khối mã mẫu |
đoạn YAML chép vào docker-compose.yml |
đoạn mẫu này không kèm ba dòng múi giờ bắt buộc của Great Lotus — khi sửa compose thật thì giữ nguyên phần environment/volumes đã có |
5 |
Nút Sao Chép |
định chép đoạn mã vào bộ nhớ tạm |
QUAN TRỌNG — không hoạt động khi mở trang bằng địa chỉ IP — xem hộp bên dưới |
NGUY HIỂM · NÚT SAO CHÉP CHẾT LẶNG TRÊN HTTP://172.16.10.220:20380 Hàm copySnippet() gọi navigator.clipboard.writeText(...). API này chỉ tồn tại trong secure context — tức HTTPS hoặc localhost. Mở trang bằng IP LAN qua HTTP thì navigator.clipboard là undefined, lời gọi ném lỗi trước khi vào .catch, nên không có cả thông báo Vui lòng copy thủ công. Người dùng bấm và không thấy gì xảy ra. ▸ Đã kiểm chứng ngày 19/09/2026 ngay trong trình duyệt: window.isSecureContext = false, typeof navigator.clipboard = undefined. ▸ Cách dùng được ngay: bôi đen đoạn mã rồi Ctrl+C, hoặc mở trang bằng http://localhost:20380 khi ngồi trực tiếp trên HP1. ▸ Muốn sửa tận gốc: bọc copySnippet bằng nhánh dự phòng document.execCommand('copy'). |
Hình 11.3. Hộp thoại thêm dự án ở trạng thái trống, với chữ gợi ý trong hai ô nhập.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Tiêu đề hộp thoại |
nhãn |
— |
2 |
Nút đóng (dấu nhân) |
đóng hộp thoại, không lưu gì |
bấm ra ngoài hộp không đóng |
3 |
Nhãn Tên Dự Án (Project Identifier) |
— |
— |
4 |
Ô nhập tên dự án |
tên duy nhất của dự án |
gõ trùng tên dự án đã có ⇒ không tạo mới mà cập nhật root_path của dự án cũ rồi quét lại vào đó |
5 |
Nhãn Đường Dẫn Thư Mục Trong Container |
— |
— |
6 |
Ô nhập đường dẫn |
thư mục cần lập chỉ mục |
nhận cả đường dẫn host của repo Great Lotus — máy chủ tự đổi sang /workspace/... (hàm normalize_container_path) |
7 |
Dòng gợi ý dưới ô nhập |
nhắc workspace đã mount ở /workspace |
— |
8 |
Ô tích Tự động quét… |
bật/tắt quét ngay sau khi thêm |
mặc định bật |
9 |
Nhãn của ô tích |
chữ Tự động quét Full Scan ngay sau khi thêm |
— |
10 |
Nút Hủy Bỏ |
đóng hộp thoại |
— |
11 |
Nút Thêm Dự Án & Quét |
gọi POST /api/v1/projects/add |
trong lúc chạy, nút đổi thành Đang quét và thêm dự án… và bị khoá |
LƯU Ý · Ô TÍCH CHỈ QUYẾT ĐỊNH CÓ QUÉT HAY KHÔNG, KHÔNG QUYẾT ĐỊNH KIỂU QUÉT Dù nhãn ghi Full Scan, JavaScript luôn gửi force_full: true khi ô được tích và auto_scan: false khi bỏ tích. Không có cách chọn Quét Nhanh ở bước thêm mới — điều đó hợp lý vì dự án mới chưa có bản ghi nào để so sánh. |
Hình 11.4. Trạng thái đang chạy: nút chuyển thành *Đang quét và thêm dự án…* và bị khoá. Với thư mục lớn, trạng thái này kéo dài hàng chục giây và trong lúc đó cả B38 không trả lời ai.
Hình 11.5. Hộp thoại đã điền: tên B38_TU_QUET, đường dẫn gõ theo đường dẫn host để thử khả năng tự quy đổi.
Số |
Thành phần |
Nội dung trong ảnh |
1 |
Ô tên dự án |
B38_TU_QUET — dự án thử, đã xoá sau khi chụp xong |
2 |
Ô đường dẫn |
/root/BUDDHA/GREAT_LOTUS/PRODUCTION/PRODUCTION_HOST_PC/Application/... (đường dẫn host) |
3 |
Nút gửi |
bấm vào là gọi API ngay, không hỏi lại lần nữa |
Trang dùng confirm() và alert() của trình duyệt chứ không tự vẽ hộp thoại. Chúng hiện ở mép trên màn hình, kèm dòng địa chỉ máy chủ, và chặn toàn bộ trang cho đến khi bấm.
Hình 11.6. Hộp xác nhận khi bấm Quét Nhanh.
Số |
Thành phần |
Ghi chú |
1 |
Dòng nguồn 172.16.10.220:20380 says |
do trình duyệt thêm; nhắc hộp thoại này của trang nào |
2 |
Nội dung câu hỏi |
Bạn có muốn chạy QUÉT NHANH (Incremental) cho dự án "X"? |
3 |
Nút Cancel |
huỷ — không có lệnh nào được gửi đi |
4 |
Nút OK |
gửi POST .../scan với force_full=false; trang đứng im tới khi quét xong |
Hình 11.7. Hộp xác nhận khi bấm Full Scan — chữ khác nhưng bố cục y hệt. Bốn số mang nghĩa như hình trên.
Hình 11.8. Hộp xác nhận khi bấm Xóa. Nội dung nói rõ sẽ xoá sạch files, symbols và call edges trong SQLite.
NGUY HIỂM · ĐÂY LÀ LỚP BẢO VỆ DUY NHẤT CHO THAO TÁC XOÁ Không có bước gõ lại tên dự án, không có thùng rác, không có hoàn tác. Bấm OK là DELETE /api/v1/projects/<tên> chạy ngay và toàn bộ bản ghi của dự án biến mất khỏi SQLite. Cách khôi phục duy nhất: thêm lại dự án rồi quét lại từ đầu (mã nguồn không bị đụng tới, nên luôn dựng lại được). ▸ Nếu quét lại lâu, hãy xem trước Chương 18 về khôi phục và Chương 7 về thời gian quét. |
Hình 11.9. Bỏ trống một trong hai ô rồi bấm Thêm Dự Án & Quét: kiểm tra ngay tại trình duyệt, chưa gọi API.
Hình 11.10. Đường dẫn không tồn tại trong container: máy chủ trả mã 400 kèm hướng dẫn thêm volumes vào docker-compose. Thông báo này viết rất đúng việc — chép được ngay thành dòng YAML.
Số |
Thành phần |
Ghi chú |
1 |
Dòng nguồn |
trình duyệt tự thêm |
2 |
Nội dung lỗi |
nguyên văn từ máy chủ (HTTPException trong add_project), có cả đường dẫn đã thử và câu lệnh mount gợi ý |
3 |
Nút OK |
đóng thông báo; hộp thoại thêm dự án vẫn còn nguyên nội dung vừa gõ |
ĐÚNG THIẾT KẾ · LỖI NÀY XẢY RA TRƯỚC KHI TẠO BẢN GHI add_project kiểm tra os.path.exists(target_path) ngay đầu hàm và ném lỗi trước khi gọi get_or_create_project. Gõ sai đường dẫn không để lại dự án rác nào trong CSDL — đã kiểm chứng: sau thử nghiệm, danh sách vẫn đúng hai dự án. |
Tab này là tài liệu tự phục vụ dành cho người cấu hình AI Agent. Không gọi API nào, nội dung viết cứng trong HTML.
Hình 12.1. Khối so sánh: vì sao AI Agent nên ưu tiên Code Graph thay vì grep.
Số |
Thành phần |
Nội dung |
Đối chiếu với số đo thật |
1 |
Tiêu đề khối so sánh |
Tại Sao AI Agent Nên Ưu Tiên Dùng Code Graph Thay Vì Grep? |
— |
2 |
Ô đỏ — hạn chế của grep |
4 gạch đầu dòng, trong đó có câu tốn 5 000 – 20 000 token |
số này là ước lượng của người viết giao diện; số đo thật ở Chương 1 là 97 KB cho một lần grep scan, tương đương khoảng 25 000 token |
3 |
Ô xanh — ưu thế của B38 |
4 gạch đầu dòng: AST chuẩn xác, tiết kiệm token, đồ thị hai chiều, SQLite dưới 3 ms |
độ trễ đo thật: 6 ms (structure), 10 ms (search), 59–61 ms (callers), 125 ms (stats). Con số dưới 3 ms chỉ đúng với truy vấn nhỏ nhất |
4 |
Tiêu đề khối mẫu prompt |
Mẫu Câu Prompt / Rules Dành Cho Claude Code & Gemini |
— |
Hình 12.2. Hai đoạn cấu hình đầu: rule cho Claude Code và rule cho Gemini / Antigravity.
Số |
Thành phần |
Dùng thế nào |
1 |
Tiêu đề 1. Cấu Hình Rules Cho Claude Code (CLAUDE.md) |
— |
2 |
Khối mã rule cho Claude |
chép vào CLAUDE.md của dự án. Rule này đã có sẵn trong repo Great Lotus (mục B38 Lotus Code Graph Priority Rule) |
3 |
Nút Sao Chép |
cùng lỗi như mục 11.1 — bôi đen và Ctrl+C thay thế |
4 |
Tiêu đề 2. Cấu Hình Rules Cho Gemini / Google Antigravity |
— |
5 |
Khối mã rule cho Gemini |
chép vào GEMINI.md / AGENTS.md; repo này đã có sẵn ở mục B38 Code Graph Protocol |
6 |
Nút Sao Chép |
như trên |
Hình 12.3. Đoạn cấu hình thứ ba: khai báo MCP server dạng stdio.
Số |
Thành phần |
Dùng thế nào |
1 |
Tiêu đề 3. Cấu Hình MCP Server JSON |
— |
2 |
Khối JSON mẫu |
khai báo chạy python3 app/mcp_stdio.py kèm biến CODE_GRAPH_DB. Đây đúng là cách ~/.gemini/config/mcp_config.json đang cấu hình cho Gemini Worker |
3 |
Nút Sao Chép |
như trên |
LƯU Ý · CLAUDE CODE Ở HP1 KHÔNG DÙNG ĐOẠN JSON NÀY Mẫu trên trang là kiểu stdio (tên b38_code_graph). Thực tế ~/.claude.json khai kiểu SSE: "lotus-code-graph": {"type": "sse", "url": "http://127.0.0.1:20380/mcp/sse"}. Hai kiểu đều chạy được; khác nhau ở chỗ stdio mở thẳng file SQLite còn SSE đi qua container. ▸ Đổi cấu hình MCP xong phải khởi động lại phiên Claude Code thì công cụ mới nạp vào. ▸ Tên trong cấu hình quyết định tên tiền tố của công cụ mà agent nhìn thấy. |
Hình 13.1. Trạng thái ban đầu của tab Tra Cứu AST.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Tiêu đề Tra Cứu Ký Hiệu AST & Khám Phá Quan Hệ |
nhãn |
— |
2 |
Phụ đề |
tra cứu định nghĩa, chữ ký hàm, docstring và truy vết nơi gọi trực tiếp từ SQLite (< 3 ms) |
xem đối chiếu độ trễ thật ở Phụ lục E |
3 |
Ô nhập từ khoá |
tên hàm, lớp, phương thức, hoặc tên file |
gõ Enter cũng tra cứu; để trống thì bấm nút không làm gì |
4 |
Ô chọn Loại |
lọc theo kind |
chỉ có 4 lựa chọn: Function, Class, Method, Async Function. Thiếu module và interface — muốn lọc hai loại đó phải gọi REST |
5 |
Nút Tra Cứu |
gọi GET /api/v1/search?q=...&limit=50 |
luôn giới hạn 50 kết quả |
6 |
Vùng kết quả |
nơi đổ bảng kết quả |
ban đầu là câu nhắc Nhập từ khoá và bấm Tra Cứu |
LƯU Ý · KHÔNG CÓ Ô CHỌN DỰ ÁN Giao diện Web luôn tra trên mọi dự án. Kết quả có thể trộn lẫn file của GreatLotus_Workspace và CMEV_Workspace, và cột file chỉ hiện đường dẫn tương đối nên hai dự án trông giống nhau. Muốn lọc, gọi REST với &project=CMEV_Workspace, hoặc dùng MCP với project_name. |
Hình 13.2. Tra từ khoá WorkspaceScanner: 5 kết quả trong 14,07 ms.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Dòng tóm tắt |
Tìm thấy N kết quả (Độ trễ: X ms) |
độ trễ do máy chủ báo về |
2 |
Cột LOẠI |
huy hiệu màu theo kind |
màu: class tím, function xanh dương, method xanh lá, async_function vàng, module hồng |
3 |
Cột TÊN KÝ HIỆU |
name (tên ngắn) |
tên đầy đủ full_name chỉ dùng ngầm cho nút Callees |
4 |
Cột CHỮ KÝ (SIGNATURE) |
chữ ký hàm dựng lại từ AST |
với class là danh sách lớp cha; với module là (N symbols) |
5 |
Cột FILE & VỊ TRÍ DÒNG |
đường dẫn tương đối + L<đầu>-<cuối> |
đường dẫn dài làm bảng rộng hơn khung — phải cuộn ngang mới thấy cột thao tác |
6 |
Huy hiệu loại của dòng đầu |
ở đây là class |
— |
7 |
Dòng kết quả đầu tiên |
WorkspaceScanner trong app/scanner.py |
thứ tự ưu tiên: khớp đúng tên trước, rồi khớp tiền tố, rồi còn lại |
Hình 13.3. Chính bảng đó sau khi cuộn ngang hết cỡ: cột THAO TÁC với hai nút mỗi dòng. Vùng kết quả rộng 1 653 px trong khung 1 326 px nên hai nút này bị khuất ở màn hình 1 600 px.
Số |
Thành phần |
Công dụng |
1 |
Cột FILE & VỊ TRÍ DÒNG |
phần đuôi đường dẫn và số dòng |
2 |
Cột THAO TÁC |
chứa hai nút |
3 |
Nút Callers |
tìm mọi nơi gọi ký hiệu này (theo tên ngắn) |
4 |
Nút Callees |
liệt kê các lời gọi bên trong ký hiệu này (theo full_name + file) |
LƯU Ý · HAI NÚT QUAN TRỌNG NHẤT LẠI NẰM KHUẤT Ai mở trang lần đầu rất dễ tưởng B38 chỉ tra được định nghĩa. Nhớ cuộn ngang bảng kết quả (hoặc thu nhỏ trang bằng Ctrl và dấu trừ) để thấy cột Thao Tác. |
Hình 13.4. Gõ profile_identity — một tên file, không phải tên hàm. B38 trả về chính hai file đó với loại module và chữ ký *(N symbols)*.
Số |
Thành phần |
Ghi chú |
1 |
Dòng tóm tắt |
2 kết quả, đều là module |
2 |
Dòng 1 — profile_identity |
Library/B05_GPM_Login_Manager/profile_identity.py, 12 ký hiệu bên trong |
3 |
Dòng 2 — test_profile_identity |
file kiểm thử tương ứng trong B37_Profile_Broker/verify/ |
Khả năng này được thêm ngày 17/08/2026. Trước đó bảng symbols chỉ chứa class/hàm/phương thức, nên gõ đúng tên module lại ra 0 kết quả dù file đã được lập chỉ mục đầy đủ. Cách hiện thực: search_files() lọc theo tên file (basename) chứ không lọc theo cả đường dẫn — nếu lọc theo đường dẫn thì gõ app sẽ kéo về hàng trăm file chỉ vì trùng tên thư mục cha, phá mất chính lợi thế ít token của B38. Module được lấy trước, tối đa 5 suất, phần còn lại của limit mới dành cho ký hiệu.
Hình 13.5. Tra scan với bộ lọc Method: chỉ còn phương thức mang tên chứa *scan*.
Số |
Thành phần |
Ghi chú |
1 |
Ô chọn Loại |
đang chọn Method ⇒ thêm &kind=method vào URL |
2 |
Dòng tóm tắt |
số kết quả sau khi lọc |
3 |
Dòng đầu tiên |
mọi dòng đều mang huy hiệu xanh lá method |
ĐÚNG THIẾT KẾ · CHỌN MỘT LOẠI THÌ MẤT KẾT QUẢ MODULE Trong mã, module chỉ được chèn khi kind để trống hoặc bằng module, và khi không lọc theo file. Vậy nên lọc Method sẽ không còn dòng module nào — đúng thiết kế. |
Hình 13.6. Từ khoá không tồn tại: một dòng chữ đỏ, không có gợi ý nào thêm.
Số |
Thành phần |
Ghi chú |
1 |
Ô nhập |
giữ nguyên từ khoá vừa gõ |
2 |
Thông báo |
Không tìm thấy ký hiệu nào khớp với "..." — màu đỏ nhạt |
Hình 13.7. Cùng một thông báo, nhưng lần này là báo động giả: html_readable là file có thật, thêm vào repo ngày 18/09; chỉ mục lúc đó mới quét đến 13/09 nên chưa biết file này. Sau khi bấm Quét Nhanh (2,45 giây) thì tra lại ra ngay 1 kết quả loại module.
Hình 13.8. Dòng thời gian của chính sự việc trên. Bài học: *không tìm thấy* ở B38 luôn phải hiểu là *chưa quét* cho đến khi kiểm tra last_scan_at.
NGUY HIỂM · ĐÂY LÀ CÁI BẪY NGUY HIỂM NHẤT CỦA B38 VỚI AI AGENT Một agent hỏi B38, nhận không tìm thấy, rồi kết luận hàm này không tồn tại và viết lại từ đầu — trong khi hàm vẫn nằm đó. Luật an toàn: trước khi kết luận một ký hiệu không tồn tại, hãy xem last_scan_at và quét lại nếu chỉ mục cũ hơn lần sửa mã gần nhất. |
Hình 13.9. Callers của save_file_graph: đúng một vị trí, trong WorkspaceScanner.scan.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Dòng tóm tắt |
Có N vị trí gọi đến "X" (Độ trễ: …) |
— |
2 |
Cột VỊ TRÍ GỌI (FILE:LINE) |
file và số dòng của lời gọi |
đường dẫn tương đối |
3 |
Cột CALLER SYMBOL |
ký hiệu chứa lời gọi (edges.source_symbol) |
hiện <module> nếu lời gọi nằm ở thân file |
4 |
Cột CHI TIẾT LỆNH GỌI |
edges.details, ví dụ Call to self.db.save_file_graph |
cho biết lời gọi viết dưới dạng nào |
5 |
Dòng kết quả |
một dòng cho mỗi lời gọi |
sắp theo đường dẫn rồi tới số dòng |
Hình 13.10. Cách khớp của get_callers: theo TÊN, không theo kiểu. Đây là lý do một tên phổ biến sẽ gom cả lời gọi của lớp khác, còn lời gọi động (getattr) thì không bao giờ thấy.
Hình 13.11. Bấm Xem Callers cho str từ Top 10 (3 573 lượt gọi) nhưng bảng chỉ hiện 50 dòng, và câu tóm tắt ghi *Có 50 vị trí gọi*. Các dòng đầu lại thuộc A0_scripts/ của dự án CMEV.
Số |
Thành phần |
Ghi chú |
1 |
Dòng tóm tắt |
ghi 50 — là số dòng lấy được, không phải tổng số thật |
2 |
Dòng đầu tiên |
thuộc A0_scripts/… tức dự án CMEV_Workspace: callers không lọc theo dự án |
NGUY HIỂM · CON SỐ TRONG CÂU TÓM TẮT LÀ SỐ DÒNG TRẢ VỀ, KHÔNG PHẢI TỔNG get_callers nhận limit mặc định 50 (REST và giao diện) hoặc 40 (MCP), và giao diện in data.total — chính là độ dài mảng vừa nhận. Với hàm phổ biến, luôn phải hiểu 50 là ít nhất 50. ▸ Muốn con số thật: gọi REST với &limit=5000 rồi đếm, hoặc truy vấn thẳng SQLite. ▸ Trong sách này, số 3 573 lượt gọi str lấy từ bảng Top 10 (get_stats), nơi dùng COUNT(*) chứ không bị giới hạn. |
Hình 13.12. Callees của WorkspaceScanner.scan: 36 lời gọi bên trong, liệt kê theo số dòng.
Số |
Thành phần |
Công dụng |
Lưu ý |
1 |
Dòng tóm tắt |
X gọi N hàm/phương thức con |
N là số lời gọi, không phải số hàm khác nhau |
2 |
Cột DÒNG GỌI |
số dòng trong file |
— |
3 |
Cột HÀM ĐƯỢC GỌI (CALLEE) |
edges.target_name nguyên dạng |
self.db.get_connection, os.walk, time.time… gồm cả hàm dựng sẵn |
4 |
Cột CHI TIẾT |
edges.details |
— |
5 |
Dòng đầu tiên |
lời gọi sớm nhất trong thân hàm |
— |
LƯU Ý · CALLEES CẦN ĐÚNG HAI THAM SỐ Truy vấn này so khớp chính xác files.path = ? và edges.source_symbol = ?. Sai một dấu, thừa dấu hai chấm cuối đường dẫn, hay dùng tên ngắn thay cho full_name (ví dụ scan thay vì WorkspaceScanner.scan) đều trả về rỗng. Khi bấm nút trên giao diện thì hai giá trị này được điền tự động nên không sai. |
PHẦN IV
CÁC TÁC VỤ CHÍNH
Giao diện Web tiện cho người, nhưng phần lớn giá trị của B38 đến từ hai đường còn lại. Chương này đưa lệnh chạy được ngay, kèm kết quả thật.
Địa chỉ gốc: http://172.16.10.220:20380 (từ HP1 có thể dùng http://127.0.0.1:20380). Mọi tuyến tra cứu đều là GET, không cần khoá, không cần header.
# 1. Tim dinh nghia mot lop / ham / file curl -s "http://127.0.0.1:20380/api/v1/search?q=WorkspaceScanner&limit=10" | python3 -m json.tool
# 2. Loc theo loai va theo du an curl -s "http://127.0.0.1:20380/api/v1/search?q=scan&kind=method&project=GreatLotus_Workspace"
# 3. Chi liet ke FILE trung ten (khong lay symbol) curl -s "http://127.0.0.1:20380/api/v1/search?q=profile_store&kind=module"
# 4. Ai goi ham nay (nho nang limit neu ten pho bien) curl -s "http://127.0.0.1:20380/api/v1/callers?symbol=save_file_graph&limit=200"
# 5. Ham nay goi nhung ai (can DUNG full_name + duong dan tuong doi) curl -s "http://127.0.0.1:20380/api/v1/callees?file_path=Application/.../app/scanner.py&symbol_name=WorkspaceScanner.scan"
# 6. Import hai chieu cua mot file curl -s "http://127.0.0.1:20380/api/v1/dependencies?file_path=Library/B05_GPM_Login_Manager/profile_identity.py"
# 7. Outline mot file curl -s "http://127.0.0.1:20380/api/v1/structure?file_path=Application/.../app/scanner.py"
# 8. Suc khoe va thong ke curl -s http://127.0.0.1:20380/health curl -s http://127.0.0.1:20380/api/v1/stats | python3 -m json.tool |
MẸO · BA QUY TẮC ĐỂ KHÔNG TRẢ VỀ RỖNG Phần lớn trường hợp API trả rỗng mà chắc chắn có mã đều rơi vào ba lỗi dưới đây. ▸ Đường dẫn phải là tương đối so với root_path của dự án: Library/B05_.../profile_identity.py, không phải /root/BUDDHA/... và cũng không phải /workspace/.... Đã thử: truyền đường dẫn tuyệt đối cho structure trả về total = 0. ▸ callees cần full_name, tức WorkspaceScanner.scan chứ không phải scan. ▸ Dấu gạch dưới là ký tự đại diện trong SQL LIKE: gõ get_x sẽ khớp cả getax. Hiếm khi gây hại, nhưng biết để khỏi ngạc nhiên. |
app/cli.py mở thẳng file SQLite nên vẫn chạy được ngay cả khi container đang tắt (miễn là không ai đang ghi).
cd .../DCP_PRODUCTION/B38_Code_Graph/app python3 cli.py search get_callers # tim ky hieu python3 cli.py search scan --kind method --limit 10 python3 cli.py callers save_file_graph # noi goi python3 cli.py deps app/server.py # import hai chieu python3 cli.py struct app/graph_engine.py # outline python3 cli.py projects # danh sach du an python3 cli.py stats # thong ke (in JSON) python3 cli.py scan --full --project GreatLotus_Workspace --workspace /duong/dan |
NGUY HIỂM · CLI.PY SCAN MẶC ĐỊNH QUÉT THEO ĐƯỜNG DẪN HOST DEFAULT_WORKSPACE của cli.py và mcp_stdio.py là /root/BUDDHA/GREAT_LOTUS/PRODUCTION/PRODUCTION_HOST_PC — đường dẫn trên host, không phải /workspace. Quét bằng CLI sẽ ghi đè root_path của dự án thành đường dẫn host; sau đó bấm Quét Nhanh trên Web vẫn chạy được (máy chủ tự quy đổi) nhưng bảng dự án sẽ hiện đường dẫn kiểu /root/BUDDHA/.... Xem Chương 19 để biết trường hợp tệ hơn. |
B38 hiện thực bốn phương thức JSON-RPC 2.0: initialize, tools/list, tools/call, ping (và bỏ qua notifications/initialized). Có hai kiểu vận chuyển:
Kiểu |
Đường đi |
Ai đang dùng |
Đặc điểm |
SSE |
GET /mcp/sse mở luồng sự kiện, trả về đường POST /mcp/messages?sessionId=...; kết quả gửi ngược qua luồng SSE |
Claude Code trên HP1 |
đi qua container; phiên giữ trong bộ nhớ máy chủ, gói keepalive mỗi 20 giây |
stdio |
chạy python3 app/mcp_stdio.py, trao đổi JSON theo dòng qua stdin/stdout |
Gemini Worker qua agy |
mở thẳng file SQLite trên host; không cần container chạy |
Thử tay giao thức stdio (hữu ích khi nghi ngờ worker không gọi được công cụ):
cd .../B38_Code_Graph/app printf '%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \ '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"code_graph_search_symbol", "arguments":{"query":"WorkspaceScanner","limit":3}}}' | python3 mcp_stdio.py |
Trong phiên Claude Code, các công cụ hiện ra dưới tên code_graph_*. Cách dùng thực tế:
Trước khi sửa một hàm — gọi code_graph_get_callers(symbol_name="ten_ham") để biết sửa xong ảnh hưởng tới đâu.
Khi lạc trong repo — gọi code_graph_search_symbol(query="ten_module", kind="module") để tìm đúng file phụ trách việc đó.
Khi cần hiểu một file lớn — gọi code_graph_get_structure(file_path=...) thay vì đọc cả file — server.py 1 590 dòng chỉ còn 25 dòng outline.
Khi muốn biết một thay đổi lan tới đâu — code_graph_get_dependencies(file_path=...) cho cả hai chiều import.
LƯU Ý · KẾT QUẢ MCP CÓ LIÊN KẾT FILE:/// — VÀ LIÊN KẾT ẤY CÓ THỂ SAI DỰ ÁN handle_tool_call ghép đường dẫn bằng DEFAULT_WORKSPACE, vốn là thư mục Great Lotus. Kết quả thuộc CMEV_Workspace vẫn được ghép vào đường dẫn Great Lotus ⇒ liên kết trỏ sai chỗ. Hãy đọc cột file_path chứ đừng tin liên kết. |
Biết trình tự này giúp chẩn đoán nhanh khi agent báo không gọi được công cụ: chỉ cần xác định nó tắc ở bước nào.
Hình 14.1. Trình tự thật của một phiên, theo mcp_sse_endpoint() (server.py dòng 304) và mcp_messages() (dòng 337). Điểm dễ nhầm: lời gọi POST /mcp/messages chỉ nhận lại 202 Accepted — kết quả thật luôn đi ngược qua luồng SSE.
Hiện tượng |
Nghĩa là tắc ở đâu |
Cách kiểm |
Agent không thấy công cụ code_graph_* nào |
chưa đăng ký MCP, hoặc phiên chưa khởi động lại |
xem ~/.claude.json; mở phiên mới |
GET /mcp/sse không trả gì |
container chết hoặc đang quét |
curl -s http://127.0.0.1:20380/health |
Gọi công cụ xong treo, không có kết quả |
luồng SSE đã đứt nhưng agent vẫn POST |
sessionId hết hiệu lực sẽ nhận 404; kết nối lại |
Sau khi restart container, agent mất kết nối |
sse_clients nằm trong bộ nhớ, mất theo tiến trình |
đúng thiết kế; agent chỉ cần nối lại |
Chương này đi hết một vòng: thêm dự án, quét, kiểm tra, rồi xoá. Toàn bộ ảnh trong chương là thao tác thật trên hệ thống sản xuất với một dự án thử, và dự án ấy đã được xoá sau khi chụp.
1. Mở tab Cấu Hình & Dự Án, bấm Thêm Dự Án Mới.
2. Gõ tên dự án (đây sẽ là khoá duy nhất, dùng cho cả API và MCP).
3. Gõ đường dẫn thư mục. Nhận cả /workspace/... lẫn đường dẫn host của repo Great Lotus — máy chủ tự quy đổi bằng normalize_container_path().
4. Để nguyên ô tích Tự động quét rồi bấm Thêm Dự Án & Quét.
5. Chờ nút đổi về trạng thái thường và đọc hộp thông báo kết quả.
Hình 15.1. Thêm thật dự án B38_TU_QUET trỏ vào chính thư mục mã nguồn của B38: 5 file. Ba số trên hình lần lượt là dòng nguồn do trình duyệt thêm, nội dung thông báo, và nút OK.
Hình 15.2. Bảng dự án ngay sau đó: dòng B38_TU_QUET (số 1) hiện đường dẫn đã được quy đổi sang /workspace/... dù lúc nhập là đường dẫn host — bằng chứng normalize_container_path() chạy đúng.
Đây là trường hợp của CMEV_Workspace. Thứ tự bắt buộc:
1. Sửa docker-compose.yml, thêm một dòng vào volumes của lotus_code_graph, ví dụ - /root/BUDDHA/000_CMEV:/workspace_cmev:ro.
2. Kiểm tra cú pháp: docker compose config -q (file này có hai kiểu viết environment, luôn validate trước khi áp dụng).
3. docker compose up -d lotus_code_graph — phải tạo lại container, restart không nhận mount mới.
4. Vào trang Web, Thêm Dự Án Mới với đường dẫn trong container (/workspace_cmev), không phải đường dẫn host.
LƯU Ý · GÕ ĐƯỜNG DẪN HOST CỦA THƯ MỤC NGOÀI REPO SẼ BỊ TỪ CHỐI normalize_container_path() chỉ biết quy đổi tiền tố của repo Great Lotus. Với /root/BUDDHA/000_CMEV nó giữ nguyên, thấy thư mục không tồn tại trong container và trả lỗi 400 — đúng thông báo lỗi 400 ở Chương 11. Đường dẫn đúng để gõ là /workspace_cmev. |
Ba cách, cùng một hàm bên dưới:
Cách |
Lệnh / thao tác |
Ghi chú |
Giao diện Web |
nút Quét Nhanh hoặc Full Scan ở dòng dự án |
có hộp xác nhận; kết quả hiện ở hộp thông báo |
REST |
curl -X POST -H 'Content-Type: application/json' -d '{"force_full":false}' http://127.0.0.1:20380/api/v1/projects/GreatLotus_Workspace/scan |
tuyến này tự sửa lại root_path về dạng trong container nếu đang lưu đường dẫn host |
MCP |
code_graph_scan_project(project_name=..., workspace_dir=..., force_full=...) |
QUAN TRỌNG — luôn truyền workspace_dir — xem Chương 19 |
Hình 15.3. Quét Nhanh dự án thử 5 file: 0,003 giây, 0 file mới, 0 file cập nhật (vì vừa quét xong lúc thêm).
Hình 15.4. Quét Nhanh GreatLotus_Workspace khi không có gì thay đổi: 2 110 file, 0,507 giây. Lần quét trước đó vài phút — khi chỉ mục còn cũ 6 ngày — mất 2,45 giây và tìm ra 13 file mới, 24 file sửa.
# so lieu tung du an curl -s http://127.0.0.1:20380/api/v1/projects | python3 -m json.tool
# kiem tra dung mot file da vao chi muc chua curl -s "http://127.0.0.1:20380/api/v1/search?q=html_readable&kind=module"
# outline cua file vua sua — neu thieu ham moi viet thi chi muc chua cap nhat curl -s "http://127.0.0.1:20380/api/v1/structure?file_path=docs/tools/docgen/html_readable.py" |
Hình 15.5. Bảng dự án sau khi quét: GreatLotus_Workspace đã mang mốc 19/09 23:39 thay cho 13/09, và dự án thử vẫn còn (sẽ bị xoá ở bước tiếp theo).
NGUY HIỂM · KHÔNG HOÀN TÁC ĐƯỢC, NHƯNG KHÔNG MẤT MÃ NGUỒN Xoá dự án chỉ xoá chỉ mục — bảng files, symbols, edges và bản ghi dự án. Mã nguồn trên đĩa không bị đụng tới (mọi mount workspace đều :ro). Vì vậy hậu quả thật sự là phải quét lại, chứ không phải mất mã. |
1. Tab Cấu Hình & Dự Án, bấm nút Xóa màu đỏ ở đúng dòng dự án cần xoá.
2. Đọc kỹ tên dự án trong hộp xác nhận — đây là lớp bảo vệ duy nhất.
3. Bấm OK; hệ thống gọi DELETE /api/v1/projects/<tên> và báo lại số bản ghi đã xoá.
Hình 15.6. Xoá dự án thử B38_TU_QUET — thông báo thành công.
Hình 15.7. Sau khi xoá: bảng dự án trở về đúng hai dự án thật như trước lúc biên soạn. Đây là trạng thái hệ thống được bàn giao lại sau khi làm sách.
Tương đương bằng dòng lệnh:
curl -s -X DELETE http://127.0.0.1:20380/api/v1/projects/B38_TU_QUET # hoac python3 app/cli.py delete-project B38_TU_QUET |
NGUY HIỂM · CÔNG CỤ XOÁ CŨNG NẰM TRONG TAY AI AGENT code_graph_delete_project là một trong 9 công cụ MCP được công bố cho mọi agent kết nối — không có bước xác nhận nào ở phía MCP, và thao tác này không được ghi vào usage.jsonl. Nếu không muốn agent có quyền đó, hãy bỏ định nghĩa công cụ này khỏi TOOLS_DEFINITION trong mcp_stdio.py rồi docker restart lotus_code_graph. |
PHẦN V
VẬN HÀNH HẰNG NGÀY
Câu hỏi |
Lệnh |
Nhìn vào đâu |
B38 còn sống không? |
curl -s http://127.0.0.1:20380/health |
status: healthy, uptime_seconds, server_time phải là giờ Việt Nam |
Container có bị khởi động lại không? |
docker inspect lotus_code_graph --format '{{.State.StartedAt}} {{.RestartCount}}' |
RestartCount bằng 0 là chưa lần nào tự khởi động lại |
Chỉ mục có còn đúng không? |
curl -s http://127.0.0.1:20380/api/v1/projects |
so last_scan_at với thời điểm sửa mã gần nhất |
Số đo tham chiếu ngày 19/09/2026: uptime_seconds = 529 601 (6,1 ngày), RestartCount = 0, container khởi động lúc 13/09 lúc thêm mount CMEV. Bộ nhớ 62,93 MiB, CPU 0,13 %.
|
Nhật ký trong bộ nhớ (/api/v1/metrics) |
Nhật ký trên đĩa (data/usage.jsonl) |
Ghi ở đâu |
biến access_metrics trong tiến trình server.py |
file JSONL cạnh CSDL |
Ghi khi nào |
mỗi lần REST hoặc MCP SSE được gọi |
mỗi lần một hàm truy vấn của CodeGraphDB chạy, bất kể đi từ cửa nào |
Thấy được gì |
endpoint, từ khoá, số kết quả, độ trễ, giờ |
thời điểm, tên hàm, client, thời gian (ms) |
Có thấy Gemini Worker không |
không — worker dùng stdio, không đi qua HTTP |
có — client ghi host_stdio |
Giữ được bao lâu |
30 dòng gần nhất; mất khi restart |
vĩnh viễn, nối thêm từng dòng |
Có ghi scan / xoá không |
không (chỉ ghi truy vấn) |
không — delete_project và scan không có bộ đếm |
Đọc nhanh usage.jsonl — ai đã hỏi B38 bao nhiêu lượt:
cd .../B38_Code_Graph/data python3 - <<'EOF' import json, collections, datetime by_tool, by_client = collections.Counter(), collections.Counter() for line in open("usage.jsonl", encoding="utf-8"): r = json.loads(line) by_tool[r["tool"]] += 1 by_client[r["client"]] += 1 print("Theo cong cu :", by_tool.most_common()) print("Theo nguoi goi:", by_client.most_common()) EOF |
Kết quả thật trước phiên biên soạn (27 bản ghi, 20/08 → 13/09): get_stats 14, list_projects 11, search_symbols 2; theo người gọi: container 26, host_stdio 1. Nói cách khác, gần như toàn bộ là do mở trang Dashboard.
MẸO · BẢNG TỔNG KẾT WORKER POOL ĐỌC CHÍNH FILE NÀY Library/A0_Gemini_Worker_MCP/worker_pool.py có hàm _code_graph_usage_md() đọc usage.jsonl và in mục Truy vấn B38 Code Graph ở cuối mỗi báo cáo lô worker, tách theo Claude (MCP SSE/REST qua container) và Gemini Worker (MCP stdio do agy sinh). Nếu mục đó báo 0 lượt, nghĩa là cả lô worker không ai tra AST. |
docker logs --tail 50 lotus_code_graph # moi dong HTTP deu duoc uvicorn ghi lai docker logs --since 30m lotus_code_graph | grep -v "200 OK" # chi xem loi docker stats --no-stream lotus_code_graph |
Ba dòng cảnh báo UserWarning: Duplicate Operation ID web_portal_... xuất hiện mỗi khi có ai mở /openapi.json hoặc /docs là vô hại: ba tuyến /, /dashboard, /explorer cùng trỏ vào một hàm nên FastAPI than phiền về trùng operationId.
FastAPI dựng sẵn hai trang không nằm trong menu: http://172.16.10.220:20380/docs (Swagger UI, thử gọi được ngay) và /openapi.json (đặc tả máy đọc). Đây là cách nhanh nhất để đọc đúng tham số của từng tuyến khi sách này lạc hậu so với mã.
Chỉ có đúng một trường hợp B38 tự quét: lúc khởi động, nếu CSDL hoàn toàn rỗng (stats.files_count == 0) thì nó chạy một lần quét gia tăng ở chế độ nền. Ngoài ra không có lịch, không có theo dõi file, không có webhook git.
Hình 17.1. Hệ quả thực tế đã gặp khi biên soạn sách: file thêm ngày 18/09 không tra được, vì lần quét gần nhất là 13/09. Sau 2,45 giây quét lại thì tra ra ngay.
Tình huống |
Việc nên làm |
Chi phí |
Vừa sửa/ thêm vài file trong repo |
Quét Nhanh dự án tương ứng |
≈ 0,5–2,5 giây cho 2 110 file |
Trước khi giao một lô Gemini Worker tra cứu mã |
Quét Nhanh |
như trên — rẻ hơn nhiều so với việc worker kết luận sai |
Sau khi sửa bộ trích xuất (graph_engine.py) |
restart container rồi Full Scan |
hàng chục giây, trong lúc đó B38 không trả lời ai |
Sau khi đổi/ thêm mount trong compose |
docker compose up -d rồi thêm dự án + quét |
tuỳ kích thước |
Nghi ngờ chỉ mục sai (kết quả vô lý) |
Full Scan, rồi đối chiếu total_files với find ... -name '*.py' | wc -l |
hàng chục giây |
Hằng tuần, không có gì đặc biệt |
Quét Nhanh cả hai dự án |
dưới 5 giây |
MẸO · MUỐN TỰ ĐỘNG, ĐẶT LỊCH TỪ BÊN NGOÀI B38 không có bộ hẹn giờ. Cách đơn giản nhất là một dòng cron trên HP1 gọi REST — nhớ chọn giờ vắng vì quét làm B38 ngừng trả lời: ▸ 30 6 * * * curl -s -X POST -H 'Content-Type: application/json' -d '{"force_full":false}' http://127.0.0.1:20380/api/v1/projects/GreatLotus_Workspace/scan > /dev/null |
Mốc |
Files |
Symbols |
Edges |
Kích thước .sqlite |
17/08/2026 (khảo sát cũ) |
2 149 |
14 243 |
117 889 |
không ghi nhận |
13/09/2026 (thêm CMEV) |
2 209 |
15 606 |
128 270 |
30 MB |
19/09/2026 (sau Quét Nhanh) |
2 222 |
15 887 |
130 213 |
30 MB + 6,3 MB WAL |
Khoảng 13,5 KB CSDL cho mỗi file mã nguồn. Với tốc độ tăng của repo này, dung lượng không phải vấn đề trong nhiều năm tới. Nếu file WAL phình lớn mà không thu lại, chạy sqlite3 data/code_graph.sqlite "PRAGMA wal_checkpoint(TRUNCATE);" lúc B38 rảnh.
PHẦN VI
AN TOÀN VÀ SAO LƯU
Hình 18.1. Bản đồ: cái gì được sao lưu, cái gì không, và cái gì không cần. Nguồn: .git của B38, .gitignore, B00_Docker_Volume_DailyBackup/01_backup_all_docker_volumes.sh.
code_graph.sqlite là dữ liệu phái sinh: mọi thông tin trong đó đều tính lại được từ mã nguồn bằng một lần Full Scan. Mất file này thì thiệt hại là vài chục giây quét, không phải mất dữ liệu.
LƯU Ý · NHƯNG USAGE.JSONL THÌ KHÔNG DỰNG LẠI ĐƯỢC Nhật ký lượt truy vấn là dữ liệu gốc duy nhất trong thư mục data/. Nó không nằm trong git (thư mục data bị .gitignore bỏ qua) và cũng không nằm trong bản sao lưu hằng ngày của B00 — vì B00 chỉ sao lưu docker volume, còn B38 dùng bind-mount. Muốn giữ lịch sử đo lường, hãy chép định kỳ file này ra nơi khác. |
cd .../B38_Code_Graph/data
# DUNG: gop ca WAL, khong can dung dich vu sqlite3 code_graph.sqlite ".backup /disk2/backup/b38_code_graph_$(date +%Y%m%d).sqlite"
# Hoac bang Python python3 -c "import sqlite3;s=sqlite3.connect('code_graph.sqlite');d=sqlite3.connect('/disk2/backup/b38.sqlite');s.backup(d);d.close();s.close()"
# SAI: chi chep file chinh — mat moi thay doi con nam trong WAL cp code_graph.sqlite /disk2/backup/ # <-- dung lam the nay |
NGUY HIỂM · CHUYỆN ĐÃ XẢY RA NGAY TRONG LÚC VIẾT SÁCH NÀY Một bản sao lấy bằng cp code_graph.sqlite lúc 23:41 ngày 19/09 vẫn còn dự án thử đã bị xoá lúc 23:40, và vẫn ghi GreatLotus_Workspace có 2 097 file trong khi thực tế đã là 2 110. Lý do: 6,3 MB thay đổi mới nhất còn nằm trong code_graph.sqlite-wal. Đây đúng là vết xe của sự cố sao lưu profile SQLite từng gặp trước đây — xem Phụ lục A. |
1. Bảo đảm mã nguồn vẫn còn: git -C .../B38_Code_Graph log --oneline -3 (repo riêng, remote github.com/githublotus/B38_Code_Graph).
2. Nếu thư mục data/ mất: cứ để trống. docker compose up -d lotus_code_graph — B38 thấy CSDL rỗng và tự quét lần đầu cho WORKSPACE_DIR mặc định.
3. Thêm lại các dự án phụ (ví dụ CMEV_Workspace với /workspace_cmev) qua trang Web hoặc POST /api/v1/projects/add.
4. Đối chiếu: GET /api/v1/stats phải ra số gần giống bảng ở mục 17.3.
ĐÚNG THIẾT KẾ · KHÔNG CÓ BƯỚC NÀO CẦN ĐỤNG TỚI MÃ NGUỒN Vì mọi mount workspace đều chỉ đọc, kịch bản xấu nhất của B38 (mất sạch CSDL) không thể làm hỏng repo. Đây là lý do B38 được xếp vào nhóm dịch vụ không quan trọng về dữ liệu. |
Mặt |
Hiện trạng |
Rủi ro |
Khuyến nghị |
Xác thực |
không có |
ai vào được cổng 20380 đều xoá được chỉ mục |
giữ trong LAN; không forward |
CORS |
allow_origins=["*"], allow_credentials=True |
một trang web bất kỳ trong LAN có thể gọi API thay người dùng |
đổi thành danh sách nguồn cụ thể nếu cần siết |
Ghi vào mã nguồn |
không thể — mount :ro |
không |
giữ nguyên cờ :ro |
Rò rỉ nội dung mã |
API trả chữ ký hàm và docstring, không trả thân hàm |
docstring có thể chứa thông tin nội bộ |
cân nhắc khi mở rộng phạm vi truy cập |
Bí mật trong .env |
không bị lập chỉ mục (đuôi .env không nằm trong danh sách hỗ trợ) |
thấp |
không cần làm gì |
Công cụ phá huỷ qua MCP |
code_graph_delete_project mở cho mọi agent |
agent xoá nhầm dự án |
bỏ công cụ này khỏi TOOLS_DEFINITION nếu không cần |
PHẦN VII
RỦI RO VÀ THẢM HOẠ
Mỗi mục dưới đây theo cùng một khuôn: triệu chứng → nguyên nhân → xử lý → phòng ngừa. Các số liệu đều là số đo hoặc thí nghiệm thật, ghi rõ ở từng mục.
Hình 19.1. Thí nghiệm trên bản sao cơ sở dữ liệu, 19/09/2026: gọi code_graph_scan_project với đúng tên dự án nhưng quên workspace_dir. Kết quả: dự án CMEV mất sạch 112 file và bị thay bằng 2 110 file của repo Great Lotus, root_path cũng bị ghi đè.
|
Nội dung |
Triệu chứng |
Một dự án đột nhiên có số file giống hệt dự án khác; cột Thư mục mount hiện đường dẫn lạ (ví dụ /root/BUDDHA/GREAT_LOTUS/... thay vì /workspace_cmev); tra cứu trả về file không thuộc dự án đó |
Nguyên nhân |
mcp_stdio.py (dòng 299–303) lấy workspace_dir = args.get("workspace_dir") or DEFAULT_WORKSPACE. Thiếu tham số ⇒ dùng thư mục mặc định. WorkspaceScanner.__init__ gọi get_or_create_project(name, root) — hàm này UPDATE root_path. Quét xong, delete_missing_files() xoá mọi file cũ không thấy trong thư mục mới |
Xử lý |
Khai
báo lại dự án với đường dẫn đúng rồi quét
toàn bộ: |
Phòng ngừa |
Luôn truyền workspace_dir khi gọi code_graph_scan_project; trong tài liệu hướng dẫn agent, ghi rõ tham số này là bắt buộc. Với thao tác quét, ưu tiên nút trên trang Web hoặc REST — hai đường này đọc root_path từ CSDL và tự quy đổi, không nhận thư mục từ người gọi |
ĐÚNG THIẾT KẾ · KHÔNG MẤT GÌ NGOÀI THỜI GIAN QUÉT Chỉ mục là dữ liệu phái sinh, nên kịch bản này chỉ tốn công quét lại: 2,6 giây cho CMEV_Workspace, hàng chục giây cho một Full Scan của Great Lotus. Nhưng trong khoảng thời gian chỉ mục sai, mọi câu trả lời của B38 cho dự án đó đều sai — đó mới là thiệt hại thật. |
|
Nội dung |
Triệu chứng |
Dự án biến mất khỏi bảng; mọi truy vấn vào nó trả rỗng |
Nguyên nhân |
Bấm Xóa rồi OK, hoặc agent gọi code_graph_delete_project, hoặc DELETE /api/v1/projects/<tên>. Không có thùng rác, không có nhật ký — thao tác này không được ghi vào usage.jsonl |
Xử lý |
Thêm lại dự án với đúng root_path rồi quét (auto_scan: true, force_full: true) |
Phòng ngừa |
Đọc kỹ tên trong hộp xác nhận; gỡ code_graph_delete_project khỏi TOOLS_DEFINITION nếu không muốn agent có quyền này; không mở cổng 20380 ra ngoài LAN |
|
Nội dung |
Triệu chứng |
Mọi truy vấn treo vài giây đến vài chục giây; agent báo hết thời gian chờ; ngay cả /health cũng không trả lời |
Nguyên nhân |
scan() là hàm đồng bộ, được gọi thẳng trong hàm async của FastAPI. Một tiến trình, một worker uvicorn ⇒ vòng lặp sự kiện bị chặn. Đo thật: /health chờ 2,23 giây trong lúc Quét Nhanh 2,45 giây |
Xử lý |
Chờ quét xong — không có cách huỷ giữa chừng qua API |
Phòng ngừa |
Full Scan vào giờ vắng; không quét khi một lô worker đang chạy; nếu cần sửa tận gốc thì bọc scanner.scan(...) bằng await asyncio.to_thread(...) — đúng cách đã áp dụng cho Gemini Worker Pool (xem Phụ lục A) |
|
Nội dung |
Triệu chứng |
search trả 0 kết quả cho một hàm/file chắc chắn có thật; agent kết luận chưa tồn tại rồi viết lại từ đầu, sinh mã trùng |
Nguyên nhân |
B38 chỉ quét khi có người/agent gọi. Trường hợp thật khi biên soạn: html_readable.py thêm ngày 18/09, chỉ mục mới quét đến 13/09 ⇒ tra ra 0 |
Xử lý |
Quét Nhanh rồi tra lại (2,45 giây). Sau khi quét, cùng từ khoá trả về 1 kết quả loại module |
Phòng ngừa |
Trước khi kết luận không tồn tại, xem last_scan_at. Đặt một lệnh cron quét hằng ngày (xem Chương 17). Trong rule của agent, thêm câu nếu B38 không ra kết quả thì quét lại rồi mới fallback sang grep |
|
Nội dung |
Triệu chứng |
Cổng 20380 không trả lời; docker ps thấy container liên tục Restarting |
Nguyên nhân |
app/ là bind-mount, nên lỗi cú pháp hoặc lỗi thứ tự khai báo (dùng một decorator trước khi định nghĩa nó) làm uvicorn chết ngay lúc import; restart: unless-stopped khiến Docker thử lại mãi |
Xử lý |
docker logs --tail 30 lotus_code_graph đọc vết lỗi; sửa file trên host; docker restart lotus_code_graph. Không cần build lại image |
Phòng ngừa |
Kiểm tra trước khi restart: python3 -m py_compile app/*.py, và nếu sửa nhiều thì thử python3 -c "import sys; sys.path.insert(0,'app'); import server" bằng Python trên host |
|
Nội dung |
Triệu chứng |
Sau một lần quét, số file của dự án tụt mạnh; nhiều ký hiệu biến mất |
Nguyên nhân |
delete_missing_files() xoá mọi bản ghi có đường dẫn không còn thấy. Đổi tên một thư mục lớn = toàn bộ đường dẫn cũ biến mất, đường dẫn mới được thêm — đúng như thiết kế |
Xử lý |
Không cần làm gì: chính lần quét đó đã thêm lại các file ở vị trí mới |
Phòng ngừa |
Không có gì phải phòng — nhưng nhớ rằng số liệu files sẽ nhảy, đừng hoảng |
|
Nội dung |
Triệu chứng |
Dashboard hiện 0 dự án, 0 file |
Nguyên nhân |
Xoá nhầm thư mục data/, hoặc docker compose down -v trên máy khác đường dẫn, hoặc ổ đĩa hỏng |
Xử lý |
Khởi động lại container: CSDL rỗng ⇒ B38 tự quét lần đầu cho /workspace. Thêm lại các dự án phụ bằng tay (CMEV_Workspace ⇒ /workspace_cmev) |
Phòng ngừa |
Không cần sao lưu chỉ mục. Nhưng hãy chép định kỳ usage.jsonl — đó là thứ duy nhất trong data/ không dựng lại được |
|
Nội dung |
Triệu chứng |
Truy cập được B38 từ Internet; hoặc thấy truy vấn lạ trong nhật ký hoạt động |
Nguyên nhân |
Ánh xạ cổng của B38 nằm trên 0.0.0.0 và [::]; chỉ cần một lần mở NAT/port-forward hoặc một tuyến Caddy mới là lộ ra. B38 không có xác thực và CORS mở hoàn toàn |
Xử lý |
Gỡ tuyến forward/Caddy ngay; kiểm tra GET /api/v1/projects xem dự án còn đủ không; nếu nghi ngờ bị xoá, khai báo lại và quét |
Phòng ngừa |
Giữ B38 ở LAN. Nếu bắt buộc phải mở, đặt sau lớp xác thực (như cách B43 dùng Caddy + 2FA), đừng mở thẳng |
PHẦN VIII
KHẮC PHỤC SỰ CỐ
Triệu chứng |
Nguyên nhân thường gặp |
Cách chữa |
Trang 20380 không mở được |
container dừng hoặc đang khởi động lại |
docker ps | grep code_graph; docker logs --tail 30 lotus_code_graph; docker restart lotus_code_graph |
Trang mở nhưng mọi thẻ số là -- |
API lỗi hoặc CSDL khoá |
mở Console trình duyệt; gọi tay curl -s .../api/v1/stats; xem log container |
Tra cứu ra 0 kết quả dù mã có thật |
chỉ mục cũ hơn lần sửa mã |
xem last_scan_at, bấm Quét Nhanh, tra lại |
Tra cứu tên file ra 0 dù file có thật |
file nằm trong thư mục bị bỏ qua (tmp/, data/, build/, env/, thư mục bắt đầu bằng dấu chấm) hoặc có đuôi không được hỗ trợ (.md, .html, .env) |
kiểm tra đường dẫn file; nếu cần lập chỉ mục thì đổi chỗ đặt file (danh sách bỏ qua ở Phụ lục D) |
structure / dependencies trả rỗng |
truyền đường dẫn tuyệt đối |
dùng đường dẫn tương đối so với root_path |
callees trả rỗng |
truyền tên ngắn thay cho full_name |
dùng Lop.phuong_thuc; lấy đúng chuỗi từ kết quả search |
Callers của hàm JavaScript luôn rỗng |
JS/TS không sinh cạnh calls |
dùng command grep -rn cho mã JS |
Một file Python có 0 ký hiệu |
file lỗi cú pháp, bị except SyntaxError: pass nuốt |
chạy python3 -m py_compile <file> để lộ lỗi thật |
Số callers luôn đúng bằng 50 |
trần limit mặc định |
gọi REST với &limit=5000, hoặc đọc số thật từ bảng Top 10 |
Kết quả lẫn file của dự án khác |
Web không lọc dự án |
gọi REST kèm &project=..., hoặc MCP với project_name |
Bấm Sao Chép không có phản ứng |
navigator.clipboard không tồn tại ngoài secure context |
bôi đen + Ctrl+C, hoặc mở bằng http://localhost:20380 trên chính HP1 |
Trang treo khi bấm Quét |
quét chặn vòng lặp sự kiện |
chờ; lần sau chọn giờ vắng |
Bảng dự án hiện đường dẫn /root/BUDDHA/... |
ai đó vừa quét bằng cli.py hoặc MCP stdio |
quét lại qua REST/Web để root_path được quy đổi, hoặc khai báo lại bằng projects/add |
Dự án có số file giống hệt dự án khác |
đã dính bẫy ở mục 19.1 |
khai báo lại root_path đúng + Full Scan |
docker logs đầy UserWarning: Duplicate Operation ID |
ba tuyến cùng trỏ một hàm |
vô hại, bỏ qua |
Công cụ code_graph_* không hiện trong phiên AI |
chưa đăng ký MCP, hoặc đăng ký xong chưa khởi động lại phiên |
kiểm tra ~/.claude.json / ~/.gemini/config/mcp_config.json; mở phiên mới |
Bản sao CSDL thiếu thay đổi mới nhất |
chép mỗi file .sqlite, bỏ WAL |
dùng sqlite3 ... ".backup" |
Thống kê /api/v1/stats chậm vài giây |
quét toàn bảng edges (hơn 130 000 dòng) mỗi lần gọi |
bình thường; tránh gọi trong vòng lặp |
PHẦN IX
THỰC HÀNH
Các bài xếp từ chỉ-đọc đến có-thay-đổi. Bài 1–5 hoàn toàn an toàn trên hệ thống sản xuất. Bài 6–8 có thao tác ghi; làm đúng theo các bước thì hệ thống trở về nguyên trạng.
Mục tiêu: Biết B38 đang sống ra sao và đang giữ bao nhiêu dữ liệu, chỉ bằng dòng lệnh.
Các bước:
1. curl -s http://127.0.0.1:20380/health — ghi lại uptime_seconds và server_time.
2. curl -s http://127.0.0.1:20380/api/v1/stats | python3 -m json.tool — ghi lại 4 con số tổng.
3. curl -s http://127.0.0.1:20380/api/v1/projects | python3 -m json.tool — ghi last_scan_at từng dự án.
4. docker stats --no-stream lotus_code_graph — ghi mức RAM.
Tiêu chí đạt:
Nói được giờ máy chủ là giờ Việt Nam (UTC+7), không phải UTC.
Giải thích được vì sao files_count của /stats lớn hơn total_files của một dự án.
Chỉ ra được dự án nào có chỉ mục cũ nhất.
Mục tiêu: Dùng đủ bốn công cụ tra cứu cho cùng một ký hiệu để thấy chúng bổ trợ nhau.
Các bước:
1. Tìm định nghĩa: search?q=save_file_graph — ghi lại file_path, full_name, line_start.
2. Tìm nơi gọi: callers?symbol=save_file_graph.
3. Tìm lời gọi bên trong: callees?file_path=<đường dẫn vừa lấy>&symbol_name=CodeGraphDB.save_file_graph.
4. Xem outline file chứa nó: structure?file_path=<đường dẫn>.
5. Làm lại bước 3 nhưng truyền tên ngắn save_file_graph và quan sát kết quả.
Tiêu chí đạt:
Bước 3 với full_name cho kết quả, với tên ngắn trả rỗng — giải thích được vì sao.
Đối chiếu được: hàm có 1 nơi gọi, nằm trong WorkspaceScanner.scan.
Mục tiêu: Tự đo lại bảng so sánh ở Chương 1 để tin vào con số.
Các bước:
1. time (command grep -rn "scan" . --include=*.py | wc -l) từ gốc repo — ghi số dòng và thời gian.
2. Đếm dung lượng: command grep -rn "scan" . --include=*.py | wc -c.
3. time curl -s "http://127.0.0.1:20380/api/v1/callers?symbol=scan" | wc -c.
4. Tính tỉ lệ dung lượng giữa hai cách.
Tiêu chí đạt:
Ra được bậc chênh lệch hàng chục lần.
Nêu được ít nhất ba loại dòng rác mà grep trả về còn B38 thì không.
Mục tiêu: Nắm cách tra cứu module và biết lúc nào nó không hoạt động.
Các bước:
1. Tra profile_identity — quan sát loại module và chữ ký (N symbols).
2. Tra docker-compose — giải thích vì sao một file YAML lại có mặt.
3. Tra một file bất kỳ trong tmp/ của repo (ví dụ cap_common) và quan sát kết quả.
4. Mở scanner.py, tìm IGNORED_DIRS để tự giải thích kết quả bước 3.
Tiêu chí đạt:
Giải thích được vì sao file trong tmp/ không bao giờ vào chỉ mục.
Nêu được ít nhất năm thư mục bị bỏ qua và ba đuôi file không được hỗ trợ.
Mục tiêu: Phân biệt hai loại nhật ký và rút ra kết luận vận hành.
Các bước:
1. Mở tab Dashboard, cuộn xuống khối Tần Suất Truy Xuất — ghi lại vài dòng.
2. Chạy vài truy vấn REST rồi mở lại tab Dashboard, quan sát dòng mới xuất hiện.
3. Đọc data/usage.jsonl bằng đoạn Python ở mục 16.2 — đếm theo tool và theo client.
4. So sánh: dòng nào có trong usage.jsonl mà không có trong bảng trên Web, và ngược lại.
Tiêu chí đạt:
Nói được vì sao huy hiệu N Lượt Hôm Nay không thật sự là hôm nay.
Nói được vì sao lượt gọi của Gemini Worker không bao giờ hiện trên bảng Web.
Mục tiêu: Tự tái hiện cái bẫy nguy hiểm nhất với AI Agent.
Các bước:
1. Tạo một file mới trong repo, ví dụ Library/lab_b38_demo.py, bên trong viết def ham_lab_b38(): return 1.
2. Tra ngay: search?q=ham_lab_b38 — kết quả phải là 0.
3. Xem last_scan_at của GreatLotus_Workspace và đối chiếu với giờ vừa tạo file.
4. Bấm Quét Nhanh, đọc số file Thêm mới.
5. Tra lại search?q=ham_lab_b38 — giờ phải ra 1 kết quả loại function.
6. Xoá file vừa tạo rồi Quét Nhanh lần nữa; tra lại để thấy ký hiệu biến mất.
Tiêu chí đạt:
Giải thích được hai lần 0 kết quả ở bước 2 và bước 6 khác nhau ở chỗ nào.
Nêu được câu luật: trước khi kết luận không tồn tại, hãy xem last_scan_at.
LƯU Ý · LƯU Ý KHI LÀM BÀI NÀY Bài này ghi thêm rồi xoá đúng một file trong repo. Nhớ hoàn tất bước 6 để repo sạch, và không đặt file trong tmp/ (thư mục đó bị bỏ qua nên bài sẽ không chạy được). |
Mục tiêu: Thực hành thêm — quét — xoá mà không đụng tới hai dự án thật.
Các bước:
1. Tab Cấu Hình & Dự Án → Thêm Dự Án Mới; tên LAB_B38, đường dẫn /workspace/Application/APP_1_GREAT_LOTUS_PROJECT/App/A0_DCP_GREATE_LOTUS_NETWORK/DCP_PRODUCTION/B38_Code_Graph.
2. Bấm Thêm Dự Án & Quét; đọc số file trong thông báo (phải là 5).
3. Tra search?q=WorkspaceScanner&project=LAB_B38 và so với &project=GreatLotus_Workspace.
4. Bấm Quét Nhanh cho LAB_B38; đọc thời gian và số file cập nhật.
5. Bấm Xóa cho LAB_B38, đọc kỹ tên trong hộp xác nhận rồi bấm OK.
6. Kiểm tra GET /api/v1/projects — phải còn đúng hai dự án.
Tiêu chí đạt:
Cùng một ký hiệu xuất hiện ở hai dự án với đường dẫn tương đối khác nhau — giải thích được vì sao.
Sau bước 6, hệ thống trở lại đúng trạng thái ban đầu.
LƯU Ý · LƯU Ý KHI LÀM BÀI NÀY Tuyệt đối không bấm Xóa ở dòng GreatLotus_Workspace hay CMEV_Workspace. Hộp xác nhận có ghi tên dự án — đọc trước khi bấm OK. |
Mục tiêu: Tự đo lại con số 2,23 giây ở Chương 19 và hiểu vì sao Full Scan phải chọn giờ.
Các bước:
1. Mở một cửa sổ terminal chạy vòng lặp đo: while true; do curl -s -o /dev/null -w "%{time_total}\n" http://127.0.0.1:20380/health; sleep 0.25; done.
2. Ở cửa sổ khác (hoặc trên Web) bấm Quét Nhanh cho GreatLotus_Workspace.
3. Quan sát cột thời gian trong vòng lặp: giá trị nhảy vọt đúng lúc quét.
4. Dừng vòng lặp bằng Ctrl+C; ghi lại giá trị lớn nhất.
Tiêu chí đạt:
Giá trị lớn nhất xấp xỉ thời gian quét mà hộp thông báo báo về.
Nêu được cách sửa tận gốc (asyncio.to_thread) và vì sao chưa sửa thì phải chọn giờ.
LƯU Ý · LƯU Ý KHI LÀM BÀI NÀY Chỉ dùng Quét Nhanh cho bài này. Full Scan sẽ chặn B38 hàng chục giây — nếu có lô Gemini Worker đang chạy, chúng sẽ chịu ảnh hưởng. |
PHẦN X
PHỤ LỤC
Phần này không có trong mã nguồn và không tra được bằng công cụ nào. Đây là những lần hệ thống (hoặc người dùng hệ thống) vấp phải một điều bất ngờ, và bài học rút ra. Mỗi mục kể theo lối: triệu chứng → chẩn đoán sai lúc đầu → nguyên nhân thật → cách chữa → bài học.
Triệu chứng — CLAUDE.md có hẳn mục B38 Lotus Code Graph Priority Rule ra lệnh ưu tiên dùng code_graph_* thay grep, nhưng trong phiên làm việc không hề thấy công cụ nào tên như vậy.
Chẩn đoán sai lúc đầu — nghĩ rằng tên công cụ bị đổi, hoặc chỉ Gemini mới có quyền gọi.
Nguyên nhân thật — ~/.claude.json chỉ khai hai MCP server (n8n-echo, n8n-reghotmail); lotus-code-graph chưa bao giờ được đăng ký. Rule nằm trong tài liệu, còn kết nối thì không có.
Cách chữa — thêm "lotus-code-graph": {"type": "sse", "url": "http://127.0.0.1:20380/mcp/sse"} vào mcpServers, rồi khởi động lại phiên — phiên đang chạy không tự thấy công cụ mới.
Bài học — rule không tạo ra năng lực. Sau khi viết một rule bắt dùng công cụ, phải kiểm tra công cụ đó thật sự hiện ra trong phiên; cách kiểm nhanh nhất là gọi thử một lệnh và xem usage.jsonl có thêm dòng không.
Triệu chứng — tra profile_identity — tên một file có thật, đã được lập chỉ mục đầy đủ 12 hàm bên trong — nhưng kết quả là rỗng.
Chẩn đoán sai lúc đầu — nghi chỉ mục hỏng, định chạy Full Scan.
Nguyên nhân thật — bảng symbols chỉ chứa class/function/method. Tên file không phải là một ký hiệu, nên không có gì để khớp; dữ liệu nằm sẵn ở bảng files mà search_symbols không tra tới.
Cách chữa — thêm search_files() tra bảng files, lọc theo tên file (basename) chứ không theo cả đường dẫn, trả về cùng cấu trúc dữ liệu với ký hiệu và gắn kind="module". Module được lấy trước, tối đa 5 suất trong limit.
Bài học — khi một công cụ trả rỗng, hãy hỏi dữ liệu có ở đó không trước khi hỏi chỉ mục có hỏng không. Và: sửa ở tầng CodeGraphDB thì cả REST, MCP SSE, MCP stdio lẫn trang Web đều được hưởng — không phải sửa bốn nơi.
Bối cảnh — thêm decorator @_count_query vào 7 hàm truy vấn của CodeGraphDB, ghi mỗi lượt một dòng JSONL kèm client (container hay host_stdio, phân biệt bằng sự tồn tại của /.dockerenv).
Kết quả đo được sau một tháng — 24 lượt, trong đó 22 lượt là do trang Dashboard tự gọi khi có người mở. Đúng 2 lượt tra cứu ký hiệu thật, và đúng 1 lượt đến từ host_stdio.
Ý nghĩa — dù luật ưu tiên B38 đã nằm trong CLAUDE.md, GEMINI.md, AGENTS.md và cả system_prompt của Gemini Worker Pool, trên thực tế gần như không ai gọi.
Bài học — đặt bộ đếm ở tầng thấp nhất (ở đây là lớp CSDL) thì đo được mọi đường vào, kể cả tiến trình chạy ngoài container. Và: nghiệm thu một rule bằng số đo, đừng nghiệm thu bằng việc rule đã được viết.
Bối cảnh — dự án CMEV chuyển ra /root/BUDDHA/000_CMEV thành workspace riêng, nhưng vẫn muốn tra cứu được bằng B38.
Cách làm — thêm một mount - /root/BUDDHA/000_CMEV:/workspace_cmev:ro vào chính service lotus_code_graph của compose Great Lotus, docker compose config -q rồi up -d (tạo lại container), sau đó khai báo dự án CMEV_Workspace trỏ /workspace_cmev.
Kết quả — 112 file · 1 342 ký hiệu · 12 564 cạnh, quét hết 2,578 giây.
Bẫy đi kèm — từ lúc có hai dự án, mọi truy vấn không lọc project đều trộn kết quả của cả hai. Thấy rõ nhất ở nút Xem Callers của Top 10: callers của str trả về cả file A0_scripts/ của CMEV.
Bài học — thêm dự án là việc rẻ, nhưng mọi truy vấn sau đó đều cần nghĩ tới phạm vi. Giao diện Web hiện chưa có ô chọn dự án — dùng REST hoặc MCP nếu cần lọc.
Triệu chứng — chép code_graph.sqlite bằng cp lúc 23:41, mở ra vẫn thấy dự án thử đã xoá lúc 23:40, và GreatLotus_Workspace vẫn ghi 2 097 file trong khi thực tế đã là 2 110.
Chẩn đoán sai lúc đầu — tưởng thao tác xoá trên Web không thành công.
Nguyên nhân thật — journal_mode=WAL: thay đổi mới nằm trong code_graph.sqlite-wal (6,3 MB) chứ chưa gộp vào file chính. Bản sao chỉ lấy file chính nên là ảnh của quá khứ.
Cách chữa — sao lưu bằng sqlite3 ... ".backup ..." hoặc conn.backup() của Python — cả hai đều gộp WAL.
Bài học — đây là cùng một vết xe với sự cố đồng bộ profile Chrome trước đây (bản sao SQLite thiếu WAL làm hồ sơ không nhất quán). Hễ gặp SQLite ở chế độ WAL thì không bao giờ sao lưu bằng cp.
Nghi vấn — đọc mcp_stdio.py thấy workspace_dir = args.get("workspace_dir") or DEFAULT_WORKSPACE — nếu agent chỉ truyền project_name thì điều gì xảy ra?
Cách kiểm chứng an toàn — chép cơ sở dữ liệu ra thư mục tạm rồi chạy thí nghiệm trên bản sao, đặt biến CODE_GRAPH_DB trỏ vào bản sao. Không đụng file thật.
Kết quả — CMEV_Workspace từ 112 file · 1 342 ký hiệu biến thành 2 110 file · 14 545 ký hiệu của repo Great Lotus; root_path bị ghi đè; chỉ còn đúng 1 file A0_scripts/ sót lại (do trùng tên đường dẫn). Thời gian: 25,3 giây.
Cách chữa — khai báo lại dự án bằng POST /api/v1/projects/add với root_path đúng và force_full: true.
Bài học — tham số mặc định nguy hiểm hơn tham số bắt buộc. Và: khi nghi ngờ một hành vi phá huỷ, hãy tái hiện nó trên bản sao trước — một lần thí nghiệm 25 giây đáng giá hơn mười lần suy đoán.
Triệu chứng — bấm Sao Chép ở khối mã trong tab Cấu Hình và tab Hướng Dẫn: không có thông báo thành công, cũng không có thông báo lỗi, bộ nhớ tạm không đổi.
Nguyên nhân thật — navigator.clipboard chỉ tồn tại trong secure context. Mở bằng http://172.16.10.220:20380 thì window.isSecureContext = false và typeof navigator.clipboard = "undefined" (đã đọc thẳng trong trình duyệt). Lời gọi ném TypeError trước khi tới .catch(...), nên nhánh Vui lòng copy thủ công không bao giờ chạy.
Cách chữa tạm — bôi đen + Ctrl+C, hoặc mở http://localhost:20380 khi ngồi trực tiếp trên HP1.
Bài học — .catch() của một Promise không bắt được lỗi ném ra lúc dựng Promise. Khi viết nút phụ thuộc API của trình duyệt, luôn kiểm tra sự tồn tại trước (if (navigator.clipboard) ... else ...).
Vấn đề — alert() và confirm() là cửa sổ của trình duyệt, không nằm trong DOM; ảnh chụp bằng WebDriver không bao giờ thấy chúng, mà gọi lệnh WebDriver trong lúc hộp thoại mở còn bị lỗi.
Cách làm — chụp nguyên màn hình X :99 của B35 bằng ffmpeg -f x11grab chạy trong chính container noVNC, rồi docker cp ảnh ra ngoài. Hộp thoại hiện đầy đủ, kèm dòng 172.16.10.220:20380 says.
Bẫy nhỏ nhưng mất công — WebDriver thấy hộp thoại trước khi màn hình kịp vẽ nó. Lần chụp đầu ra ảnh không có hộp thoại nào. Phải chờ khoảng 1,5 giây rồi mới chụp.
Phần thưởng ngoài dự tính — chính lần chụp hụt ấy lại bắt được trạng thái đang tải của nút Đang quét và thêm dự án… — một trạng thái khó chụp hơn cả hộp thoại.
Bài học — khi công cụ chính không thấy được thứ cần chụp, hãy lùi một tầng: chụp màn hình thật thay vì chụp trang web.
Địa chỉ gốc http://172.16.10.220:20380. Không có xác thực. Danh sách lấy từ /openapi.json và đối chiếu mã server.py.
Phương thức · Đường dẫn |
Tham số |
Trả về |
GET
/health |
— |
status, service, version, server_time, db_path, workspace, uptime_seconds |
GET /api/v1/stats |
— |
projects_count, files_count, symbols_count, edges_count, languages[], symbol_kinds[], edge_types[], top_called[] (10 mục) |
GET /api/v1/metrics |
— |
total_queries_today (thật ra là từ lúc khởi động), endpoint_counts, recent_queries[] (tối đa 30), db_stats |
GET /api/v1/projects |
— |
total, projects[] với id, name, root_path, last_scan_at, total_files, total_symbols, total_edges |
POST /api/v1/projects/add |
thân JSON: name (bắt buộc), root_path (bắt buộc), auto_scan (mặc định true), force_full (mặc định false) |
status, project_id, root_path (đã quy đổi), metrics của lần quét; 400 nếu thư mục không tồn tại trong container |
POST /api/v1/projects/{tên|id}/scan |
thân JSON: force_full (mặc định false) |
status, project, root_path, metrics; 404 nếu không có dự án; 400 nếu thư mục không tồn tại |
DELETE /api/v1/projects/{tên|id} |
— |
số deleted_files, deleted_symbols, deleted_edges; 404 nếu không tìm thấy |
GET /api/v1/search |
q (bắt buộc), kind, file, project, limit (mặc định 50) |
query, total, latency_ms, results[] |
GET /api/v1/callers |
symbol (bắt buộc), limit (mặc định 50) |
symbol, total, latency_ms, callers[] |
GET /api/v1/callees |
file_path, symbol_name (đều bắt buộc, symbol_name phải là full_name) |
file_path, symbol, total, latency_ms, callees[] |
GET /api/v1/dependencies |
file_path (bắt buộc, đường dẫn tương đối) |
file_path, imports[], imported_by[] |
GET /api/v1/structure |
file_path (bắt buộc) |
file_path, total, latency_ms, structure[] |
POST /api/v1/scan |
thân JSON: force_full, workspace_dir, project_name |
quét theo thư mục truyền vào (mặc định /workspace, dự án GreatLotus_Workspace) |
GET /mcp/sse |
— |
luồng SSE; sự kiện đầu tiên trả đường /mcp/messages?sessionId=...; gói keepalive mỗi 20 giây |
POST /mcp/messages |
sessionId (query) + thân JSON-RPC 2.0 |
202 Accepted; kết quả thật gửi qua luồng SSE |
GET|HEAD / · /dashboard · /explorer |
— |
cùng một trang HTML quản trị |
GET /docs · GET /openapi.json |
— |
Swagger UI và đặc tả OpenAPI do FastAPI tự sinh |
LƯU Ý · IMPORTED_BY KHỚP THEO TÊN FILE, NÊN CÓ DƯƠNG TÍNH GIẢ get_file_dependencies tìm file khác import mình bằng target_name LIKE '%<tên file>%'. Với server.py, chuỗi server khớp cả http.server, mcp_server… nên con số 22 file đang import phải đọc là nhiều nhất 22. Với tên module đặc thù (profile_identity) thì kết quả rất sạch. |
Định nghĩa trong mcp_stdio.py → TOOLS_DEFINITION; dùng chung cho cả MCP stdio và MCP SSE.
Công cụ |
Tham số (mặc định) |
Ghi chú quan trọng |
code_graph_search_symbol |
query (bắt buộc), kind, file_filter, project_name, limit (30) |
kind="module" = chỉ liệt kê FILE theo tên; để trống kind thì module được ưu tiên tối đa 5 suất |
code_graph_get_callers |
symbol_name (bắt buộc), limit (40) |
khớp theo tên, không lọc theo dự án — đọc kỹ Chương 13 |
code_graph_get_callees |
file_path, symbol_full_name (đều bắt buộc) |
phải là full_name; sai một ký tự là trả rỗng |
code_graph_get_dependencies |
file_path (bắt buộc) |
hai chiều; chiều ai import tôi có dương tính giả |
code_graph_get_structure |
file_path (bắt buộc) |
cách rẻ nhất để hiểu một file lớn |
code_graph_scan_project |
project_name (GreatLotus_Workspace), workspace_dir (mặc định là đường dẫn HOST của repo Great Lotus), force_full (false) |
QUAN TRỌNG — luôn truyền workspace_dir; xem Chương 19 mục 19.1 |
code_graph_list_projects |
— |
kèm root_path — chỗ nhanh nhất để phát hiện dự án bị quét nhầm |
code_graph_delete_project |
project_name (bắt buộc) |
QUAN TRỌNG — phá huỷ, không xác nhận, không ghi nhật ký |
code_graph_stats |
— |
tốn vài trăm mili-giây vì đếm toàn bảng |
Nhóm |
Danh sách đầy đủ (theo scanner.py) |
Ngôn ngữ nhận diện |
.py → python · .js .jsx .mjs .cjs → javascript · .ts .tsx → typescript · .sh .bash → shell · .json → json · .yaml .yml → yaml · .sql → sql |
Có bộ trích xuất |
python (đầy đủ qua ast) · javascript/typescript (regex, không có cạnh calls) |
Chỉ ghi bản ghi file |
json · yaml · shell · sql — tra được theo tên file, không có ký hiệu bên trong |
Thư mục bị bỏ qua |
.git .github .gemini .claude node_modules __pycache__ .pytest_cache .mypy_cache .ruff_cache .venv venv env .idea .vscode dist build target .next .nuxt .output tmp temp coverage .turbo .cache data — và mọi thư mục bắt đầu bằng dấu chấm |
Đuôi bị chặn thẳng |
.pyc .pyo .pyd .sqlite .sqlite3 .db .tar .gz .zip .rar .7z .iso .bin .exe .dll .so .dylib .png .jpg .jpeg .gif .svg .webp .ico .mp4 .mp3 .wav .pdf .docx .xlsx .woff .woff2 .ttf .eot .log .lock |
File ẩn |
mọi tên bắt đầu bằng dấu chấm đều bị bỏ (điều kiện ngoại lệ cho .env trong mã không có tác dụng, vì .env cũng không nằm trong danh sách ngôn ngữ) |
Thành phần chỉ mục thực tế của GreatLotus_Workspace (20/09/2026): 1 773 file python, 184 json, 119 javascript, 89 shell, 53 yaml, 4 typescript — trong đó 908 file (43 %) thuộc thư viện youtube-dl nhúng sẵn.
Phép đo |
Kết quả |
Cách đo |
GET /api/v1/structure |
6,0 ms |
trung vị 5 lần, file server.py (25 ký hiệu) |
GET /api/v1/search |
10,2 ms |
trung vị 5 lần, từ khoá WorkspaceScanner |
GET /api/v1/callers |
59–62 ms |
trung vị 5 lần, save_file_graph và scan |
GET /api/v1/stats |
125 ms |
trung vị 5 lần; có lần nguội lên tới 2,4 giây |
GET /health lúc bình thường |
vài mili-giây |
vòng lặp curl |
GET /health lúc đang quét |
2 232 ms |
vòng lặp curl 0,25 giây/lần trong lúc Quét Nhanh |
Quét Nhanh, không có gì đổi |
0,507 giây · 2 110 file |
bấm nút trên Web |
Quét Nhanh, 13 file mới + 24 sửa |
2,45 giây · 2 110 file |
bấm nút trên Web |
Quét toàn bộ (mọi file đều mới) |
25,3 giây · 2 110 file |
thí nghiệm trên bản sao, Python 3.10 của host |
Quét lần đầu CMEV |
2,578 giây · 112 file |
nhật ký ngày 13/09 |
grep -rn một từ khoá qua toàn repo |
≈ 0,7 giây |
3 từ khoá khác nhau, kết quả gần như nhau |
Bộ nhớ container |
62,93 MiB |
docker stats --no-stream |
Kích thước chỉ mục |
30 MB cho 2 222 file ≈ 13,5 KB/file |
ls -la data/ |
Thuật ngữ |
Nghĩa trong hệ B38 |
AST |
Abstract Syntax Tree — cây cú pháp mà Python dựng ra khi đọc mã; B38 đi trên cây này để lấy class/hàm/lời gọi thay vì dò chữ |
Ký hiệu (symbol) |
một class, hàm, phương thức, hàm bất đồng bộ hoặc interface đã được ghi nhận |
Cạnh (edge) |
quan hệ giữa hai tên: calls, imports, inherits |
full_name |
tên có ngữ cảnh, ví dụ CodeGraphDB.save_file_graph; khoá để nối ký hiệu với cạnh |
<module> |
giá trị của source_symbol khi lời gọi nằm ở thân file, không nằm trong hàm nào |
module (kind) |
loại kết quả đặc biệt: một FILE khớp theo tên, chữ ký hiện (N symbols) |
Callers / Callees |
nơi gọi đến một hàm / các lời gọi bên trong một hàm |
Quét Nhanh (incremental) |
chỉ đọc lại file có mtime/size/md5 khác trước |
Full Scan |
đọc và phân tích lại mọi file |
MCP |
Model Context Protocol — giao thức để AI Agent gọi công cụ; B38 hiện thực hai kiểu vận chuyển: SSE và stdio |
stdio / SSE |
hai kiểu vận chuyển MCP: chạy tiến trình trao đổi qua stdin-stdout, hoặc luồng sự kiện HTTP |
WAL |
Write-Ahead Log của SQLite; thay đổi mới nằm ở file -wal cho tới khi được gộp |
Bind-mount |
gắn thư mục của host vào container; khác docker volume — và đây là lý do B38 không nằm trong bản sao lưu volume của B00 |
Secure context |
điều kiện của trình duyệt (HTTPS hoặc localhost) để mở một số API như clipboard |
Mã nguồn — B38_Code_Graph/app/{graph_engine,scanner,server,mcp_stdio,cli}.py, Dockerfile, requirements.txt, .gitignore, lịch sử git của repo B38_Code_Graph.
Cấu hình — DCP_PRODUCTION/docker-compose.yml (khối lotus_code_graph), ~/.claude.json, ~/.gemini/config/mcp_config.json, Library/A0_Gemini_Worker_MCP/worker_pool.py.
Hệ thống sống — GET /health, /api/v1/stats, /projects, /metrics, /openapi.json; truy vấn chỉ đọc vào code_graph.sqlite; docker ps/stats/inspect/logs; data/usage.jsonl.
Tài liệu nội bộ — docs/b38-code-graph.md, docs/network/network-overview.md, docs/workspace-split-2026-09-13.md của workspace CMEV, và kho ghi nhớ của phiên làm việc.
Chặng |
Công cụ |
Kết quả |
Chụp ảnh |
Chrome thật trên B35 (docs/tools/docgen/shot_kit.py), thêm ảnh màn hình X :99 bằng ffmpeg -f x11grab cho các hộp thoại gốc |
34 ảnh chụp |
Đánh số |
docs/tools/docgen/annotate_kit.py — vòng tròn chỉ tô viền, đặt ngoài phần tử theo toạ độ getBoundingClientRect() ghi lúc chụp |
mỗi thành phần một số |
Vẽ sơ đồ |
matplotlib, phong cách phẳng, tiếng Việt có dấu, không emoji |
13 hình tự vẽ |
Dựng sách |
docs/tools/docgen/doc_kit.py + các module c1…c9, khổ A4, Heading style thật |
.docx |
Xuất PDF |
docs/tools/docgen/export_pdf.py (LibreOffice UNO, cập nhật mục lục 2 lượt) |
|
Bản HTML |
soffice --convert-to html rồi docs/tools/docgen/html_readable.py, xuất bản qua /UID_01_publish_html_doc |
trang đọc được trên điện thoại |
GHI CHÚ · ẢNH TRONG SÁCH CHỤP Ở HAI THỜI ĐIỂM Phần lớn ảnh giao diện chụp lúc 23:29 ngày 19/09/2026, khi chỉ mục còn mang số liệu của lần quét 13/09. Các ảnh trong Chương 15 (vòng đời dự án) chụp lúc 23:36–23:41 cùng ngày, sau khi đã Quét Nhanh. Vì vậy số file trên ảnh có chỗ là 2 097/2 209, có chỗ là 2 110/2 222 — đó là chủ ý, không phải sai sót. |
Mọi thao tác ghi đã được hoàn tác: dự án thử B38_TU_QUET đã xoá, hệ thống trả về đúng hai dự án CMEV_Workspace và GreatLotus_Workspace. Thay đổi duy nhất còn lại là chỉ mục Great Lotus đã được cập nhật (từ 13/09 lên 19/09) — một thay đổi có lợi và đằng nào cũng cần làm.