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.





Lời nói đầu

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).

Kỷ luật nguồn — vì sao có thể tin những con số trong sách này

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:

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.



Trạng thái hệ thống lúc biên soạn

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 …



Sách này KHÔNG nói về cái gì



Mục lục

⟳ 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





Chương 1 — B38 Lotus Code Graph là gì

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.

1.1 Bài toán B38 giải quyết

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.



1.2 Bốn thứ B38 trả lời được

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.



1.3 Khi nào dùng B38, khi nào vẫn phải grep

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.

1.4 Hai dự án B38 đang giữ

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.

Chương 2 — Ai dùng B38 và ranh giới trách nhiệm

2.1 Bốn nhóm người dùng

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.

2.2 Ranh giới trách nhiệm — B38 làm gì và KHÔNG làm gì

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.



2.3 B38 đứng ở đâu trong hệ Great Lotus

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.

Chương 3 — Lịch sử phát triển và các mốc đã trả giá

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.



3.1 Một sự thật khó chịu: B38 gần như không được dùng

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





Chương 4 — Container và cấu hình triển khai

4.1 Khai báo trong docker-compose

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.



4.2 Ảnh nền (image) và thư viện

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.

Chương 5 — Bản đồ mã nguồn

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.



5.1 Các hàm truy vấn của CodeGraphDB

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ý



Chương 6 — Lược đồ dữ liệu SQLite

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.

6.1 Ý nghĩa từng cột quan trọng

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>



6.2 Kích thước và chế độ WAL

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.



Chương 7 — Bộ quét — quét cái gì, bỏ qua cái gì, mất bao lâu

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).

7.1 Những gì bị bỏ qua — và hệ quả

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/.



7.2 Quét Nhanh và Full Scan khác nhau ở đâu


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



7.3 Quét mất bao lâu — số đo thật

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.

Chương 8 — Bộ trích xuất — B38 hiểu được gì trong mã

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.

8.1 Python — phân tích bằng ast, khá đầy đủ

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.



8.2 JavaScript / TypeScript — phân tích bằng biểu thức chính quy, rất nông

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.



8.3 Các file còn lại

.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





Chương 9 — Khung chung của trang quản trị

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.

9.1 Thanh đầu trang

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í.



9.2 Bốn thẻ số tổng quan

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



9.3 Giao diện trên điện thoại

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.



Chương 10 — Tab Dashboard

10.1 Bảng *Danh Sách Dự Án Đang Giám Sát*

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



10.2 Khối *Phân Bố Ngôn Ngữ* và *Top 10 Ký Hiệu Được Gọi Nhiều Nhất*

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).



10.3 Khối *Tần Suất Truy Xuất & Nhật Ký Hoạt Động*

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.



Chương 11 — Tab Cấu Hình & Dự Án

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



11.1 Khối *Trợ Lý Cấu Hình Docker Compose Volume*

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').



11.2 Hộp thoại *Thêm Dự Án Mới*

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



11.3 Bốn hộp thoại hệ thống

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.



Chương 12 — Tab Hướng Dẫn & AI Prompts

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.



Chương 13 — Tab Tra Cứu AST

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.



13.1 Kết quả tìm ký hiệu

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.



13.2 Tìm theo TÊN FILE / MODULE

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.

13.3 Lọc theo loại 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ế.



13.4 Khi không có kết quả

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.



13.5 Xem nơi gọi (Callers)

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.

13.6 Trần 50 dòng — chỗ dễ hiểu sai nhất

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.



13.7 Xem lời gọi bên trong (Callees)

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





Chương 14 — Tra cứu bằng REST, CLI và MCP

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.

14.1 REST API — dùng curl từ bất kỳ máy nào trong LAN

Đị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.



14.2 Dòng lệnh cli.py — chạy trên host, không cần container

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.



14.3 MCP — cách AI Agent dùng B38

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ế:

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.



14.4 Bên trong một phiên MCP SSE

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 15 — Quản lý vòng đời một dự án

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.

15.1 Thêm một dự án nằm TRONG workspace đã mount

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.

15.2 Thêm một dự án nằm NGOÀI workspace

Đâ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.



15.3 Quét lại

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.

15.4 Kiểm tra sau khi quét

# 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).

15.5 Xoá một dự án

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





Chương 16 — Theo dõi sức khoẻ và đọc nhật ký

16.1 Ba câu hỏi và ba lệnh

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 %.

16.2 Hai nhật ký khác nhau — đừng nhầm


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.



16.3 Nhật ký của container

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.

16.4 Trang tài liệu API tự sinh

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ương 17 — Bảo trì chỉ mục — quét khi nào và tốn gì

17.1 B38 KHÔNG tự quét

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.

17.2 Thói quen đề xuất

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



17.3 Chỉ mục phình to tới đâu

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





Chương 18 — Sao lưu, khôi phục và ranh giới bảo mật

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.

18.1 Vì sao chỉ mục KHÔNG cần sao lưu

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.



18.2 Sao lưu đúng cách nếu vẫn muốn giữ chỉ mụ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.



18.3 Khôi phục từ con số không

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.



18.4 Ranh giới bảo mật

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Ạ





Chương 19 — Tám kịch bản hỏng và cách xử lý

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.

19.1 Quét nhầm thư mục vào dự án khác (nguy hiểm nhất)

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ộ:
curl -X POST -H 'Content-Type: application/json' -d '{"name":"CMEV_Workspace", "root_path":"/workspace_cmev", "auto_scan":true, "force_full":true}' http://127.0.0.1:20380/api/v1/projects/add

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.



19.2 Xoá nhầm dự án


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



19.3 B38 đứng hình khi đang quét


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)



19.4 Chỉ mục cũ khiến agent kết luận sai


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



19.5 Sửa mã app/*.py sai cú pháp — container vào vòng khởi động lại


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



19.6 Đổi tên hoặc di chuyển thư mục mã nguồn


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



19.7 Mất thư mục data/


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



19.8 Cổng 20380 lọt ra ngoài LAN


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Ố





Chương 20 — Bảng tra: triệu chứng → nguyên nhân → cách chữa

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





Chương 21 — Tám bài 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.

Bài 1 — Đọc sức khoẻ và quy mô chỉ mục

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:

Bài 2 — Bốn kiểu truy vấn trên một hàm

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ài 3 — So sánh B38 với grep trên cùng một câu hỏi

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:

Bài 4 — Tìm theo tên file và hiểu giới hạn

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:

Bài 5 — Đọc nhật ký để biết ai đang dùng B38

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:

Bài 6 — Chứng minh chỉ mục cũ gây kết luận sai

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:

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).



Bài 7 — Vòng đời một dự án thử

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:

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.



Bài 8 — Đo mức chặn khi quét

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:

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ụ lục A — Những chuyện đã trả giá

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.

A.1 Rule bắt dùng B38 đã viết cả tháng, nhưng công cụ chưa từng được nối vào (17/08/2026)

A.2 Gõ đúng tên file mà ra 0 kết quả (17/08/2026)

A.3 Bộ đếm ra đời, và sự thật phơi ra (18/08 → 19/09/2026)

A.4 Tách workspace CMEV mà vẫn dùng chung một B38 (13/09/2026)

A.5 Bản sao cơ sở dữ liệu thiếu mất những thay đổi mới nhất (19/09/2026)

A.6 Một lời gọi MCP thiếu tham số xoá sạch chỉ mục một dự án (thí nghiệm 19/09/2026)

A.7 Nút bấm chết lặng vì trang không chạy trên HTTPS (19/09/2026)

A.8 Chụp được hộp thoại gốc của trình duyệt (kinh nghiệm dựng sách)

Phụ lục B — REST API đầy đủ

Đị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
GET /api/v1/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.



Phụ lục C — Chín công cụ MCP

Đị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



Phụ lục D — Phạm vi quét: ngôn ngữ, thư mục và đuôi file

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ụ lục E — Bảng số đo hiệu năng (19–20/09/2026)

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/



Phụ lục F — Thuật ngữ

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



Phụ lục G — Nguồn và cách dựng tài liệu này

G.1 Nguồn đã đọc

G.2 Cách dựng

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)

.pdf

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.