@dtd-dev/agent-kb 0.1.0 → 0.1.2
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 +35 -14
- package/bin/cli.js +110 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,9 @@ Không cài global, không cần clone. Zero-dependency nên `npx` chạy tức
|
|
|
36
36
|
| `help` | Trợ giúp |
|
|
37
37
|
|
|
38
38
|
Chạy tương tác sẽ hỏi: **tên dự án, backend, database, frontend, CI/CD, lệnh deploy, tool phụ**
|
|
39
|
-
(Enter để giữ gợi ý). agent-kb **tự đọc `package.json`/`go.mod`/… để gợi ý stack & database sẵn
|
|
39
|
+
(Enter để giữ gợi ý). agent-kb **tự đọc `package.json`/`go.mod`/… để gợi ý stack & database sẵn**
|
|
40
|
+
— quét cả **thư mục con tới 3 cấp** nên hỗ trợ **monorepo** (vd `apps/web`, `services/api`). Nếu
|
|
41
|
+
không thấy manifest nào, nó **báo rõ** và để bạn nhập tay (hoặc dùng cờ `--backend/--frontend/--database`).
|
|
40
42
|
Các phần còn lại (cache, state, UI lib, container, URL môi trường, rollback…) là placeholder
|
|
41
43
|
`<!-- vd: ... -->` trong file để bạn điền tay sau.
|
|
42
44
|
|
|
@@ -86,20 +88,39 @@ tiêu đề đã điền tên feature — khỏi copy thư mục `feature-a/` (v
|
|
|
86
88
|
- `.ai/core/*` là Tier 1 (luôn load, giữ nhỏ). Mọi thứ khác load theo task qua router trong `AGENTS.md`.
|
|
87
89
|
- Không nhân bản rule sang nhiều file → tránh lệch và tốn token.
|
|
88
90
|
|
|
89
|
-
##
|
|
90
|
-
1. `name` trong `package.json` đã đặt là `@dtd-dev/agent-kb` (đổi scope nếu cần).
|
|
91
|
-
2. `npm login`
|
|
92
|
-
3. `npm publish --access public`
|
|
93
|
-
Sau đó cả team dùng: `npx @dtd-dev/agent-kb`.
|
|
91
|
+
## Bản đồ file: cái nào core, cái nào cá nhân hoá, cái nào nhờ AI điền
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
Sau khi `init`, mỗi file rơi vào **một** trong 3 nhóm. Cột "Ai điền" cho biết bạn phải tự làm hay nhờ AI/CLI:
|
|
94
|
+
|
|
95
|
+
| File / Thư mục | Nhóm | Ai điền | Ghi chú |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| `AGENTS.md` (router + quy tắc), `CLAUDE.md`/`GEMINI.md`/`copilot-instructions.md` | 🟦 **Core** | CLI | Khung dùng lại mọi dự án. Phần trong block `BEGIN/END agent-kb` do CLI quản — đừng sửa tay (chạy lại `init` sẽ ghi đè block). |
|
|
98
|
+
| `.ai/README.md` (index Tier 0) | 🟦 **Core** | — | Bản đồ "task → file". Giữ nguyên. |
|
|
99
|
+
| `.ai/workflows/*` (create-feature, fix-bug, code-review, release, learn, incident-response) | 🟦 **Core** | — | Phương pháp luận, dùng lại như nhau giữa các dự án. |
|
|
100
|
+
| `.ai/agents/*` (reviewer, tester, bug-fixer, feature-builder) | 🟦 **Core** | — | Nguồn chân lý của sub-agent; CLI tự emit sang `.claude/agents/`. |
|
|
101
|
+
| `.ai/core/tech-stack.md` | 🟩 **AI điền** | CLI + AI | CLI điền `{{...}}` từ nhận diện stack; AI bổ sung các chỗ `<!-- vd: ... -->` (cache, state, UI lib…) sau khi đọc code. |
|
|
102
|
+
| `.ai/core/coding-standards.md`, `.ai/core/glossary.md` | 🟩 **AI điền** | AI | Nhờ AI đọc codebase rồi viết. |
|
|
103
|
+
| `.ai/architecture.md` | 🟩 **AI điền** | AI | AI suy ra từ cấu trúc dự án. |
|
|
104
|
+
| `.ai/examples/*` | 🟩 **AI điền** | AI | Sinh từ code thật của bạn (thay ví dụ mẫu). |
|
|
105
|
+
| `.ai/skills/devops/*` (deployment, docker, github-actions), `.ai/skills/frontend/react.md`, `ui-guideline.md` | 🟩 **AI điền** | AI | Skill generic — AI viết theo stack thực tế. `deployment.md` có `{{DEPLOY_CMD}}` do CLI điền. |
|
|
106
|
+
| `.ai/memory/*` (common-bugs, lessons-learned, troubleshooting) | 🟩 **AI điền** | AI (dần dần) | Tích luỹ qua workflow `learn.md` mỗi khi rút kinh nghiệm. |
|
|
107
|
+
| `.ai/product/*` (vision, business-rules, domain-model) | 🟥 **Cá nhân hoá** | Người (+AI hỗ trợ) | **Nội dung shipped là MẪU CarePay** — bắt buộc thay bằng nghiệp vụ công ty bạn. Giữ cách đánh số (BR-001…). |
|
|
108
|
+
| `.ai/skills/backend/carepay-*`, `.ai/skills/frontend/carepay-firebase.md` | 🟥 **Cá nhân hoá** | Người | Skill nghiệp vụ mẫu — xoá hoặc thay bằng skill dự án bạn. |
|
|
109
|
+
| `.ai/decisions/ADR-00x-*` | 🟥 **Cá nhân hoá** | Người | Mẫu — thay bằng quyết định thật; tạo mới: `agent-kb adr <tiêu đề>`. |
|
|
110
|
+
| `.ai/specs/feature-a/*` | 🟥 **Cá nhân hoá** | Người | Spec mẫu — tạo spec thật: `agent-kb feature <tên>`. |
|
|
111
|
+
|
|
112
|
+
> 🟦 **Core** = giữ nguyên, dùng lại mọi dự án. 🟩 **AI điền** = nhờ AI đọc code rồi viết. 🟥 **Cá nhân hoá** = nội dung nghiệp vụ riêng, bạn (hoặc AI có ngữ cảnh công ty) phải thay.
|
|
113
|
+
|
|
114
|
+
### AI đọc file nào mỗi lần chạy?
|
|
115
|
+
- **Luôn (mọi session)**: `CLAUDE.md` → import `AGENTS.md` (router + Tier 1). Tier 1 = `.ai/core/tech-stack.md` + `coding-standards.md` + `glossary.md`. Giữ 3 file này thật ngắn (< ~500 token/file) vì đây là phần tốn token cố định.
|
|
116
|
+
- **Theo task (Tier 2, on-demand)**: AI tra bảng router trong `AGENTS.md` / `.ai/README.md` rồi chỉ mở file khớp task — vd fix bug → `workflows/fix-bug.md` + `memory/common-bugs.md`; hiểu nghiệp vụ → `product/business-rules.md` + `product/domain-model.md`. **Không** nuốt cả `.ai/`.
|
|
100
117
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
Gợi ý onboard dự án mới: điền 🟩 (nhờ AI) trước → thay 🟥 (nghiệp vụ) → chạy `agent-kb doctor` để chốt.
|
|
119
|
+
|
|
120
|
+
**Prompt 1-câu để Agent tự điền các file 🟩** (dán vào Claude Code/Cursor… ở thư mục gốc dự án):
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Đọc codebase của dự án này rồi điền chính xác CHỈ các file nhóm 🟩 trong .ai/ (core/tech-stack.md ở các chỗ <!-- vd: ... -->, core/coding-standards.md, core/glossary.md, architecture.md, examples/*, skills/devops/* và skills/frontend/react.md + ui-guideline.md) bằng dữ kiện có thật trong code — giữ mỗi file core/* dưới ~500 token, KHÔNG đụng file 🟥 nghiệp vụ (product/*, skills/backend/carepay-*, carepay-firebase, decisions/, specs/), KHÔNG sửa trong block BEGIN/END agent-kb — xong chạy `agent-kb doctor` và sửa tới khi sạch cảnh báo.
|
|
124
|
+
```
|
|
104
125
|
|
|
105
126
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -167,38 +167,86 @@ function substituteTokens(values) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
// Thư mục rác/sinh ra — không quét để khỏi nhận diện nhầm & cho nhanh.
|
|
171
|
+
const IGNORE_DIRS = new Set([
|
|
172
|
+
"node_modules", ".git", ".ai", ".claude", "dist", "build", "out",
|
|
173
|
+
".next", ".nuxt", "coverage", "vendor", "target", ".gradle", ".venv", "venv", "__pycache__",
|
|
174
|
+
]);
|
|
175
|
+
const MANIFEST_NAMES = [
|
|
176
|
+
"package.json", "go.mod", "requirements.txt", "pyproject.toml", "Pipfile",
|
|
177
|
+
"pom.xml", "build.gradle", "build.gradle.kts", "Cargo.toml", "composer.json",
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
// Tìm các thư mục có manifest, quét tối đa maxDepth cấp từ root (bỏ thư mục rác/dotdir).
|
|
181
|
+
function findProjectRoots(root, maxDepth) {
|
|
182
|
+
const roots = [];
|
|
183
|
+
const walk = (dir, depth) => {
|
|
184
|
+
let entries;
|
|
185
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
186
|
+
if (entries.some((e) => e.isFile() && MANIFEST_NAMES.includes(e.name))) roots.push(dir);
|
|
187
|
+
if (depth >= maxDepth) return;
|
|
188
|
+
for (const e of entries) {
|
|
189
|
+
if (e.isDirectory() && !e.name.startsWith(".") && !IGNORE_DIRS.has(e.name)) {
|
|
190
|
+
walk(path.join(dir, e.name), depth + 1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
175
193
|
};
|
|
194
|
+
walk(root, 0);
|
|
195
|
+
return roots;
|
|
196
|
+
}
|
|
176
197
|
|
|
177
|
-
|
|
198
|
+
// Nhận diện stack trong MỘT thư mục, gộp vào `out` (chỉ điền chỗ còn trống → root thắng subfolder).
|
|
199
|
+
function detectInDir(dir, out, addStack) {
|
|
200
|
+
const exists = (f) => fs.existsSync(path.join(dir, f));
|
|
201
|
+
const pkg = readJSONSafe(path.join(dir, "package.json"));
|
|
178
202
|
if (pkg) {
|
|
179
203
|
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
180
204
|
const has = (n) => Object.prototype.hasOwnProperty.call(deps, n);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
else if (has("
|
|
184
|
-
else if (has("
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
else if (has("
|
|
189
|
-
else if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
let fe = null, be = null;
|
|
206
|
+
if (has("next")) { fe = "Next.js + React"; addStack("react"); }
|
|
207
|
+
else if (has("react")) { fe = "React" + (has("vite") ? " + Vite" : ""); addStack("react"); }
|
|
208
|
+
else if (has("vue")) fe = "Vue";
|
|
209
|
+
else if (has("svelte") || has("@sveltejs/kit")) fe = "Svelte";
|
|
210
|
+
|
|
211
|
+
if (has("@nestjs/core")) { be = "Node.js + NestJS"; addStack("node"); }
|
|
212
|
+
else if (has("express")) { be = "Node.js + Express"; addStack("node"); }
|
|
213
|
+
else if (has("fastify")) { be = "Node.js + Fastify"; addStack("node"); }
|
|
214
|
+
else if (!fe) { out._node = true; addStack("node"); } // Node "trơn": ưu tiên thấp nhất, quyết ở cuối
|
|
215
|
+
|
|
216
|
+
if (fe && !out.frontend) out.frontend = fe;
|
|
217
|
+
if (be && !out.backend) out.backend = be;
|
|
218
|
+
|
|
219
|
+
if (!out.database) {
|
|
220
|
+
if (has("pg") || has("postgres") || has("postgresql")) out.database = "PostgreSQL";
|
|
221
|
+
else if (has("mysql") || has("mysql2")) out.database = "MySQL";
|
|
222
|
+
else if (has("mongoose") || has("mongodb")) out.database = "MongoDB";
|
|
223
|
+
else if (has("firebase") || has("firebase-admin")) out.database = "Firebase/Firestore";
|
|
224
|
+
}
|
|
225
|
+
if ((has("redis") || has("ioredis")) && !(out.database || "").includes("Redis")) {
|
|
226
|
+
out.database = out.database ? out.database + " + Redis" : "Redis";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (exists("go.mod")) { out.backend = out.backend || "Go"; addStack("go"); }
|
|
230
|
+
if (exists("requirements.txt") || exists("pyproject.toml") || exists("Pipfile")) { out.backend = out.backend || "Python"; addStack("python"); }
|
|
231
|
+
if (exists("pom.xml") || exists("build.gradle") || exists("build.gradle.kts")) { out.backend = out.backend || "Java"; addStack("java"); }
|
|
232
|
+
if (exists("Cargo.toml")) { out.backend = out.backend || "Rust"; addStack("rust"); }
|
|
233
|
+
if (exists("composer.json")) { out.backend = out.backend || "PHP"; addStack("php"); }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Đọc dự án để gợi ý stack — chỉ đọc file, không đổi gì. Quét cả thư mục con (monorepo).
|
|
237
|
+
function detectStack() {
|
|
238
|
+
const out = { backend: null, frontend: null, database: null, cicd: null, stacks: [], roots: [], _node: false };
|
|
239
|
+
const addStack = (s) => {
|
|
240
|
+
if (!out.stacks.includes(s)) out.stacks.push(s);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Root (CWD) trước, rồi tới subfolder nông→sâu, để stack của thư mục gốc được ưu tiên.
|
|
244
|
+
const roots = findProjectRoots(CWD, 3).sort((a, b) => a.length - b.length);
|
|
245
|
+
for (const dir of roots) {
|
|
246
|
+
detectInDir(dir, out, addStack);
|
|
247
|
+
out.roots.push(path.relative(CWD, dir) || ".");
|
|
248
|
+
}
|
|
249
|
+
if (!out.backend && out._node) out.backend = "Node.js"; // fallback Node sau khi đã xét hết
|
|
202
250
|
|
|
203
251
|
if (fs.existsSync(path.join(CWD, ".github", "workflows"))) out.cicd = "GitHub Actions";
|
|
204
252
|
else if (hasFile(".gitlab-ci.yml")) out.cicd = "GitLab CI";
|
|
@@ -223,6 +271,8 @@ function availableStacks() {
|
|
|
223
271
|
}
|
|
224
272
|
|
|
225
273
|
// Emit sub-agent: copy .ai/agents/*.md (nguồn chân lý) -> .claude/agents/ cho Claude Code đọc.
|
|
274
|
+
// .claude/agents/ là bản emit PHÁI SINH — luôn ghi đè theo nguồn (không tôn trọng --force/skip),
|
|
275
|
+
// nếu không sửa .ai/agents/ rồi chạy lại init sẽ không đồng bộ.
|
|
226
276
|
function emitAgents(stats) {
|
|
227
277
|
const src = path.join(CWD, ".ai", "agents");
|
|
228
278
|
if (!fs.existsSync(src)) return 0;
|
|
@@ -230,7 +280,17 @@ function emitAgents(stats) {
|
|
|
230
280
|
let n = 0;
|
|
231
281
|
for (const f of fs.readdirSync(src)) {
|
|
232
282
|
if (!f.endsWith(".md")) continue;
|
|
233
|
-
|
|
283
|
+
const to = path.join(dest, f);
|
|
284
|
+
const rel = path.relative(CWD, to);
|
|
285
|
+
const content = fs.readFileSync(path.join(src, f), "utf8");
|
|
286
|
+
const existed = fs.existsSync(to);
|
|
287
|
+
if (existed && fs.readFileSync(to, "utf8") === content) {
|
|
288
|
+
n++;
|
|
289
|
+
continue; // đã trùng nguồn → không ghi, không báo
|
|
290
|
+
}
|
|
291
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
292
|
+
fs.writeFileSync(to, content);
|
|
293
|
+
(existed ? stats.synced : stats.written).push(rel);
|
|
234
294
|
n++;
|
|
235
295
|
}
|
|
236
296
|
return n;
|
|
@@ -259,6 +319,14 @@ async function init() {
|
|
|
259
319
|
|
|
260
320
|
if (det.backend || det.frontend || det.cicd) {
|
|
261
321
|
console.log(`\nℹ Nhận diện stack: ${[det.backend, det.frontend, det.database, det.cicd].filter(Boolean).join(" · ") || "—"}`);
|
|
322
|
+
if (det.roots.length > 1 || (det.roots.length === 1 && det.roots[0] !== ".")) {
|
|
323
|
+
console.log(` (manifest tại: ${det.roots.join(", ")})`);
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
console.log("\n⚠ Không tự nhận diện được stack — không thấy manifest");
|
|
327
|
+
console.log(" (package.json / go.mod / requirements.txt / pyproject.toml / pom.xml / Cargo.toml / composer.json)");
|
|
328
|
+
console.log(" trong thư mục này hay các thư mục con (quét tối đa 3 cấp).");
|
|
329
|
+
console.log(" → Chạy ở đúng thư mục gốc dự án, hoặc nhập tay / dùng cờ --backend --frontend --database.");
|
|
262
330
|
}
|
|
263
331
|
|
|
264
332
|
if (!YES) {
|
|
@@ -279,7 +347,7 @@ async function init() {
|
|
|
279
347
|
rl.close();
|
|
280
348
|
}
|
|
281
349
|
|
|
282
|
-
const stats = { written: [], skipped: [], merged: [] };
|
|
350
|
+
const stats = { written: [], skipped: [], merged: [], synced: [] };
|
|
283
351
|
for (const [from, to] of MAP) {
|
|
284
352
|
copyRecursive(path.join(TEMPLATE_DIR, from), path.join(CWD, to), stats);
|
|
285
353
|
}
|
|
@@ -309,7 +377,10 @@ async function init() {
|
|
|
309
377
|
}
|
|
310
378
|
if (tools.length) console.log(`🔌 Tool đã bật: ${tools.join(", ")}`);
|
|
311
379
|
if (stacks.length) console.log(`📦 Gói stack: ${stacks.join(", ")} → .ai/skills/stacks/`);
|
|
312
|
-
if (agentsCount)
|
|
380
|
+
if (agentsCount) {
|
|
381
|
+
const note = stats.synced.length ? ` (${stats.synced.length} đồng bộ lại từ nguồn)` : "";
|
|
382
|
+
console.log(`🤖 Sub-agents: ${agentsCount} → .claude/agents/ (nguồn: .ai/agents/)${note}`);
|
|
383
|
+
}
|
|
313
384
|
if (stats.skipped.length) {
|
|
314
385
|
console.log(`↷ Bỏ qua ${stats.skipped.length} file đã tồn tại (dùng --force để ghi đè).`);
|
|
315
386
|
}
|
|
@@ -496,13 +567,19 @@ function doctor() {
|
|
|
496
567
|
if (fs.existsSync(path.join(CWD, dest))) oks.push(`${dest} (${t})`);
|
|
497
568
|
}
|
|
498
569
|
|
|
499
|
-
// Sub-agents: mỗi .ai/agents/*.md cần có bản emit ở .claude/agents/
|
|
570
|
+
// Sub-agents: mỗi .ai/agents/*.md cần có bản emit ở .claude/agents/ KHỚP nội dung nguồn.
|
|
500
571
|
const aiAgents = path.join(CWD, ".ai", "agents");
|
|
501
572
|
if (fs.existsSync(aiAgents)) {
|
|
502
573
|
for (const f of fs.readdirSync(aiAgents)) {
|
|
503
574
|
if (!f.endsWith(".md")) continue;
|
|
504
|
-
|
|
505
|
-
|
|
575
|
+
const emit = path.join(CWD, ".claude", "agents", f);
|
|
576
|
+
if (!fs.existsSync(emit)) {
|
|
577
|
+
warnings.push(`Agent .ai/agents/${f} chưa emit sang .claude/agents/ (chạy lại agent-kb init)`);
|
|
578
|
+
} else if (fs.readFileSync(path.join(aiAgents, f), "utf8") !== fs.readFileSync(emit, "utf8")) {
|
|
579
|
+
warnings.push(`.claude/agents/${f} lệch với nguồn .ai/agents/${f} (chạy lại agent-kb init để đồng bộ)`);
|
|
580
|
+
} else {
|
|
581
|
+
oks.push(`.claude/agents/${f}`);
|
|
582
|
+
}
|
|
506
583
|
}
|
|
507
584
|
}
|
|
508
585
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dtd-dev/agent-kb",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Bộ agent skill chuẩn hoá cho doanh nghiệp: scaffold knowledge base .ai/ tiết kiệm token, tăng độ chính xác khi viết code, và cá nhân hoá theo nghiệp vụ công ty để tái dùng cho nhiều dự án cùng nghiệp vụ (AGENTS.md, CLAUDE.md, GEMINI.md, Copilot).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agent-kb": "bin/cli.js"
|