@khiem_enhance/ai-doc-agent 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 +32 -0
- package/dist/analyzers/architecture.js +27 -0
- package/dist/analyzers/modules.js +39 -0
- package/dist/cli.js +16 -0
- package/dist/commands/generate.js +45 -0
- package/dist/config/env.js +8 -0
- package/dist/git/gitUtils.js +13 -0
- package/dist/llm/openaiClient.js +19 -0
- package/dist/scanner/contentReader.js +10 -0
- package/dist/scanner/fileScanner.js +17 -0
- package/dist/scanner/moduleDetector.js +23 -0
- package/dist/writers/markdownWriter.js +12 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ai-doc-agent
|
|
2
|
+
|
|
3
|
+
AI-powered documentation generator that analyzes your source code and generates
|
|
4
|
+
technical documentation directly from it.
|
|
5
|
+
|
|
6
|
+
> The codebase is the single source of truth.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ Features
|
|
11
|
+
|
|
12
|
+
- Generate **Architecture Overview**
|
|
13
|
+
- Generate **Module / Feature documentation**
|
|
14
|
+
- Generate **API documentation** (from FE usage or BE code)
|
|
15
|
+
- Generate **Component / Service documentation**
|
|
16
|
+
- Output structured **Markdown docs**
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 📦 Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js >= 18
|
|
23
|
+
- An OpenAI API key
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🔐 Setup
|
|
28
|
+
|
|
29
|
+
Set your OpenAI API key as an environment variable:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export OPENAI_API_KEY=sk-xxxxxxxx
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateArchitectureDoc = generateArchitectureDoc;
|
|
4
|
+
const openaiClient_1 = require("../llm/openaiClient");
|
|
5
|
+
async function generateArchitectureDoc(tree, files) {
|
|
6
|
+
const prompt = `
|
|
7
|
+
You are an AI Documentation Agent.
|
|
8
|
+
|
|
9
|
+
RULES:
|
|
10
|
+
- Code is the single source of truth
|
|
11
|
+
- Do NOT invent features
|
|
12
|
+
- If unclear, mark assumptions clearly
|
|
13
|
+
|
|
14
|
+
GOAL:
|
|
15
|
+
Generate Architecture Overview documentation.
|
|
16
|
+
|
|
17
|
+
PROJECT STRUCTURE:
|
|
18
|
+
${tree}
|
|
19
|
+
|
|
20
|
+
SOURCE CODE:
|
|
21
|
+
${files}
|
|
22
|
+
|
|
23
|
+
OUTPUT:
|
|
24
|
+
Markdown only.
|
|
25
|
+
`;
|
|
26
|
+
return (0, openaiClient_1.askLLM)(prompt);
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateModuleDocs = generateModuleDocs;
|
|
4
|
+
const openaiClient_1 = require("../llm/openaiClient");
|
|
5
|
+
async function generateModuleDocs(moduleName, fileList, sourceCode) {
|
|
6
|
+
const prompt = `
|
|
7
|
+
You are an AI Documentation Agent.
|
|
8
|
+
|
|
9
|
+
RULES:
|
|
10
|
+
- Code is the single source of truth
|
|
11
|
+
- Do NOT invent features
|
|
12
|
+
- If unclear, explicitly mark assumptions
|
|
13
|
+
|
|
14
|
+
GOAL:
|
|
15
|
+
Generate documentation for ONE module / feature.
|
|
16
|
+
|
|
17
|
+
MODULE NAME:
|
|
18
|
+
${moduleName}
|
|
19
|
+
|
|
20
|
+
FILES:
|
|
21
|
+
${fileList}
|
|
22
|
+
|
|
23
|
+
SOURCE CODE:
|
|
24
|
+
${sourceCode}
|
|
25
|
+
|
|
26
|
+
DOCUMENT STRUCTURE:
|
|
27
|
+
# ${moduleName}
|
|
28
|
+
|
|
29
|
+
## Purpose
|
|
30
|
+
## Responsibilities
|
|
31
|
+
## Main Files
|
|
32
|
+
## Execution Flow
|
|
33
|
+
## Edge Cases / Constraints
|
|
34
|
+
|
|
35
|
+
OUTPUT:
|
|
36
|
+
Markdown only.
|
|
37
|
+
`;
|
|
38
|
+
return (0, openaiClient_1.askLLM)(prompt);
|
|
39
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const generate_1 = require("./commands/generate");
|
|
6
|
+
const program = new commander_1.Command();
|
|
7
|
+
program
|
|
8
|
+
.name("ai-doc-agent")
|
|
9
|
+
.description("AI-powered documentation generator")
|
|
10
|
+
.version("0.1.0");
|
|
11
|
+
program
|
|
12
|
+
.command("generate")
|
|
13
|
+
.option("--since <commit>", "Only analyze changes since commit")
|
|
14
|
+
.option("--output <dir>", "Docs output directory", "docs")
|
|
15
|
+
.action(generate_1.generateDocs);
|
|
16
|
+
program.parse();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateDocs = generateDocs;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fileScanner_1 = require("../scanner/fileScanner");
|
|
9
|
+
const contentReader_1 = require("../scanner/contentReader");
|
|
10
|
+
const architecture_1 = require("../analyzers/architecture");
|
|
11
|
+
const modules_1 = require("../analyzers/modules");
|
|
12
|
+
const markdownWriter_1 = require("../writers/markdownWriter");
|
|
13
|
+
const gitUtils_1 = require("../git/gitUtils");
|
|
14
|
+
const moduleDetector_1 = require("../scanner/moduleDetector");
|
|
15
|
+
async function generateDocs(options) {
|
|
16
|
+
const root = process.cwd();
|
|
17
|
+
const files = options.since
|
|
18
|
+
? (0, gitUtils_1.getChangedFiles)(options.since).map(f => path_1.default.join(root, f))
|
|
19
|
+
: await (0, fileScanner_1.scanProject)(root);
|
|
20
|
+
// ---------- Architecture ----------
|
|
21
|
+
const tree = files
|
|
22
|
+
.map(f => path_1.default.relative(root, f))
|
|
23
|
+
.join("\n");
|
|
24
|
+
const architectureSource = files
|
|
25
|
+
.slice(0, 25)
|
|
26
|
+
.map(f => `FILE: ${f}\n${(0, contentReader_1.readFile)(f)}`)
|
|
27
|
+
.join("\n\n");
|
|
28
|
+
const architecture = await (0, architecture_1.generateArchitectureDoc)(tree, architectureSource);
|
|
29
|
+
(0, markdownWriter_1.writeDoc)(options.output, "architecture.md", architecture);
|
|
30
|
+
// ---------- Modules ----------
|
|
31
|
+
const modules = (0, moduleDetector_1.detectModules)(files, root);
|
|
32
|
+
for (const [moduleName, moduleFiles] of Object.entries(modules)) {
|
|
33
|
+
const fileList = moduleFiles
|
|
34
|
+
.map(f => path_1.default.relative(root, f))
|
|
35
|
+
.join("\n");
|
|
36
|
+
const source = moduleFiles
|
|
37
|
+
.slice(0, 20)
|
|
38
|
+
.map(f => `FILE: ${f}\n${(0, contentReader_1.readFile)(f)}`)
|
|
39
|
+
.join("\n\n");
|
|
40
|
+
const doc = await (0, modules_1.generateModuleDocs)(moduleName, fileList, source);
|
|
41
|
+
(0, markdownWriter_1.writeDoc)(path_1.default.join(options.output, "modules"), `${moduleName}.md`, doc);
|
|
42
|
+
console.log(`📄 Module doc generated: ${moduleName}`);
|
|
43
|
+
}
|
|
44
|
+
console.log("✅ Docs generation completed");
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getChangedFiles = getChangedFiles;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function getChangedFiles(since) {
|
|
6
|
+
if (!since)
|
|
7
|
+
return [];
|
|
8
|
+
const output = (0, child_process_1.execSync)(`git diff --name-only ${since}`, { encoding: "utf-8" });
|
|
9
|
+
return output
|
|
10
|
+
.split("\n")
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.filter(f => /\.(ts|tsx|js|jsx)$/.test(f));
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.openai = void 0;
|
|
7
|
+
exports.askLLM = askLLM;
|
|
8
|
+
const openai_1 = __importDefault(require("openai"));
|
|
9
|
+
const env_1 = require("../config/env");
|
|
10
|
+
exports.openai = new openai_1.default({
|
|
11
|
+
apiKey: env_1.env.openaiKey
|
|
12
|
+
});
|
|
13
|
+
async function askLLM(prompt) {
|
|
14
|
+
const res = await exports.openai.chat.completions.create({
|
|
15
|
+
model: env_1.env.model,
|
|
16
|
+
messages: [{ role: "user", content: prompt }]
|
|
17
|
+
});
|
|
18
|
+
return res.choices[0].message.content;
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readFile = readFile;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
function readFile(file) {
|
|
9
|
+
return fs_1.default.readFileSync(file, "utf-8");
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scanProject = scanProject;
|
|
7
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
8
|
+
async function scanProject(root) {
|
|
9
|
+
return (0, fast_glob_1.default)([
|
|
10
|
+
"**/*.{ts,tsx,js,jsx}",
|
|
11
|
+
"!**/node_modules/**",
|
|
12
|
+
"!**/dist/**"
|
|
13
|
+
], {
|
|
14
|
+
cwd: root,
|
|
15
|
+
absolute: true
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectModules = detectModules;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
function detectModules(files, root) {
|
|
9
|
+
const modules = {};
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
const relative = path_1.default.relative(root, file);
|
|
12
|
+
const parts = relative.split(path_1.default.sep);
|
|
13
|
+
// src/<module>/...
|
|
14
|
+
const moduleName = parts[0] === "src" && parts[1]
|
|
15
|
+
? parts[1]
|
|
16
|
+
: "shared";
|
|
17
|
+
if (!modules[moduleName]) {
|
|
18
|
+
modules[moduleName] = [];
|
|
19
|
+
}
|
|
20
|
+
modules[moduleName].push(file);
|
|
21
|
+
}
|
|
22
|
+
return modules;
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeDoc = writeDoc;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function writeDoc(dir, fileName, content) {
|
|
10
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
11
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, fileName), content);
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khiem_enhance/ai-doc-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered documentation generator from source code",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-doc-agent": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/cli.js",
|
|
10
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"commander": "^11.0.0",
|
|
20
|
+
"dotenv": "^16.0.0",
|
|
21
|
+
"fast-glob": "^3.3.0",
|
|
22
|
+
"openai": "^4.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|