@mrtrinhvn/ag-kit 1.4.12 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,16 @@ AG-Kit sử dụng kỹ thuật **JSON Introspection** và **Chromium Multiplexi
73
73
 
74
74
  ---
75
75
 
76
+ ## 🛡️ Zero Context-Contamination (Cách Ly Ruy Băng Phiên Bản 1.5.0+)
77
+
78
+ **AG-Kit 1.5.0 đánh dấu kỷ nguyên Kiến trúc Phân Tán (Federated Architecture)**, chặn đứng hoàn toàn hiện tượng AI râu ông nọ cắm cằm bà kia:
79
+
80
+ 1. **MCP Memory Sandbox:** Mọi truy vấn Lưu/Ra-Đa Ký Ức đều bị ép khóa vào cờ `workspace_path`. Không bao giờ có chuyện AI tiêm nhầm dữ liệu chứng khoán vào dự án quản trị nhân sự.
81
+ 2. **Process Lifecycle Shield:** Xóa bỏ bộ Lock PID truyền thống tại thư mục chung `/tmp`. AG-Kit v1.5 thiết lập cơ chế tự dọn rác Bóng Ma Task (Ghost Session) dựa trên sự kiện `lifespan` FastAPI / Node, giúp Bot chống sập trong môi trường Docker Container.
82
+ 3. **Identity Shifting (Thuật Biến Hình):** Agent sử dụng AG-Kit có khả năng soi mã nguồn Xuyên Dự Án (Cross-Project Vision) để học lỏm mẫu thiết kế. Nhưng khi đóng góp code/trí nhớ cho nhà hàng xóm, Bot sẽ tự động **nhập gia tùy tục** và dùng chính ngôn ngữ của dự án đó!
83
+
84
+ ---
85
+
76
86
  ## 🔄 Cập nhật & Bảo trì
77
87
 
78
88
  Chúng tôi liên tục nâng cấp kỹ năng cho Agent (Skills, Workflows). Để bộ não dự án của bạn luôn thông minh nhất:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrtrinhvn/ag-kit",
3
- "version": "1.4.12",
3
+ "version": "1.5.0",
4
4
  "description": "Antigravity Kit Base Framework - Generic Agentic AI Programming Core",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -66,6 +66,11 @@ def cosine_similarity(v1: bytes, v2: bytes) -> float:
66
66
 
67
67
 
68
68
  # ─── DB ────────────────────────────────────────────────────────────────────
69
+ def resolve_db_path(args: dict, fallback_db: Path) -> Path:
70
+ workspace_path = args.get("workspace_path")
71
+ if workspace_path:
72
+ return Path(workspace_path) / ".agent" / "memory" / "graph.db"
73
+ return fallback_db
69
74
 
70
75
  def get_db(db_path: Path) -> sqlite3.Connection:
71
76
  db_path.parent.mkdir(parents=True, exist_ok=True)
@@ -110,7 +115,8 @@ def _ensure_schema(conn):
110
115
 
111
116
  # ─── Tool Implementations ──────────────────────────────────────────────────
112
117
 
113
- def tool_memory_save(args: dict, db: Path) -> str:
118
+ def tool_memory_save(args: dict, fallback_db: Path) -> str:
119
+ db = resolve_db_path(args, fallback_db)
114
120
  content = args.get("content", "").strip()
115
121
  if not content: return "❌ content is required"
116
122
  category = args.get("category", "general")
@@ -131,7 +137,8 @@ def tool_memory_save(args: dict, db: Path) -> str:
131
137
  return f"✅ {emb_status} Saved hot node #{cur.lastrowid} [{category}]: {content[:80]}"
132
138
 
133
139
 
134
- def tool_memory_search(args: dict, db: Path) -> str:
140
+ def tool_memory_search(args: dict, fallback_db: Path) -> str:
141
+ db = resolve_db_path(args, fallback_db)
135
142
  keyword = args.get("keyword", "").strip()
136
143
  if not keyword: return "❌ keyword is required"
137
144
  tier = args.get("tier")
@@ -186,7 +193,8 @@ def tool_memory_search(args: dict, db: Path) -> str:
186
193
  return "\n".join(lines)
187
194
 
188
195
 
189
- def tool_memory_link(args: dict, db: Path) -> str:
196
+ def tool_memory_link(args: dict, fallback_db: Path) -> str:
197
+ db = resolve_db_path(args, fallback_db)
190
198
  from_id = args.get("from_id")
191
199
  to_id = args.get("to_id")
192
200
  relation = args.get("relation", "related_to")
@@ -207,7 +215,8 @@ def tool_memory_link(args: dict, db: Path) -> str:
207
215
  return f"🔗 #{from_id} --[{relation}]--> #{to_id}\n From: {a['content'][:60]}\n To: {b['content'][:60]}"
208
216
 
209
217
 
210
- def tool_memory_graph(args: dict, db: Path) -> str:
218
+ def tool_memory_graph(args: dict, fallback_db: Path) -> str:
219
+ db = resolve_db_path(args, fallback_db)
211
220
  node_id = args.get("node_id")
212
221
  if not node_id: return "❌ node_id is required"
213
222
  conn = get_db(db)
@@ -229,7 +238,8 @@ def tool_memory_graph(args: dict, db: Path) -> str:
229
238
  return "\n".join(out)
230
239
 
231
240
 
232
- def tool_memory_hot(args: dict, db: Path) -> str:
241
+ def tool_memory_hot(args: dict, fallback_db: Path) -> str:
242
+ db = resolve_db_path(args, fallback_db)
233
243
  limit = int(args.get("limit", 10))
234
244
  conn = get_db(db)
235
245
  rows = conn.execute(
@@ -245,7 +255,8 @@ def tool_memory_hot(args: dict, db: Path) -> str:
245
255
  return "\n".join(lines)
246
256
 
247
257
 
248
- def tool_memory_cold(args: dict, db: Path) -> str:
258
+ def tool_memory_cold(args: dict, fallback_db: Path) -> str:
259
+ db = resolve_db_path(args, fallback_db)
249
260
  limit = int(args.get("limit", 10))
250
261
  conn = get_db(db)
251
262
  rows = conn.execute(
@@ -262,7 +273,8 @@ def tool_memory_cold(args: dict, db: Path) -> str:
262
273
  return "\n".join(lines)
263
274
 
264
275
 
265
- def tool_memory_consolidate(args: dict, db: Path) -> str:
276
+ def tool_memory_consolidate(args: dict, fallback_db: Path) -> str:
277
+ db = resolve_db_path(args, fallback_db)
266
278
  days = int(args.get("days", 7))
267
279
  category = args.get("category")
268
280
  threshold = (datetime.now() - timedelta(days=days)).isoformat()
@@ -307,7 +319,8 @@ def tool_memory_consolidate(args: dict, db: Path) -> str:
307
319
  return "\n".join(results) + f"\n\n✅ Done. Summary vectors calculated."
308
320
 
309
321
 
310
- def tool_memory_status(args: dict, db: Path) -> str:
322
+ def tool_memory_status(args: dict, fallback_db: Path) -> str:
323
+ db = resolve_db_path(args, fallback_db)
311
324
  conn = get_db(db)
312
325
  total = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
313
326
  hot = conn.execute("SELECT COUNT(*) FROM nodes WHERE tier='hot'").fetchone()[0]
@@ -334,6 +347,7 @@ TOOLS = {
334
347
  "inputSchema": {
335
348
  "type": "object",
336
349
  "properties": {
350
+ "workspace_path": {"type": "string", "description": "Local workspace root path to avoid cross-project contamination"},
337
351
  "content": {"type": "string", "description": "The knowledge to save"},
338
352
  "category": {"type": "string", "enum": list(VALID_CATEGORIES), "description": "Category of knowledge"}
339
353
  },
@@ -345,6 +359,7 @@ TOOLS = {
345
359
  "inputSchema": {
346
360
  "type": "object",
347
361
  "properties": {
362
+ "workspace_path": {"type": "string", "description": "Local workspace root path"},
348
363
  "keyword": {"type": "string", "description": "Keyword to search"},
349
364
  "tier": {"type": "string", "enum": ["hot", "cold"], "description": "Search only in hot or cold tier"},
350
365
  "limit": {"type": "integer", "description": "Max results (default 8)"}
@@ -354,13 +369,13 @@ TOOLS = {
354
369
  },
355
370
  "memory_link": {
356
371
  "description": "Create a relationship (edge) between two memory nodes.",
357
- "inputSchema": {"type": "object", "properties": {"from_id": {"type": "integer"}, "to_id": {"type": "integer"}, "relation": {"type": "string"}}, "required": ["from_id", "to_id"]}
372
+ "inputSchema": {"type": "object", "properties": {"workspace_path": {"type": "string"}, "from_id": {"type": "integer"}, "to_id": {"type": "integer"}, "relation": {"type": "string"}}, "required": ["from_id", "to_id"]}
358
373
  },
359
- "memory_graph": {"description": "Show all edges (incoming and outgoing) for a node.", "inputSchema": {"type": "object", "properties": {"node_id": {"type": "integer"}}, "required": ["node_id"]}},
360
- "memory_hot": {"description": "List hot (working memory) nodes.", "inputSchema": {"type": "object", "properties": {"limit": {"type": "integer"}}}},
361
- "memory_cold": {"description": "List cold (consolidated long-term memory) nodes.", "inputSchema": {"type": "object", "properties": {"limit": {"type": "integer"}}}},
362
- "memory_consolidate": {"description": "Merge old hot nodes into cold consolidated summaries.", "inputSchema": {"type": "object", "properties": {"days": {"type": "integer"}, "category": {"type": "string"}}}},
363
- "memory_status": {"description": "Show memory graph statistics.", "inputSchema": {"type": "object", "properties": {}}}
374
+ "memory_graph": {"description": "Show all edges (incoming and outgoing) for a node.", "inputSchema": {"type": "object", "properties": {"workspace_path": {"type": "string"}, "node_id": {"type": "integer"}}, "required": ["node_id"]}},
375
+ "memory_hot": {"description": "List hot (working memory) nodes.", "inputSchema": {"type": "object", "properties": {"workspace_path": {"type": "string"}, "limit": {"type": "integer"}}}},
376
+ "memory_cold": {"description": "List cold (consolidated long-term memory) nodes.", "inputSchema": {"type": "object", "properties": {"workspace_path": {"type": "string"}, "limit": {"type": "integer"}}}},
377
+ "memory_consolidate": {"description": "Merge old hot nodes into cold consolidated summaries.", "inputSchema": {"type": "object", "properties": {"workspace_path": {"type": "string"}, "days": {"type": "integer"}, "category": {"type": "string"}}}},
378
+ "memory_status": {"description": "Show memory graph statistics.", "inputSchema": {"type": "object", "properties": {"workspace_path": {"type": "string"}}}}
364
379
  }
365
380
 
366
381
  TOOL_FNS = {
@@ -101,18 +101,20 @@ Server `ag-kit-memory` được đăng ký sẵn trong `~/.gemini/antigravity/mc
101
101
 
102
102
  | Tool | Khi nào gọi |
103
103
  |---|---|
104
- | `memory_search(keyword)` | **TRƯỚC** khi bắt đầu bất kỳ task nào — kiểm tra context cũ |
105
- | `memory_save(content, category)` | **SAU** khi fix bug, ra quyết định, học được điều mới |
106
- | `memory_link(from, to, relation)` | Khi phát hiện 2 kiến thức có quan hệ |
107
- | `memory_consolidate(days=7)` | Hàng tuần: gộp hot → cold summaries |
108
- | `memory_status()` | Kiểm tra sức khỏe của bộ não |
104
+ | `memory_search(keyword, workspace_path)` | **TRƯỚC** khi bắt đầu bất kỳ task nào — kiểm tra context cũ |
105
+ | `memory_save(content, category, workspace_path)` | **SAU** khi fix bug, ra quyết định, học được điều mới |
106
+ | `memory_link(from, to, relation, workspace_path)` | Khi phát hiện 2 kiến thức có quan hệ |
107
+ | `memory_consolidate(days=7, workspace_path)` | Hàng tuần: gộp hot → cold summaries |
108
+ | `memory_status(workspace_path)` | Kiểm tra sức khỏe của bộ não |
109
+
110
+ > **⚠️ QUAN TRỌNG VỀ ISO-WORKSPACE (MANDATORY)**: Tất cả các tool của `ag-kit-memory` BẮT BUỘC phải nhận tham số `workspace_path` là đường dẫn gốc CWD hiện tại của dự án (VD: `/home/tao/Projects/chungkhoan`). Nếu không truyền, MCP sẽ ghi đè database hoặc đọc sai ký ức của dự án khác gây thảm họa ô nhiễm dữ liệu!
109
111
 
110
112
  ```
111
113
  Workflow đúng:
112
- 1. Bắt đầu task → memory_search("topic") → đọc context cũ
114
+ 1. Bắt đầu task → memory_search("topic", workspace_path="/...") → đọc context cũ
113
115
  2. Làm việc...
114
- 3. Xong task → memory_save("kết quả/quyết định quan trịng")
115
- 4. Hàng tuần → memory_consolidate(days=7) → dọn rác, tổng hợp
116
+ 3. Xong task → memory_save(content="...", workspace_path="/...")
117
+ 4. Hàng tuần → memory_consolidate(workspace_path="/...")
116
118
  ```
117
119
 
118
120
 
@@ -134,7 +136,8 @@ Hãy khắc cốt ghi tâm quy tắc: `IDE_PORT` (Nhận lệnh AI - ví dụ: 9
134
136
  3. Nếu cần đi sâu hơn: dùng `vfs <project_root>/src` hoặc `memory_tool.py search`.
135
137
  4. **CẤM** scan toàn bộ thư mục project khác khi chưa đọc `summary.md` trước.
136
138
  - **Cấm ô nhiễm (No Leakage):** Tuyệt đối không ghi chép các từ khóa chuyên biệt (B1, B2, Sentinel, DNSE...) vào các file Skill dùng chung.
137
- - **Tổng quát hóa (Sanitization):** Mọi pattern học được từ project A khi đưa vào Core Framework đều phải tẩy sạch tên nghiệp vụ. Trước khi trích xuất pattern lớn, hỏi Sếp: *"Em thấy pattern X dự án Y rất hay, em xin phép tổng quát hóa không?"*
139
+ - **Thuật Biến Hình (Identity Shifting - Nhập Gia Tùy Tục):** Khi Sếp giao phó điều hướng bạn sang thao tác hoặc sửa lỗi một Dự Án X (trong khi bạn đang máy chủ Dự Án Y), **thì ngay giây phút bạn đặt chân sang thư mục nhà X, bạn PHẢI tự coi mình chính Agent của nhà X!**
140
+ - *Hành động:* Bạn nắm toàn quyền và **CÓ TRÁCH NHIỆM BẮT BUỘC** ghi chú những bài học, fix bug, và pattern trực tiếp vào cuốn sổ `.agent/knowledge/` hoặc gọi `memory_save(workspace_path="/home/tao/Projects/Dự_Án_X")` của chính nhà đó, bằng chính đặc thù ngôn ngữ của Dự Án đó (Ví dụ sang ceogravity thì cứ thoải mái gõ chữ B1, B2)! Đừng chỉ để lại lời nhắn như người dưng, hãy hành xử như một người quản gia thực thụ của bất kỳ dự án nào bạn được đưa tới. Mọi giá trị tri thức phải được gửi gắm chính xác vào Local Context của nơi nó nảy mầm.
138
141
 
139
142
  ---
140
143
 
@@ -60,6 +60,18 @@ priority: CRITICAL
60
60
 
61
61
  ---
62
62
 
63
+ ## State & Process Management Rules (MANDATORY)
64
+
65
+ Để đảm bảo hệ thống không bị mắc kẹt (Ghost Sessions) khi các Service chạy trong Docker hoặc bị Reboot Host, mọi Agent **BẮT BUỘC** phải tuân thủ nghiêm ngặt 3 quy tắc sau khi viết các Background Tasks/Queues:
66
+
67
+ | 🚨 Rule Nhiễm Độc | ✅ Giải pháp Chuẩn mực |
68
+ |---|---|
69
+ | Dùng `os.kill(pid, 0)` để kiểm tra Task còn sống | **CẤM!** PID của Docker luôn reset về thấp (VD: 1, 8), vòng lặp PID sẽ gây ra "Ghost Session". Đổi sang dùng timeout check. |
70
+ | Lưu File Lock/PID ở `Root /tmp/` | **RỦI RO XUNG ĐỘT!** Các dự án chạy chung 1 VPS sẽ chọc nhầm PID của nhau. Bắt buộc lưu vào ranh giới nội bộ: `.agent/run/` |
71
+ | Quên làm sạch Database Trạng thái khi Server boot | **THẢM HỌA UI!** Task đang chạy dở mà Host sập, khi khởi động lại cờ `is_running` vẫn kẹt `true`. BẮT BUỘC phải cấy hàm `reset_all_ghost_sessions()` vào sự kiện `lifespan` lúc FastAPI/Node Server khởi chạy. |
72
+
73
+ ---
74
+
63
75
  ## AI Coding Style
64
76
 
65
77
  | Situation | Action |
@@ -340,6 +340,12 @@ For animation patterns: [animation-guide.md](animation-guide.md), for advanced:
340
340
  - **Same layout structure / Vercel clone**
341
341
  - **Not asking user preferences**
342
342
 
343
+ ### ❌ Front-end Performance Anti-Patterns
344
+
345
+ - **Thảm họa Polling bằng `setInterval`**: Khi Fetch API liên tục, nếu mạng chậm, các request sẽ bị kẹt dồn ứ gây ra lỗi `ERR_INSUFFICIENT_RESOURCES` sập Browser Socket. **MANDATORY**: Thay vì `setInterval`, phải dùng đệ quy `setTimeout` (Chờ request Promise A hoàn tất thì mới trigger request B).
346
+ - Chèn trực tiếp quá nhiều thư viện nặng vào Bundle thay vì Lazy Load.
347
+ - Không có cơ chế Debounce/Throttle cho các thẻ Input Search.
348
+
343
349
  ### ❌ Dark Patterns (Unethical)
344
350
 
345
351
  - Hidden costs
@@ -147,6 +147,11 @@ my-mcp-server/
147
147
  | args | Command arguments |
148
148
  | env | Environment variables |
149
149
 
150
+ ### ⚠️ Database Context Mismatch (Global vs Local)
151
+ Khi thiết kế MCP lưu trữ dữ liệu State hoặc Vector DB, phải lường trước tình huống MCP chạy ở tư cách Global (ví dụ cài qua `npm install -g` hoặc qua Claude Desktop) nhưng System Client chạy ở Local project.
152
+ - **Rủi ro:** Lỗi "Lạc đường CSDL". MCP sẽ ghi cứng data vào biến Workspace lúc nó được start (Vd: ghi đè Database dự án A trong khi User đang thao tác bên dự án B).
153
+ - **Giải pháp:** Server MCP cần có khả năng linh hoạt đọc `cwd` (current working directory) của Client đang tương tác, hoặc nhận đường dẫn Database rõ ràng thông qua Argument Input của Tool để chắc chắn dữ liệu được tiêm đúng vào **Local Context** của User.
154
+
150
155
  ---
151
156
 
152
157
  ## 9. Testing
@@ -105,3 +105,15 @@ Khi bạn (AI) nhận thức được mình đang ở trong một dự án đã
105
105
 
106
106
  Bằng giao thức này, kiến thức của dự án sẽ KHÔNG BAO GIỜ bị đứt gãy sau khi cập nhật kiến trúc. Mọi ký ức quá khứ sẽ tự động "tiến hóa" thành dạng Đồ thị tương lai!
107
107
 
108
+ ---
109
+
110
+ ## 8. Công cụ Gỡ Rối Định Tuyến (Brain Debugging CLI)
111
+
112
+ Trong quá trình bảo trì trí nhớ (ví dụ: cần truy vấn SQL trực tiếp để tìm kiếm Vector, xóa các Node bị kẹt bằng tay gốc rễ), bạn hoặc User **BẮT BUỘC** phải cài đặt công cụ CLI `sqlite3` trên môi trường Terminal.
113
+
114
+ Lỗi **"Command 'sqlite3' not found"** là nguyên nhân số một khiến AI Agent hoặc Developer không thể thâm nhập sâu vào file `graph.db` để debug cục bộ.
115
+ - **Trên Ubuntu/Debian:** Yêu cầu User chạy `sudo apt install sqlite3 -y`
116
+ - **Trên Mac (Homebrew):** Yêu cầu User chạy `brew install sqlite`
117
+ - **Trên Windows:** Cài đặt thông qua Winget hoặc tải Binaries.
118
+
119
+ > **Trách nhiệm của AI:** Trước khi chạy các kịch bản python hoặc bash để truy vấn `.agent/memory/graph.db`, hãy kiểm tra xem `sqlite3` có tồn tại trên Host không. Nếu không, hãy thông báo ngay cho User để cài đặt công cụ tối thượng này! Mọi sự cố thiếu `sqlite3` không thuộc về bộ não AG-Kit mà là lỗi thiếu Dependencies môi trường.
@@ -337,6 +337,11 @@ Celery/ARQ:
337
337
  └── Complex workflows
338
338
  ```
339
339
 
340
+ ### ⚠️ Docker Ghost Session Warning
341
+ Khi thiết kế ứng dụng có Background task tracking lưu trữ State (VD: vào file/JSON hoặc SQLite) với cờ `is_running: true`.
342
+ **TUYỆT ĐỐI KHÔNG** phụ thuộc hoàn toàn vào check Process ID `os.kill(pid, 0)` để xác định tiến trình còn sống hay không. Trong Docker container khi restart, hệ điều hành reset PID về thấp (VD: cấp lại PID 1, 8), kiến `os.kill` quét nhầm và gây ra kẹt trạng thái **Ghost Session**.
343
+ **Giải pháp kiến trúc**: Gắn cơ chế ép dọn dẹp trạng thái `force_reset_all()` vào ngay `lifespan` hoặc `startup` event của FastAPI để triệt tiêu các bóng ma trước khi API online.
344
+
340
345
  ---
341
346
 
342
347
  ## 8. Error Handling Principles
@@ -86,6 +86,7 @@ Khi triển khai, hãy hướng dẫn User:
86
86
  - **Tuyệt đối KHÔNG hardcode cổng cố định (như 9555).** Hạ tầng phải hỗ trợ Scale (sinh nhiều Agent song song).
87
87
  - Hãy cấp một dải khoảng 100 cổng (Ví dụ: `9555-9655`).
88
88
  - Script khởi động (`start.sh`) phải có logic tự dò cổng nào đang trống trong dải 100 cổng này $\rightarrow$ Gán cho tiến trình Antigravity IDE $\rightarrow$ Cập nhật `.env` (`IDE_PORT=95xx`) $\rightarrow$ Kẻ điều phối (Node.js) đọc file `.env` để kết nối vào đúng IDE đó.
89
+ - ⚠️ **Lưu ý Networking (Docker vs Host):** Nếu Kẻ điều phối (Gateway) chạy trực tiếp trên Host (qua Node.js) sử dụng Port `9656`, còn Hệ thống Lõi xử lý NLP (Python, Backend) chạy bên trong Docker Container, thì Hệ thống Lõi KHÔNG THỂ gọi ngược ra Gateway bằng `127.0.0.1` hay `localhost`. Bắt buộc phải cấu hình biến môi trường kết nối trỏ tới **IP Bridge của Docker** (thường là `172.17.0.1` trên Linux hoặc `host.docker.internal` trên Mac/Windows). VD: `GATEWAY_URL=http://172.17.0.1:9656`. Mặc định gọi localhost sẽ văng lỗi `Connection Refused` hoặc `Failed to fetch`.
89
90
  5. **Độc Lập Tên Gọi (Project-Agnostic Naming)**:
90
91
  - Các file script điều phối (`receptionist_up.sh`, `receptionist_down.sh`...) **TUYỆT ĐỐI KHÔNG ĐƯỢC MANG TÊN DỰ ÁN CỤ THỂ** (Ví dụ cấm gõ cứng: `Waking up CEOgravity Bot` hay `.ceogravity_bot.pid`).
91
92
  - Phải sử dụng danh xưng danh chuẩn mực là **"AG Gateway Bot"** và các file tracking trung lập (Vd: `.ag_gateway_bot.pid`). Điều này là bắt buộc để biến hệ sinh thái `.agent/` thành một Template hoàn hảo có thể bưng thả (White-label) vào bất kỳ dự án nào (NextJS, Python, Rust...) mà không bị ô nhiễm tên dự án cũ.