@dtd-dev/agent-kb 0.1.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 +105 -0
- package/bin/cli.js +653 -0
- package/package.json +33 -0
- package/template/AGENTS.md +34 -0
- package/template/CLAUDE.md +4 -0
- package/template/GEMINI.md +6 -0
- package/template/_adr-skeleton.md +8 -0
- package/template/_agent-skeleton.md +21 -0
- package/template/_feature-skeleton/api.md +8 -0
- package/template/_feature-skeleton/design.md +6 -0
- package/template/_feature-skeleton/requirements.md +9 -0
- package/template/_feature-skeleton/test-cases.md +7 -0
- package/template/_skill-skeleton.md +16 -0
- package/template/_stacks/go.md +19 -0
- package/template/_stacks/java.md +19 -0
- package/template/_stacks/node.md +19 -0
- package/template/_stacks/php.md +18 -0
- package/template/_stacks/python.md +19 -0
- package/template/_stacks/react.md +20 -0
- package/template/_stacks/rust.md +18 -0
- package/template/ai/README.md +28 -0
- package/template/ai/agents/bug-fixer.md +23 -0
- package/template/ai/agents/feature-builder.md +18 -0
- package/template/ai/agents/reviewer.md +20 -0
- package/template/ai/agents/tester.md +21 -0
- package/template/ai/architecture.md +40 -0
- package/template/ai/core/coding-standards.md +28 -0
- package/template/ai/core/glossary.md +20 -0
- package/template/ai/core/tech-stack.md +19 -0
- package/template/ai/decisions/ADR-001-auth.md +9 -0
- package/template/ai/decisions/ADR-002-cache.md +9 -0
- package/template/ai/decisions/ADR-003-logging.md +9 -0
- package/template/ai/examples/api-example.md +31 -0
- package/template/ai/examples/coding-example.md +28 -0
- package/template/ai/examples/testing-example.md +32 -0
- package/template/ai/memory/common-bugs.md +11 -0
- package/template/ai/memory/lessons-learned.md +6 -0
- package/template/ai/memory/troubleshooting.md +19 -0
- package/template/ai/product/business-rules.md +19 -0
- package/template/ai/product/domain-model.md +22 -0
- package/template/ai/product/vision.md +8 -0
- package/template/ai/skills/backend/carepay-oncall-gen2.md +24 -0
- package/template/ai/skills/backend/carepay-request-gen2.md +23 -0
- package/template/ai/skills/backend/carepay-trigger-gen2.md +24 -0
- package/template/ai/skills/devops/deployment.md +29 -0
- package/template/ai/skills/devops/docker.md +24 -0
- package/template/ai/skills/devops/github-actions.md +26 -0
- package/template/ai/skills/frontend/carepay-firebase.md +25 -0
- package/template/ai/skills/frontend/react.md +24 -0
- package/template/ai/skills/frontend/ui-guideline.md +29 -0
- package/template/ai/specs/feature-a/api.md +26 -0
- package/template/ai/specs/feature-a/design.md +12 -0
- package/template/ai/specs/feature-a/requirements.md +13 -0
- package/template/ai/specs/feature-a/test-cases.md +12 -0
- package/template/ai/workflows/code-review.md +25 -0
- package/template/ai/workflows/create-feature.md +37 -0
- package/template/ai/workflows/fix-bug.md +16 -0
- package/template/ai/workflows/incident-response.md +19 -0
- package/template/ai/workflows/learn.md +24 -0
- package/template/ai/workflows/release.md +23 -0
- package/template/github/copilot-instructions.md +5 -0
- package/template/tools/pointer.md +5 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* agent-kb — scaffold a token-efficient .ai/ knowledge base + cross-tool agent config.
|
|
6
|
+
* Zero dependencies (uses only Node built-ins) so `npx` runs instantly.
|
|
7
|
+
*
|
|
8
|
+
* Lệnh chính:
|
|
9
|
+
* agent-kb [init] khởi tạo KB + agent config (tương tác / -y)
|
|
10
|
+
* agent-kb feature <tên> tạo .ai/specs/<tên>/ từ khung trống
|
|
11
|
+
* agent-kb adr <tiêu đề> tạo ADR mới (tự đánh số)
|
|
12
|
+
* agent-kb skill <a>/<tên> tạo skill mới từ khung
|
|
13
|
+
* agent-kb doctor kiểm tra & audit KB (read-only)
|
|
14
|
+
* agent-kb import gom config rule cũ vào AGENTS.md
|
|
15
|
+
* agent-kb mcp add <name> thêm MCP server vào .mcp.json
|
|
16
|
+
* agent-kb hook install cài git pre-commit hook chạy doctor
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require("fs");
|
|
20
|
+
const path = require("path");
|
|
21
|
+
const readline = require("readline");
|
|
22
|
+
|
|
23
|
+
const TEMPLATE_DIR = path.join(__dirname, "..", "template");
|
|
24
|
+
const CWD = process.cwd();
|
|
25
|
+
|
|
26
|
+
// .ai/ copy nguyên cây (skip file đã có); các file agent-config xử lý riêng qua mergeOrCreate.
|
|
27
|
+
const MAP = [["ai", ".ai"]];
|
|
28
|
+
|
|
29
|
+
// File agent-config lõi: nếu đã tồn tại thì CHÈN block agent-kb (không bỏ qua, không đụng phần ngoài block).
|
|
30
|
+
const AGENT_FILES = [
|
|
31
|
+
["AGENTS.md", "AGENTS.md"],
|
|
32
|
+
["CLAUDE.md", "CLAUDE.md"],
|
|
33
|
+
["GEMINI.md", "GEMINI.md"],
|
|
34
|
+
[path.join("github", "copilot-instructions.md"), path.join(".github", "copilot-instructions.md")],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Tool tuỳ chọn: file trỏ về AGENTS.md (dùng chung 1 nội dung pointer).
|
|
38
|
+
const OPTIONAL_TOOLS = {
|
|
39
|
+
cursor: ".cursorrules",
|
|
40
|
+
windsurf: ".windsurfrules",
|
|
41
|
+
cline: ".clinerules",
|
|
42
|
+
zed: ".rules",
|
|
43
|
+
aider: "CONVENTIONS.md",
|
|
44
|
+
};
|
|
45
|
+
const POINTER_SRC = path.join(TEMPLATE_DIR, "tools", "pointer.md");
|
|
46
|
+
|
|
47
|
+
const BEGIN = "<!-- BEGIN agent-kb -->";
|
|
48
|
+
const END = "<!-- END agent-kb -->";
|
|
49
|
+
|
|
50
|
+
const args = process.argv.slice(2);
|
|
51
|
+
const cmd = args[0] && !args[0].startsWith("-") ? args[0] : "init";
|
|
52
|
+
const FORCE = args.includes("--force") || args.includes("-f");
|
|
53
|
+
const HOOK = args.includes("--hook");
|
|
54
|
+
|
|
55
|
+
function flag(name) {
|
|
56
|
+
const i = args.indexOf(name);
|
|
57
|
+
return i !== -1 && args[i + 1] ? args[i + 1] : null;
|
|
58
|
+
}
|
|
59
|
+
const FLAG_NAME = flag("--name");
|
|
60
|
+
const FLAG_BACKEND = flag("--backend");
|
|
61
|
+
const FLAG_FRONTEND = flag("--frontend");
|
|
62
|
+
const FLAG_DATABASE = flag("--database");
|
|
63
|
+
const FLAG_CICD = flag("--cicd");
|
|
64
|
+
const FLAG_DEPLOY = flag("--deploy");
|
|
65
|
+
const FLAG_TOOLS = flag("--tools");
|
|
66
|
+
const FLAG_STACK = flag("--stack");
|
|
67
|
+
const FLAG_DESC = flag("--desc");
|
|
68
|
+
const FLAG_MODEL = flag("--model");
|
|
69
|
+
|
|
70
|
+
// -y, hoặc không phải terminal (pipe/CI): dùng mặc định thay vì treo chờ nhập.
|
|
71
|
+
const YES = args.includes("--yes") || args.includes("-y") || !process.stdin.isTTY;
|
|
72
|
+
|
|
73
|
+
// ---------- helpers ----------
|
|
74
|
+
function ask(rl, q, def) {
|
|
75
|
+
return new Promise((res) => {
|
|
76
|
+
rl.question(`${q}${def ? ` (${def})` : ""}: `, (a) => res(a.trim() || def || ""));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readJSONSafe(p) {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasFile(rel) {
|
|
89
|
+
return fs.existsSync(path.join(CWD, rel));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function slugify(s) {
|
|
93
|
+
return (
|
|
94
|
+
String(s)
|
|
95
|
+
.trim()
|
|
96
|
+
.replace(/\s+/g, "-")
|
|
97
|
+
.replace(/[\/\\]/g, "-")
|
|
98
|
+
.replace(/^\.+/, "")
|
|
99
|
+
.replace(/-+/g, "-")
|
|
100
|
+
.replace(/^-|-$/g, "")
|
|
101
|
+
.toLowerCase() || "untitled"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function copyRecursive(src, dest, stats) {
|
|
106
|
+
const s = fs.statSync(src);
|
|
107
|
+
if (s.isDirectory()) {
|
|
108
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
109
|
+
for (const entry of fs.readdirSync(src)) {
|
|
110
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry), stats);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
if (fs.existsSync(dest) && !FORCE) {
|
|
114
|
+
stats.skipped.push(path.relative(CWD, dest));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
118
|
+
fs.copyFileSync(src, dest);
|
|
119
|
+
stats.written.push(path.relative(CWD, dest));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Chèn nội dung template vào file đích dưới dạng block có mốc (idempotent).
|
|
124
|
+
// - chưa có file -> tạo file = block
|
|
125
|
+
// - có file, chưa block -> append block vào cuối (giữ nguyên nội dung cũ)
|
|
126
|
+
// - có file, có block -> thay đúng vùng BEGIN..END
|
|
127
|
+
// - --force -> ghi đè cả file bằng block
|
|
128
|
+
function mergeOrCreate(src, dest, stats) {
|
|
129
|
+
const inner = fs.readFileSync(src, "utf8").trim();
|
|
130
|
+
const block = `${BEGIN}\n${inner}\n${END}\n`;
|
|
131
|
+
const rel = path.relative(CWD, dest);
|
|
132
|
+
|
|
133
|
+
if (!fs.existsSync(dest) || FORCE) {
|
|
134
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
135
|
+
fs.writeFileSync(dest, block);
|
|
136
|
+
stats.written.push(rel);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const current = fs.readFileSync(dest, "utf8");
|
|
141
|
+
const re = new RegExp(`${BEGIN}[\\s\\S]*?${END}\\n?`);
|
|
142
|
+
if (re.test(current)) {
|
|
143
|
+
fs.writeFileSync(dest, current.replace(re, block)); // cập nhật block cũ
|
|
144
|
+
} else {
|
|
145
|
+
fs.writeFileSync(dest, current.replace(/\s*$/, "\n\n") + block); // chèn vào cuối
|
|
146
|
+
}
|
|
147
|
+
stats.merged.push(rel);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function substituteTokens(values) {
|
|
151
|
+
const targets = [
|
|
152
|
+
path.join(CWD, ".ai", "core", "tech-stack.md"),
|
|
153
|
+
path.join(CWD, ".ai", "skills", "devops", "deployment.md"),
|
|
154
|
+
path.join(CWD, "AGENTS.md"),
|
|
155
|
+
];
|
|
156
|
+
for (const file of targets) {
|
|
157
|
+
if (!fs.existsSync(file)) continue;
|
|
158
|
+
let txt = fs.readFileSync(file, "utf8");
|
|
159
|
+
txt = txt
|
|
160
|
+
.replace(/\{\{PROJECT_NAME\}\}/g, values.name)
|
|
161
|
+
.replace(/\{\{BACKEND\}\}/g, values.backend)
|
|
162
|
+
.replace(/\{\{FRONTEND\}\}/g, values.frontend)
|
|
163
|
+
.replace(/\{\{DATABASE\}\}/g, values.database)
|
|
164
|
+
.replace(/\{\{CICD\}\}/g, values.cicd)
|
|
165
|
+
.replace(/\{\{DEPLOY_CMD\}\}/g, values.deploy);
|
|
166
|
+
fs.writeFileSync(file, txt);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Đọc dự án để gợi ý stack — chỉ đọc file, không đổi gì.
|
|
171
|
+
function detectStack() {
|
|
172
|
+
const out = { backend: null, frontend: null, database: null, cicd: null, stacks: [] };
|
|
173
|
+
const addStack = (s) => {
|
|
174
|
+
if (!out.stacks.includes(s)) out.stacks.push(s);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const pkg = readJSONSafe(path.join(CWD, "package.json"));
|
|
178
|
+
if (pkg) {
|
|
179
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
180
|
+
const has = (n) => Object.prototype.hasOwnProperty.call(deps, n);
|
|
181
|
+
if (has("next")) { out.frontend = "Next.js + React"; addStack("react"); }
|
|
182
|
+
else if (has("react")) { out.frontend = "React" + (has("vite") ? " + Vite" : ""); addStack("react"); }
|
|
183
|
+
else if (has("vue")) out.frontend = "Vue";
|
|
184
|
+
else if (has("svelte") || has("@sveltejs/kit")) out.frontend = "Svelte";
|
|
185
|
+
|
|
186
|
+
if (has("@nestjs/core")) { out.backend = "Node.js + NestJS"; addStack("node"); }
|
|
187
|
+
else if (has("express")) { out.backend = "Node.js + Express"; addStack("node"); }
|
|
188
|
+
else if (has("fastify")) { out.backend = "Node.js + Fastify"; addStack("node"); }
|
|
189
|
+
else if (!out.frontend) { out.backend = "Node.js"; addStack("node"); }
|
|
190
|
+
|
|
191
|
+
if (has("pg") || has("postgres") || has("postgresql")) out.database = "PostgreSQL";
|
|
192
|
+
else if (has("mysql") || has("mysql2")) out.database = "MySQL";
|
|
193
|
+
else if (has("mongoose") || has("mongodb")) out.database = "MongoDB";
|
|
194
|
+
else if (has("firebase") || has("firebase-admin")) out.database = "Firebase/Firestore";
|
|
195
|
+
if (has("redis") || has("ioredis")) out.database = out.database ? out.database + " + Redis" : "Redis";
|
|
196
|
+
}
|
|
197
|
+
if (hasFile("go.mod")) { out.backend = out.backend || "Go"; addStack("go"); }
|
|
198
|
+
if (hasFile("requirements.txt") || hasFile("pyproject.toml") || hasFile("Pipfile")) { out.backend = out.backend || "Python"; addStack("python"); }
|
|
199
|
+
if (hasFile("pom.xml") || hasFile("build.gradle") || hasFile("build.gradle.kts")) { out.backend = out.backend || "Java"; addStack("java"); }
|
|
200
|
+
if (hasFile("Cargo.toml")) { out.backend = out.backend || "Rust"; addStack("rust"); }
|
|
201
|
+
if (hasFile("composer.json")) { out.backend = out.backend || "PHP"; addStack("php"); }
|
|
202
|
+
|
|
203
|
+
if (fs.existsSync(path.join(CWD, ".github", "workflows"))) out.cicd = "GitHub Actions";
|
|
204
|
+
else if (hasFile(".gitlab-ci.yml")) out.cicd = "GitLab CI";
|
|
205
|
+
else if (hasFile("Jenkinsfile")) out.cicd = "Jenkins";
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function selectedTools(spec) {
|
|
210
|
+
if (!spec) return [];
|
|
211
|
+
const all = Object.keys(OPTIONAL_TOOLS);
|
|
212
|
+
if (spec.trim().toLowerCase() === "all") return all;
|
|
213
|
+
return spec
|
|
214
|
+
.split(",")
|
|
215
|
+
.map((s) => s.trim().toLowerCase())
|
|
216
|
+
.filter((t) => all.includes(t));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function availableStacks() {
|
|
220
|
+
const dir = path.join(TEMPLATE_DIR, "_stacks");
|
|
221
|
+
if (!fs.existsSync(dir)) return [];
|
|
222
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Emit sub-agent: copy .ai/agents/*.md (nguồn chân lý) -> .claude/agents/ cho Claude Code đọc.
|
|
226
|
+
function emitAgents(stats) {
|
|
227
|
+
const src = path.join(CWD, ".ai", "agents");
|
|
228
|
+
if (!fs.existsSync(src)) return 0;
|
|
229
|
+
const dest = path.join(CWD, ".claude", "agents");
|
|
230
|
+
let n = 0;
|
|
231
|
+
for (const f of fs.readdirSync(src)) {
|
|
232
|
+
if (!f.endsWith(".md")) continue;
|
|
233
|
+
copyRecursive(path.join(src, f), path.join(dest, f), stats);
|
|
234
|
+
n++;
|
|
235
|
+
}
|
|
236
|
+
return n;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ---------- commands ----------
|
|
240
|
+
async function init() {
|
|
241
|
+
if (!fs.existsSync(TEMPLATE_DIR)) {
|
|
242
|
+
console.error("✗ Không tìm thấy thư mục template trong package.");
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const det = detectStack();
|
|
247
|
+
const pick = (flagV, detV, placeholder) => (flagV ? "Stack: " + flagV : detV ? "Stack: " + detV : placeholder);
|
|
248
|
+
|
|
249
|
+
let values = {
|
|
250
|
+
name: FLAG_NAME || path.basename(CWD),
|
|
251
|
+
backend: pick(FLAG_BACKEND, det.backend, "Ngôn ngữ + framework: <!-- vd: Node.js 20 + NestJS 10 -->"),
|
|
252
|
+
frontend: pick(FLAG_FRONTEND, det.frontend, "Framework: <!-- vd: React 18 + Vite -->"),
|
|
253
|
+
database: FLAG_DATABASE || det.database || "<!-- vd: PostgreSQL 15 -->",
|
|
254
|
+
cicd: FLAG_CICD || det.cicd || "<!-- vd: GitHub Actions -->",
|
|
255
|
+
deploy: FLAG_DEPLOY || "<!-- vd: ./scripts/deploy.sh <env> -->",
|
|
256
|
+
};
|
|
257
|
+
let toolsSpec = FLAG_TOOLS || "";
|
|
258
|
+
let stackSpec = FLAG_STACK || "";
|
|
259
|
+
|
|
260
|
+
if (det.backend || det.frontend || det.cicd) {
|
|
261
|
+
console.log(`\nℹ Nhận diện stack: ${[det.backend, det.frontend, det.database, det.cicd].filter(Boolean).join(" · ") || "—"}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!YES) {
|
|
265
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
266
|
+
console.log("\n⚙ Khởi tạo AI knowledge base (.ai/) — Enter để giữ mặc định/gợi ý\n");
|
|
267
|
+
values.name = await ask(rl, "Tên dự án", values.name);
|
|
268
|
+
const be = await ask(rl, "Backend stack", det.backend || "");
|
|
269
|
+
const db = await ask(rl, "Database", det.database || "");
|
|
270
|
+
const fe = await ask(rl, "Frontend stack", det.frontend || "");
|
|
271
|
+
const cicd = await ask(rl, "CI/CD", det.cicd || "");
|
|
272
|
+
const deploy = await ask(rl, "Lệnh deploy", "");
|
|
273
|
+
if (be) values.backend = "Stack: " + be;
|
|
274
|
+
if (db) values.database = db;
|
|
275
|
+
if (fe) values.frontend = "Stack: " + fe;
|
|
276
|
+
if (cicd) values.cicd = cicd;
|
|
277
|
+
if (deploy) values.deploy = deploy;
|
|
278
|
+
if (!toolsSpec) toolsSpec = await ask(rl, "Thêm tool khác (cursor,windsurf,cline,zed,aider / all / Enter=bỏ)", "");
|
|
279
|
+
rl.close();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const stats = { written: [], skipped: [], merged: [] };
|
|
283
|
+
for (const [from, to] of MAP) {
|
|
284
|
+
copyRecursive(path.join(TEMPLATE_DIR, from), path.join(CWD, to), stats);
|
|
285
|
+
}
|
|
286
|
+
for (const [from, to] of AGENT_FILES) {
|
|
287
|
+
mergeOrCreate(path.join(TEMPLATE_DIR, from), path.join(CWD, to), stats);
|
|
288
|
+
}
|
|
289
|
+
const tools = selectedTools(toolsSpec);
|
|
290
|
+
for (const t of tools) {
|
|
291
|
+
mergeOrCreate(POINTER_SRC, path.join(CWD, OPTIONAL_TOOLS[t]), stats);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Gói chuẩn theo stack: từ --stack hoặc tự nhận diện.
|
|
295
|
+
const stacks = (stackSpec ? stackSpec.split(",").map((s) => s.trim().toLowerCase()) : det.stacks).filter((s) =>
|
|
296
|
+
availableStacks().includes(s)
|
|
297
|
+
);
|
|
298
|
+
for (const s of stacks) {
|
|
299
|
+
copyRecursive(path.join(TEMPLATE_DIR, "_stacks", s + ".md"), path.join(CWD, ".ai", "skills", "stacks", s + ".md"), stats);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const agentsCount = emitAgents(stats);
|
|
303
|
+
substituteTokens(values);
|
|
304
|
+
if (HOOK) installHook(true);
|
|
305
|
+
|
|
306
|
+
console.log(`\n✓ Đã tạo ${stats.written.length} file vào ${CWD}`);
|
|
307
|
+
if (stats.merged.length) {
|
|
308
|
+
console.log(`✎ Chèn/cập nhật ${stats.merged.length} file agent-config (block <!-- BEGIN/END agent-kb -->): ${stats.merged.join(", ")}`);
|
|
309
|
+
}
|
|
310
|
+
if (tools.length) console.log(`🔌 Tool đã bật: ${tools.join(", ")}`);
|
|
311
|
+
if (stacks.length) console.log(`📦 Gói stack: ${stacks.join(", ")} → .ai/skills/stacks/`);
|
|
312
|
+
if (agentsCount) console.log(`🤖 Sub-agents: ${agentsCount} → .claude/agents/ (nguồn: .ai/agents/)`);
|
|
313
|
+
if (stats.skipped.length) {
|
|
314
|
+
console.log(`↷ Bỏ qua ${stats.skipped.length} file đã tồn tại (dùng --force để ghi đè).`);
|
|
315
|
+
}
|
|
316
|
+
console.log("\nBước tiếp:");
|
|
317
|
+
console.log(" 1. Điền .ai/core/* (tech-stack, coding-standards, glossary)");
|
|
318
|
+
console.log(" 2. Tạo spec feature mới: agent-kb feature <tên>");
|
|
319
|
+
console.log(" 3. Kiểm tra bất cứ lúc nào: agent-kb doctor");
|
|
320
|
+
console.log(" 4. AGENTS.md là nguồn chân lý — các file khác đã trỏ về nó.\n");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function feature() {
|
|
324
|
+
const raw = args.slice(1).find((a) => a && !a.startsWith("-"));
|
|
325
|
+
if (!raw) {
|
|
326
|
+
console.error("✗ Thiếu tên feature. Dùng: agent-kb feature <tên>");
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
const skeleton = path.join(TEMPLATE_DIR, "_feature-skeleton");
|
|
330
|
+
if (!fs.existsSync(skeleton)) {
|
|
331
|
+
console.error("✗ Không tìm thấy khung feature (_feature-skeleton) trong package.");
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
const specsDir = path.join(CWD, ".ai", "specs");
|
|
335
|
+
if (!fs.existsSync(specsDir)) {
|
|
336
|
+
console.error("✗ Chưa thấy .ai/specs — chạy `agent-kb init` trước.");
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const name = raw.trim();
|
|
341
|
+
const slug = name.replace(/\s+/g, "-").replace(/[\/\\]/g, "-").replace(/^\.+/, "");
|
|
342
|
+
const dest = path.join(specsDir, slug);
|
|
343
|
+
if (fs.existsSync(dest) && !FORCE) {
|
|
344
|
+
console.error(`✗ .ai/specs/${slug}/ đã tồn tại (dùng --force để ghi đè).`);
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const stats = { written: [], skipped: [], merged: [] };
|
|
349
|
+
copyRecursive(skeleton, dest, stats);
|
|
350
|
+
for (const f of fs.readdirSync(dest)) {
|
|
351
|
+
const file = path.join(dest, f);
|
|
352
|
+
const txt = fs.readFileSync(file, "utf8").replace(/\{\{FEATURE_NAME\}\}/g, name);
|
|
353
|
+
fs.writeFileSync(file, txt);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(`\n✓ Đã tạo .ai/specs/${slug}/ (${stats.written.length} file)`);
|
|
357
|
+
console.log(" Điền requirements.md trước; quy trình: .ai/workflows/create-feature.md\n");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function adr() {
|
|
361
|
+
const raw = args.slice(1).find((a) => a && !a.startsWith("-"));
|
|
362
|
+
if (!raw) {
|
|
363
|
+
console.error("✗ Thiếu tiêu đề. Dùng: agent-kb adr <tiêu đề>");
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
const dir = path.join(CWD, ".ai", "decisions");
|
|
367
|
+
if (!fs.existsSync(dir)) {
|
|
368
|
+
console.error("✗ Chưa thấy .ai/decisions — chạy `agent-kb init` trước.");
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
const skel = path.join(TEMPLATE_DIR, "_adr-skeleton.md");
|
|
372
|
+
let max = 0;
|
|
373
|
+
for (const f of fs.readdirSync(dir)) {
|
|
374
|
+
const m = f.match(/^ADR-(\d+)/i);
|
|
375
|
+
if (m) max = Math.max(max, parseInt(m[1], 10));
|
|
376
|
+
}
|
|
377
|
+
const num = String(max + 1).padStart(3, "0");
|
|
378
|
+
const title = raw.trim();
|
|
379
|
+
const dest = path.join(dir, `ADR-${num}-${slugify(title)}.md`);
|
|
380
|
+
if (fs.existsSync(dest) && !FORCE) {
|
|
381
|
+
console.error(`✗ ${path.relative(CWD, dest)} đã tồn tại (dùng --force).`);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
const txt = fs.readFileSync(skel, "utf8").replace(/\{\{ADR_NUM\}\}/g, num).replace(/\{\{ADR_TITLE\}\}/g, title);
|
|
385
|
+
fs.writeFileSync(dest, txt);
|
|
386
|
+
console.log(`✓ Đã tạo ${path.relative(CWD, dest)}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function skill() {
|
|
390
|
+
const raw = args.slice(1).find((a) => a && !a.startsWith("-"));
|
|
391
|
+
if (!raw) {
|
|
392
|
+
console.error("✗ Thiếu tên. Dùng: agent-kb skill <area>/<tên> (vd: backend/payment)");
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
const skillsDir = path.join(CWD, ".ai", "skills");
|
|
396
|
+
if (!fs.existsSync(skillsDir)) {
|
|
397
|
+
console.error("✗ Chưa thấy .ai/skills — chạy `agent-kb init` trước.");
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
const skel = path.join(TEMPLATE_DIR, "_skill-skeleton.md");
|
|
401
|
+
const parts = raw.trim().split("/").map((s) => s.trim()).filter(Boolean);
|
|
402
|
+
const nameRaw = parts.pop();
|
|
403
|
+
const areaParts = parts.map(slugify);
|
|
404
|
+
const destDir = areaParts.length ? path.join(skillsDir, ...areaParts) : skillsDir;
|
|
405
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
406
|
+
const dest = path.join(destDir, slugify(nameRaw) + ".md");
|
|
407
|
+
if (fs.existsSync(dest) && !FORCE) {
|
|
408
|
+
console.error(`✗ ${path.relative(CWD, dest)} đã tồn tại (dùng --force).`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
const txt = fs.readFileSync(skel, "utf8").replace(/\{\{SKILL_NAME\}\}/g, nameRaw);
|
|
412
|
+
fs.writeFileSync(dest, txt);
|
|
413
|
+
console.log(`✓ Đã tạo ${path.relative(CWD, dest)}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function agentCmd() {
|
|
417
|
+
const raw = args.slice(1).find((a) => a && !a.startsWith("-"));
|
|
418
|
+
if (!raw) {
|
|
419
|
+
console.error("✗ Thiếu tên. Dùng: agent-kb agent <tên> [--desc ..] [--tools ..] [--model ..]");
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
if (!fs.existsSync(path.join(CWD, ".ai"))) {
|
|
423
|
+
console.error("✗ Chưa thấy .ai — chạy `agent-kb init` trước.");
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
const skel = path.join(TEMPLATE_DIR, "_agent-skeleton.md");
|
|
427
|
+
const agentsDir = path.join(CWD, ".ai", "agents");
|
|
428
|
+
const name = slugify(raw);
|
|
429
|
+
const dest = path.join(agentsDir, name + ".md");
|
|
430
|
+
if (fs.existsSync(dest) && !FORCE) {
|
|
431
|
+
console.error(`✗ .ai/agents/${name}.md đã tồn tại (dùng --force).`);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let desc = FLAG_DESC || "";
|
|
436
|
+
let tools = FLAG_TOOLS || "";
|
|
437
|
+
let model = FLAG_MODEL || "";
|
|
438
|
+
if (!YES && !FLAG_DESC) {
|
|
439
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
440
|
+
desc = await ask(rl, "Mô tả (khi nào dùng agent này)", desc);
|
|
441
|
+
tools = await ask(rl, "Tools", tools || "Read, Grep, Glob, Bash");
|
|
442
|
+
model = await ask(rl, "Model (Enter = kế thừa)", model);
|
|
443
|
+
rl.close();
|
|
444
|
+
}
|
|
445
|
+
if (!desc) desc = "<!-- mô tả khi nào dùng agent này -->";
|
|
446
|
+
if (!tools) tools = "Read, Grep, Glob, Bash";
|
|
447
|
+
|
|
448
|
+
let txt = fs
|
|
449
|
+
.readFileSync(skel, "utf8")
|
|
450
|
+
.replace(/\{\{AGENT_NAME\}\}/g, name)
|
|
451
|
+
.replace(/\{\{AGENT_DESC\}\}/g, desc)
|
|
452
|
+
.replace(/\{\{AGENT_TOOLS\}\}/g, tools);
|
|
453
|
+
if (model) txt = txt.replace(/^(tools: .*)$/m, `$1\nmodel: ${model}`);
|
|
454
|
+
|
|
455
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
456
|
+
fs.writeFileSync(dest, txt);
|
|
457
|
+
const emitDir = path.join(CWD, ".claude", "agents");
|
|
458
|
+
fs.mkdirSync(emitDir, { recursive: true });
|
|
459
|
+
fs.copyFileSync(dest, path.join(emitDir, name + ".md"));
|
|
460
|
+
|
|
461
|
+
console.log(`✓ Đã tạo .ai/agents/${name}.md → emit .claude/agents/${name}.md`);
|
|
462
|
+
console.log(" Nguồn chân lý là .ai/agents/; sửa ở đó rồi chạy lại `agent-kb init` để đồng bộ.");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function doctor() {
|
|
466
|
+
if (!fs.existsSync(path.join(CWD, ".ai"))) {
|
|
467
|
+
console.error("✗ Chưa có .ai/ — chạy `agent-kb init` trước.");
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
const oks = [];
|
|
471
|
+
const warnings = [];
|
|
472
|
+
const problems = [];
|
|
473
|
+
|
|
474
|
+
const need = (rel) => {
|
|
475
|
+
if (fs.existsSync(path.join(CWD, rel))) oks.push(rel);
|
|
476
|
+
else problems.push(`Thiếu ${rel}`);
|
|
477
|
+
};
|
|
478
|
+
["AGENTS.md", ".ai/core/tech-stack.md", ".ai/core/coding-standards.md", ".ai/core/glossary.md"].forEach(need);
|
|
479
|
+
|
|
480
|
+
for (const f of ["AGENTS.md", ".ai/core/tech-stack.md", ".ai/skills/devops/deployment.md"]) {
|
|
481
|
+
const p = path.join(CWD, f);
|
|
482
|
+
if (fs.existsSync(p) && /\{\{[A-Z_]+\}\}/.test(fs.readFileSync(p, "utf8"))) {
|
|
483
|
+
warnings.push(`${f} còn token {{...}} chưa thay (chạy lại init --force hoặc điền tay)`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const ag = path.join(CWD, "AGENTS.md");
|
|
488
|
+
if (fs.existsSync(ag)) {
|
|
489
|
+
const n = (fs.readFileSync(ag, "utf8").match(new RegExp(BEGIN, "g")) || []).length;
|
|
490
|
+
if (n === 0) warnings.push("AGENTS.md không có block agent-kb");
|
|
491
|
+
else if (n > 1) problems.push(`AGENTS.md có ${n} block agent-kb (trùng) — nên chỉ còn 1`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Tool đang bật nhưng pointer thiếu (chỉ cảnh báo những tool đã có file)
|
|
495
|
+
for (const [t, dest] of Object.entries(OPTIONAL_TOOLS)) {
|
|
496
|
+
if (fs.existsSync(path.join(CWD, dest))) oks.push(`${dest} (${t})`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Sub-agents: mỗi .ai/agents/*.md cần có bản emit ở .claude/agents/
|
|
500
|
+
const aiAgents = path.join(CWD, ".ai", "agents");
|
|
501
|
+
if (fs.existsSync(aiAgents)) {
|
|
502
|
+
for (const f of fs.readdirSync(aiAgents)) {
|
|
503
|
+
if (!f.endsWith(".md")) continue;
|
|
504
|
+
if (fs.existsSync(path.join(CWD, ".claude", "agents", f))) oks.push(`.claude/agents/${f}`);
|
|
505
|
+
else warnings.push(`Agent .ai/agents/${f} chưa emit sang .claude/agents/ (chạy lại agent-kb init)`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
oks.forEach((o) => console.log(" ✓ " + o));
|
|
510
|
+
warnings.forEach((w) => console.log(" ⚠ " + w));
|
|
511
|
+
problems.forEach((p) => console.error(" ✗ " + p));
|
|
512
|
+
|
|
513
|
+
if (problems.length) {
|
|
514
|
+
console.error(`\n✗ doctor: ${problems.length} lỗi, ${warnings.length} cảnh báo.`);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
console.log(`\n✓ doctor: OK${warnings.length ? ` (${warnings.length} cảnh báo)` : ""}.`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function stripBlocks(txt) {
|
|
521
|
+
return txt.replace(new RegExp(`${BEGIN}[\\s\\S]*?${END}\\n?`, "g"), "").trim();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function importCmd() {
|
|
525
|
+
const ag = path.join(CWD, "AGENTS.md");
|
|
526
|
+
if (!fs.existsSync(ag)) {
|
|
527
|
+
console.error("✗ Chưa có AGENTS.md — chạy `agent-kb init` trước.");
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
const sources = [".cursorrules", ".clinerules", ".windsurfrules", ".rules", "CONVENTIONS.md", "CLAUDE.md", "GEMINI.md", path.join(".github", "copilot-instructions.md")];
|
|
531
|
+
let agText = fs.readFileSync(ag, "utf8");
|
|
532
|
+
const added = [];
|
|
533
|
+
for (const src of sources) {
|
|
534
|
+
const p = path.join(CWD, src);
|
|
535
|
+
if (!fs.existsSync(p) || path.resolve(p) === path.resolve(ag)) continue;
|
|
536
|
+
const content = stripBlocks(fs.readFileSync(p, "utf8"));
|
|
537
|
+
if (!content) continue; // chỉ là block agent-kb → bỏ
|
|
538
|
+
const marker = `<!-- IMPORTED:${src} -->`;
|
|
539
|
+
if (agText.includes(marker)) continue; // đã import trước đó
|
|
540
|
+
agText = agText.replace(/\s*$/, "\n\n") + `## Nội dung import từ \`${src}\`\n${marker}\n${content}\n`;
|
|
541
|
+
added.push(src);
|
|
542
|
+
}
|
|
543
|
+
if (!added.length) {
|
|
544
|
+
console.log("Không có nội dung mới để import (các file rule khác đều trống hoặc đã import).");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
fs.writeFileSync(ag, agText);
|
|
548
|
+
console.log(`✓ Đã import vào AGENTS.md từ: ${added.join(", ")}`);
|
|
549
|
+
console.log(" Hãy rà soát, gộp vào router/Tier-1 cho gọn, rồi xoá file gốc nếu muốn.");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function mcp() {
|
|
553
|
+
if (args[1] !== "add") {
|
|
554
|
+
console.error("Dùng: agent-kb mcp add <name> -c <command> [-a arg1,arg2]");
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
const name = args.slice(2).find((a) => a && !a.startsWith("-"));
|
|
558
|
+
const command = flag("-c") || flag("--command");
|
|
559
|
+
const argsCsv = flag("-a") || flag("--args");
|
|
560
|
+
if (!name || !command) {
|
|
561
|
+
console.error("✗ Thiếu name hoặc command. Vd: agent-kb mcp add github -c npx -a @org/mcp,serve");
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
const p = path.join(CWD, ".mcp.json");
|
|
565
|
+
const json = readJSONSafe(p) || { mcpServers: {} };
|
|
566
|
+
if (!json.mcpServers) json.mcpServers = {};
|
|
567
|
+
if (json.mcpServers[name] && !FORCE) {
|
|
568
|
+
console.error(`✗ MCP server '${name}' đã có trong .mcp.json (dùng --force để ghi đè).`);
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
json.mcpServers[name] = { command, args: argsCsv ? argsCsv.split(",").map((s) => s.trim()) : [] };
|
|
572
|
+
fs.writeFileSync(p, JSON.stringify(json, null, 2) + "\n");
|
|
573
|
+
console.log(`✓ Đã thêm MCP server '${name}' vào .mcp.json`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function installHook(fromInit) {
|
|
577
|
+
const gitDir = path.join(CWD, ".git");
|
|
578
|
+
if (!fs.existsSync(gitDir)) {
|
|
579
|
+
if (fromInit) {
|
|
580
|
+
console.log("↷ Bỏ qua --hook: thư mục không phải git repo.");
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
console.error("✗ Không phải git repo (.git không tồn tại).");
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
const hooksDir = path.join(gitDir, "hooks");
|
|
587
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
588
|
+
const dest = path.join(hooksDir, "pre-commit");
|
|
589
|
+
const line = "npx --yes @dtd-dev/agent-kb doctor || { echo 'agent-kb doctor báo lỗi (commit với --no-verify để bỏ qua)'; exit 1; }";
|
|
590
|
+
if (fs.existsSync(dest)) {
|
|
591
|
+
const cur = fs.readFileSync(dest, "utf8");
|
|
592
|
+
if (cur.includes("agent-kb doctor")) {
|
|
593
|
+
console.log("Hook đã có sẵn.");
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
fs.writeFileSync(dest, cur.replace(/\s*$/, "\n") + `\n# agent-kb\n${line}\n`);
|
|
597
|
+
} else {
|
|
598
|
+
fs.writeFileSync(dest, `#!/bin/sh\n# agent-kb pre-commit\n${line}\n`);
|
|
599
|
+
}
|
|
600
|
+
fs.chmodSync(dest, 0o755);
|
|
601
|
+
console.log("✓ Đã cài git pre-commit hook (chạy agent-kb doctor).");
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function help() {
|
|
605
|
+
console.log(`agent-kb — scaffold .ai/ knowledge base + agent config (đa tool, tiết kiệm token)
|
|
606
|
+
|
|
607
|
+
Lệnh:
|
|
608
|
+
agent-kb [init] Khởi tạo (tương tác; -y để dùng mặc định, -f để ghi đè)
|
|
609
|
+
agent-kb feature <tên> Tạo .ai/specs/<tên>/ từ khung trống
|
|
610
|
+
agent-kb adr <tiêu đề> Tạo ADR mới trong .ai/decisions/ (tự đánh số)
|
|
611
|
+
agent-kb skill <a>/<tên> Tạo skill mới trong .ai/skills/<a>/
|
|
612
|
+
agent-kb agent <tên> Tạo sub-agent (.ai/agents/ → emit .claude/agents/)
|
|
613
|
+
agent-kb doctor Kiểm tra & audit KB (thoát mã ≠0 nếu có lỗi)
|
|
614
|
+
agent-kb import Gom nội dung rule cũ (.cursorrules, CLAUDE.md...) vào AGENTS.md
|
|
615
|
+
agent-kb mcp add <name> Thêm MCP server vào .mcp.json (-c command [-a args])
|
|
616
|
+
agent-kb hook install Cài git pre-commit hook chạy doctor
|
|
617
|
+
agent-kb help Hiện trợ giúp
|
|
618
|
+
|
|
619
|
+
Cờ cho init:
|
|
620
|
+
--name <tên> Tên dự án --database <db> Database
|
|
621
|
+
--backend <stack> Backend --cicd <ci> CI/CD
|
|
622
|
+
--frontend <stack> Frontend --deploy <lệnh> Lệnh deploy
|
|
623
|
+
--tools <list|all> Bật tool: cursor,windsurf,cline,zed,aider
|
|
624
|
+
--stack <list> Gói chuẩn stack: ${availableStacks().join(",") || "(none)"}
|
|
625
|
+
--hook Cài luôn git pre-commit hook
|
|
626
|
+
-y, --yes Không hỏi (tự nhận diện stack) -f, --force Ghi đè
|
|
627
|
+
|
|
628
|
+
Cờ cho 'agent':
|
|
629
|
+
--desc <mô tả> Khi nào dùng agent --tools <list> Tools (vd "Read,Grep,Bash")
|
|
630
|
+
--model <model> Model (Enter = kế thừa)
|
|
631
|
+
`);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
(async () => {
|
|
635
|
+
try {
|
|
636
|
+
if (cmd === "help" || args.includes("-h") || args.includes("--help")) return help();
|
|
637
|
+
if (cmd === "init") return await init();
|
|
638
|
+
if (cmd === "feature") return feature();
|
|
639
|
+
if (cmd === "adr") return adr();
|
|
640
|
+
if (cmd === "skill") return skill();
|
|
641
|
+
if (cmd === "agent") return await agentCmd();
|
|
642
|
+
if (cmd === "doctor") return doctor();
|
|
643
|
+
if (cmd === "import") return importCmd();
|
|
644
|
+
if (cmd === "mcp") return mcp();
|
|
645
|
+
if (cmd === "hook") return installHook(false);
|
|
646
|
+
console.error(`Lệnh không rõ: ${cmd}\n`);
|
|
647
|
+
help();
|
|
648
|
+
process.exit(1);
|
|
649
|
+
} catch (e) {
|
|
650
|
+
console.error("✗ Lỗi:", e.message);
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dtd-dev/agent-kb",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"bin": {
|
|
6
|
+
"agent-kb": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"template"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai",
|
|
17
|
+
"agent-skills",
|
|
18
|
+
"agents",
|
|
19
|
+
"knowledge-base",
|
|
20
|
+
"token-efficient",
|
|
21
|
+
"code-accuracy",
|
|
22
|
+
"enterprise",
|
|
23
|
+
"business-domain",
|
|
24
|
+
"scaffold",
|
|
25
|
+
"monorepo",
|
|
26
|
+
"claude",
|
|
27
|
+
"cursor",
|
|
28
|
+
"copilot",
|
|
29
|
+
"gemini",
|
|
30
|
+
"agents.md"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
}
|