@afterxleep/doc-bot 1.0.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/LICENSE +21 -0
- package/README.md +359 -0
- package/bin/doc-bot.js +94 -0
- package/package.json +60 -0
- package/src/index.js +371 -0
- package/src/services/DocumentationService.js +247 -0
- package/src/services/InferenceEngine.js +207 -0
- package/src/services/ManifestLoader.js +135 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
class InferenceEngine {
|
|
4
|
+
constructor(documentationService, manifestLoader = null) {
|
|
5
|
+
this.docService = documentationService;
|
|
6
|
+
this.manifestLoader = manifestLoader;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async initialize() {
|
|
10
|
+
// Initialize any required data
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async getRelevantDocumentation(context) {
|
|
14
|
+
try {
|
|
15
|
+
const globalRules = await this.docService.getGlobalRules();
|
|
16
|
+
const contextualDocs = await this.getContextualDocs(context);
|
|
17
|
+
const inferredDocs = await this.getInferredDocs(context);
|
|
18
|
+
|
|
19
|
+
const confidence = this.calculateConfidence(context, contextualDocs, inferredDocs);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
globalRules: globalRules || [],
|
|
23
|
+
contextualDocs,
|
|
24
|
+
inferredDocs,
|
|
25
|
+
confidence
|
|
26
|
+
};
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error getting relevant documentation:', error);
|
|
29
|
+
return {
|
|
30
|
+
globalRules: [],
|
|
31
|
+
contextualDocs: [],
|
|
32
|
+
inferredDocs: [],
|
|
33
|
+
confidence: 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getContextualDocs(context) {
|
|
39
|
+
const docs = [];
|
|
40
|
+
|
|
41
|
+
// Get docs based on file path
|
|
42
|
+
if (context.filePath) {
|
|
43
|
+
const pathDocs = await this.docService.getContextualDocs(context.filePath);
|
|
44
|
+
docs.push(...pathDocs);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return this.removeDuplicates(docs);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getInferredDocs(context) {
|
|
51
|
+
const docs = [];
|
|
52
|
+
|
|
53
|
+
// Keyword-based inference
|
|
54
|
+
if (context.query) {
|
|
55
|
+
const keywordDocs = await this.getDocsByKeywords(context.query);
|
|
56
|
+
docs.push(...keywordDocs);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Pattern-based inference
|
|
60
|
+
if (context.codeSnippet) {
|
|
61
|
+
const patternDocs = await this.getDocsByPatterns(context.codeSnippet);
|
|
62
|
+
docs.push(...patternDocs);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// File extension inference
|
|
66
|
+
if (context.filePath) {
|
|
67
|
+
const extensionDocs = await this.getDocsByFileExtension(context.filePath);
|
|
68
|
+
docs.push(...extensionDocs);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return this.removeDuplicates(docs);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getDocsByKeywords(query) {
|
|
75
|
+
if (!this.manifestLoader) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const manifest = await this.manifestLoader.load();
|
|
80
|
+
const keywords = manifest.inference?.keywords || {};
|
|
81
|
+
|
|
82
|
+
const docs = [];
|
|
83
|
+
const queryLower = query.toLowerCase();
|
|
84
|
+
|
|
85
|
+
for (const [keyword, docPaths] of Object.entries(keywords)) {
|
|
86
|
+
if (queryLower.includes(keyword.toLowerCase())) {
|
|
87
|
+
for (const docPath of docPaths) {
|
|
88
|
+
const doc = this.docService.getDocument(docPath);
|
|
89
|
+
if (doc) {
|
|
90
|
+
docs.push(doc);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return docs;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getDocsByPatterns(codeSnippet) {
|
|
100
|
+
if (!this.manifestLoader) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const manifest = await this.manifestLoader.load();
|
|
105
|
+
const patterns = manifest.inference?.patterns || {};
|
|
106
|
+
|
|
107
|
+
const docs = [];
|
|
108
|
+
|
|
109
|
+
for (const [pattern, docPaths] of Object.entries(patterns)) {
|
|
110
|
+
if (codeSnippet.includes(pattern)) {
|
|
111
|
+
for (const docPath of docPaths) {
|
|
112
|
+
const doc = this.docService.getDocument(docPath);
|
|
113
|
+
if (doc) {
|
|
114
|
+
docs.push(doc);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return docs;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getDocsByFileExtension(filePath) {
|
|
124
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
125
|
+
const docs = [];
|
|
126
|
+
|
|
127
|
+
// Common patterns for different file types
|
|
128
|
+
const extensionMappings = {
|
|
129
|
+
'.js': ['javascript', 'js', 'node'],
|
|
130
|
+
'.ts': ['typescript', 'ts', 'javascript'],
|
|
131
|
+
'.jsx': ['react', 'jsx', 'javascript'],
|
|
132
|
+
'.tsx': ['react', 'tsx', 'typescript'],
|
|
133
|
+
'.vue': ['vue', 'javascript'],
|
|
134
|
+
'.py': ['python', 'py'],
|
|
135
|
+
'.java': ['java'],
|
|
136
|
+
'.cpp': ['cpp', 'c++'],
|
|
137
|
+
'.c': ['c', 'cpp'],
|
|
138
|
+
'.cs': ['csharp', 'c#'],
|
|
139
|
+
'.rb': ['ruby'],
|
|
140
|
+
'.go': ['golang', 'go'],
|
|
141
|
+
'.rs': ['rust'],
|
|
142
|
+
'.php': ['php'],
|
|
143
|
+
'.swift': ['swift', 'ios', 'macos'],
|
|
144
|
+
'.kt': ['kotlin'],
|
|
145
|
+
'.scala': ['scala'],
|
|
146
|
+
'.md': ['markdown', 'documentation'],
|
|
147
|
+
'.css': ['css', 'styling'],
|
|
148
|
+
'.scss': ['sass', 'scss', 'css'],
|
|
149
|
+
'.html': ['html', 'web'],
|
|
150
|
+
'.json': ['json', 'config'],
|
|
151
|
+
'.yaml': ['yaml', 'config'],
|
|
152
|
+
'.yml': ['yaml', 'config'],
|
|
153
|
+
'.xml': ['xml'],
|
|
154
|
+
'.sql': ['sql', 'database'],
|
|
155
|
+
'.sh': ['bash', 'shell', 'script'],
|
|
156
|
+
'.dockerfile': ['docker', 'container'],
|
|
157
|
+
'.tf': ['terraform', 'infrastructure']
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const keywords = extensionMappings[ext] || [];
|
|
161
|
+
|
|
162
|
+
for (const keyword of keywords) {
|
|
163
|
+
const keywordDocs = await this.docService.searchDocuments(keyword);
|
|
164
|
+
docs.push(...keywordDocs);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return docs;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
calculateConfidence(context, contextualDocs, inferredDocs) {
|
|
171
|
+
let confidence = 0;
|
|
172
|
+
|
|
173
|
+
// Base confidence from context richness
|
|
174
|
+
if (context.query) confidence += 0.3;
|
|
175
|
+
if (context.filePath) confidence += 0.3;
|
|
176
|
+
if (context.codeSnippet) confidence += 0.2;
|
|
177
|
+
|
|
178
|
+
// Boost confidence based on number of matches
|
|
179
|
+
if (contextualDocs.length > 0) {
|
|
180
|
+
confidence += Math.min(contextualDocs.length * 0.1, 0.3);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (inferredDocs.length > 0) {
|
|
184
|
+
confidence += Math.min(inferredDocs.length * 0.05, 0.2);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Reduce confidence if no matches found
|
|
188
|
+
if (contextualDocs.length === 0 && inferredDocs.length === 0) {
|
|
189
|
+
confidence *= 0.5;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return Math.min(confidence, 1.0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
removeDuplicates(docs) {
|
|
196
|
+
const seen = new Set();
|
|
197
|
+
return docs.filter(doc => {
|
|
198
|
+
if (seen.has(doc.fileName)) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
seen.add(doc.fileName);
|
|
202
|
+
return true;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = { InferenceEngine };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class ManifestLoader {
|
|
5
|
+
constructor(configPath) {
|
|
6
|
+
this.configPath = configPath;
|
|
7
|
+
this.manifest = null;
|
|
8
|
+
this.lastModified = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async load() {
|
|
12
|
+
try {
|
|
13
|
+
const stats = await fs.stat(this.configPath);
|
|
14
|
+
|
|
15
|
+
// Only reload if file has changed
|
|
16
|
+
if (this.manifest && this.lastModified && stats.mtime <= this.lastModified) {
|
|
17
|
+
return this.manifest;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const content = await fs.readFile(this.configPath, 'utf8');
|
|
21
|
+
this.manifest = JSON.parse(content);
|
|
22
|
+
this.lastModified = stats.mtime;
|
|
23
|
+
|
|
24
|
+
// Validate manifest structure
|
|
25
|
+
this.validateManifest();
|
|
26
|
+
|
|
27
|
+
return this.manifest;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT') {
|
|
30
|
+
// Create default manifest if file doesn't exist
|
|
31
|
+
this.manifest = this.createDefaultManifest();
|
|
32
|
+
await this.save();
|
|
33
|
+
return this.manifest;
|
|
34
|
+
}
|
|
35
|
+
throw new Error(`Failed to load manifest: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async reload() {
|
|
40
|
+
this.manifest = null;
|
|
41
|
+
this.lastModified = null;
|
|
42
|
+
return await this.load();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async save() {
|
|
46
|
+
if (!this.manifest) {
|
|
47
|
+
throw new Error('No manifest to save');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await fs.ensureDir(path.dirname(this.configPath));
|
|
51
|
+
await fs.writeJSON(this.configPath, this.manifest, { spaces: 2 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
validateManifest() {
|
|
55
|
+
if (!this.manifest) {
|
|
56
|
+
throw new Error('Manifest is null');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Required fields
|
|
60
|
+
if (!this.manifest.name) {
|
|
61
|
+
this.manifest.name = 'Project Documentation';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!this.manifest.version) {
|
|
65
|
+
this.manifest.version = '1.0.0';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Optional fields with defaults
|
|
69
|
+
if (!this.manifest.globalRules) {
|
|
70
|
+
this.manifest.globalRules = [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!this.manifest.contextualRules) {
|
|
74
|
+
this.manifest.contextualRules = {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!this.manifest.inference) {
|
|
78
|
+
this.manifest.inference = {
|
|
79
|
+
keywords: {},
|
|
80
|
+
patterns: {}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate globalRules is array
|
|
85
|
+
if (!Array.isArray(this.manifest.globalRules)) {
|
|
86
|
+
throw new Error('globalRules must be an array');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validate contextualRules is object
|
|
90
|
+
if (typeof this.manifest.contextualRules !== 'object') {
|
|
91
|
+
throw new Error('contextualRules must be an object');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Validate inference structure
|
|
95
|
+
if (!this.manifest.inference.keywords) {
|
|
96
|
+
this.manifest.inference.keywords = {};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!this.manifest.inference.patterns) {
|
|
100
|
+
this.manifest.inference.patterns = {};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
createDefaultManifest() {
|
|
105
|
+
return {
|
|
106
|
+
name: 'Project Documentation',
|
|
107
|
+
version: '1.0.0',
|
|
108
|
+
description: 'AI-powered documentation',
|
|
109
|
+
globalRules: [],
|
|
110
|
+
contextualRules: {},
|
|
111
|
+
inference: {
|
|
112
|
+
keywords: {},
|
|
113
|
+
patterns: {}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getManifest() {
|
|
119
|
+
return this.manifest;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getGlobalRules() {
|
|
123
|
+
return this.manifest?.globalRules || [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getContextualRules() {
|
|
127
|
+
return this.manifest?.contextualRules || {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getInferenceConfig() {
|
|
131
|
+
return this.manifest?.inference || { keywords: {}, patterns: {} };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = { ManifestLoader };
|