@afterxleep/doc-bot 1.8.0 → 1.9.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 +44 -28
- package/bin/doc-bot.js +12 -27
- package/package.json +7 -2
- package/src/index.js +21 -66
- package/src/index.test.js +8 -3
- package/src/services/DocumentIndex.js +1 -1
- package/src/services/DocumentationService.js +5 -5
- package/src/services/InferenceEngine.js +8 -48
- package/src/services/__tests__/DocumentIndex.test.js +1 -1
- package/src/services/__tests__/InferenceEngine.integration.test.js +12 -20
- package/src/services/ManifestLoader.js +0 -135
package/README.md
CHANGED
|
@@ -10,24 +10,21 @@ It's platform agnostic and designed to replace and extend the rule systems provi
|
|
|
10
10
|
## What is doc-bot?
|
|
11
11
|
|
|
12
12
|
doc-bot is an intelligent documentation server that:
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- 📋 **Applies** global rules to every AI interaction
|
|
16
|
-
- 🎯 **Suggests** contextual documentation based on file patterns
|
|
17
|
-
- 🤖 **Detects** code patterns, frameworks, and keywords automatically
|
|
13
|
+
- 🧠 **Auto-indexes** content for smart inference, based on metadata and keywords
|
|
14
|
+
- 🤖 **Provides agentic tools** to query, and update your documentation
|
|
18
15
|
- 🔄 **Updates** automatically when docs change
|
|
19
16
|
|
|
20
17
|
## Why MCP Instead of Static Rules?
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
IDE's use static rule files (like Cursor Rules or Copilot's .github/copilot-instructions.md), and each one has their own format, metadata and approach.
|
|
23
20
|
|
|
24
21
|
### 🚀 Dynamic Search vs Static Rules
|
|
25
22
|
|
|
26
23
|
**Static Systems:**
|
|
27
24
|
- All rules must fit in a single file or limited token window
|
|
28
25
|
- AI reads everything, even irrelevant rules
|
|
29
|
-
- No way to search or filter documentation
|
|
30
|
-
- Rules compete for
|
|
26
|
+
- No way to search or filter documentation (besides plain 'grep')
|
|
27
|
+
- Rules compete for context space
|
|
31
28
|
|
|
32
29
|
**MCP with doc-bot:**
|
|
33
30
|
- AI searches for exactly what it needs
|
|
@@ -38,9 +35,8 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
38
35
|
### 🧠 Contextual Intelligence
|
|
39
36
|
|
|
40
37
|
**Static Systems:**
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
- Can't provide specific help for your current task
|
|
38
|
+
- Duplicate or symlinked rules to work with multiple agents
|
|
39
|
+
- Agents use `grep` for basic text-base searching
|
|
44
40
|
|
|
45
41
|
**MCP with doc-bot:**
|
|
46
42
|
- AI searches for relevant documentation based on your query
|
|
@@ -51,8 +47,8 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
51
47
|
### 📈 Scalability Without Limits
|
|
52
48
|
|
|
53
49
|
**Static Systems:**
|
|
54
|
-
- Limited by token count
|
|
55
|
-
- Adding more rules
|
|
50
|
+
- Limited by token count
|
|
51
|
+
- Adding more rules has impact in your context window
|
|
56
52
|
- Documentation competes with your actual code for context
|
|
57
53
|
|
|
58
54
|
**MCP with doc-bot:**
|
|
@@ -66,7 +62,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
66
62
|
**Static Systems:**
|
|
67
63
|
- Changes require restarting your AI/IDE
|
|
68
64
|
- No way to know if rules are current
|
|
69
|
-
- Manual synchronization across tools
|
|
65
|
+
- Manual synchronization across tools and AI agents
|
|
70
66
|
|
|
71
67
|
**MCP with doc-bot:**
|
|
72
68
|
- Hot reload on documentation changes
|
|
@@ -84,7 +80,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
84
80
|
**MCP with doc-bot:**
|
|
85
81
|
- AI can list all available documentation
|
|
86
82
|
- Discovers relevant docs automatically
|
|
87
|
-
- Suggests documentation based on
|
|
83
|
+
- Suggests documentation based on relevance
|
|
88
84
|
- Searchable knowledge base with intelligent ranking
|
|
89
85
|
- No need for AI to grep through your codebase - dedicated search engine
|
|
90
86
|
|
|
@@ -116,6 +112,8 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
116
112
|
}
|
|
117
113
|
}
|
|
118
114
|
```
|
|
115
|
+
Note: If a relative path does not work, you can use VSCode `${workspaceFolder}`environment variable `${workspaceFolder}/my-custom-docs`
|
|
116
|
+
|
|
119
117
|
|
|
120
118
|
**With verbose logging (for debugging):**
|
|
121
119
|
```json
|
|
@@ -193,6 +191,7 @@ alwaysApply: false
|
|
|
193
191
|
title: "Testing Guide"
|
|
194
192
|
description: "How to write and run tests"
|
|
195
193
|
keywords: ["testing", "jest", "tdd", "unit-tests"]
|
|
194
|
+
filePatterns: ["*.test.js", "*.spec.js", "__tests__/**/*"]
|
|
196
195
|
---
|
|
197
196
|
|
|
198
197
|
# Testing Guide
|
|
@@ -208,14 +207,16 @@ Run tests with: `npm test`
|
|
|
208
207
|
|
|
209
208
|
## Frontmatter-Based Configuration
|
|
210
209
|
|
|
211
|
-
doc-bot uses frontmatter in your markdown files to automatically detect and categorize rules
|
|
210
|
+
doc-bot uses frontmatter in your markdown files to automatically detect and categorize rules.
|
|
212
211
|
|
|
213
212
|
### Frontmatter Fields:
|
|
214
213
|
|
|
215
214
|
- **`alwaysApply: true`** - Global rules applied to every AI interaction
|
|
216
215
|
- **`alwaysApply: false`** - Contextual rules searched and applied based on relevance
|
|
217
216
|
- **`keywords: ["list", "of", "keywords"]`** - For smart indexing and search
|
|
217
|
+
- **`filePatterns: ["*.js", "src/**/*.ts"]`** - Apply docs to specific files (see below)
|
|
218
218
|
- **`title`** and **`description`** - Standard metadata
|
|
219
|
+
- **`confidence: 0.9`** - Relevance confidence score (0-1)
|
|
219
220
|
|
|
220
221
|
### 🎯 Automatic Intelligence
|
|
221
222
|
|
|
@@ -243,6 +244,31 @@ keywords: ["react", "components", "hooks", "jsx"]
|
|
|
243
244
|
Your documentation content here...
|
|
244
245
|
```
|
|
245
246
|
|
|
247
|
+
### File Pattern Matching
|
|
248
|
+
|
|
249
|
+
doc-bot supports contextual documentation using file patterns. Documentation can be targeted to specific files:
|
|
250
|
+
|
|
251
|
+
```markdown
|
|
252
|
+
---
|
|
253
|
+
alwaysApply: false
|
|
254
|
+
title: "React Testing Guide"
|
|
255
|
+
keywords: ["testing", "jest", "react"]
|
|
256
|
+
filePatterns: ["*.test.jsx", "*.test.tsx", "__tests__/**/*"]
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
# React Testing Guide
|
|
260
|
+
|
|
261
|
+
This documentation appears only when working with test files...
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Pattern Examples:**
|
|
265
|
+
- `*.js` - All JavaScript files
|
|
266
|
+
- `src/**/*.ts` - TypeScript files in src directory
|
|
267
|
+
- `[Tt]est.js` - Test.js or test.js
|
|
268
|
+
- `*.{jsx,tsx}` - React component files
|
|
269
|
+
|
|
270
|
+
When AI requests documentation for a specific file (e.g., `Button.test.jsx`), only docs with matching patterns are returned.
|
|
271
|
+
|
|
246
272
|
## Development setup
|
|
247
273
|
|
|
248
274
|
### Running locally
|
|
@@ -267,13 +293,7 @@ Your documentation content here...
|
|
|
267
293
|
```bash
|
|
268
294
|
npm run dev
|
|
269
295
|
```
|
|
270
|
-
|
|
271
|
-
5. **Run with examples documentation:**
|
|
272
|
-
```bash
|
|
273
|
-
npm run start:examples
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
6. **Run tests:**
|
|
296
|
+
5. **Run tests:**
|
|
277
297
|
```bash
|
|
278
298
|
npm test
|
|
279
299
|
```
|
|
@@ -291,7 +311,6 @@ doc-bot [options]
|
|
|
291
311
|
|
|
292
312
|
Options:
|
|
293
313
|
-d, --docs <path> Path to docs folder (default: doc-bot)
|
|
294
|
-
-c, --config <path> Path to manifest file (optional, for backward compatibility)
|
|
295
314
|
-v, --verbose Enable verbose logging
|
|
296
315
|
-w, --watch Watch for file changes
|
|
297
316
|
-h, --help Show help
|
|
@@ -307,9 +326,6 @@ doc-bot --docs ./my-docs
|
|
|
307
326
|
|
|
308
327
|
# With verbose logging and file watching
|
|
309
328
|
doc-bot --verbose --watch
|
|
310
|
-
|
|
311
|
-
# With optional manifest for backward compatibility
|
|
312
|
-
doc-bot --config ./manifest.json
|
|
313
329
|
```
|
|
314
330
|
|
|
315
331
|
## Publishing and Development
|
|
@@ -378,4 +394,4 @@ MIT License - see the [LICENSE](LICENSE) file for details.
|
|
|
378
394
|
|
|
379
395
|
- [npm package](https://www.npmjs.com/package/@afterxleep/doc-bot)
|
|
380
396
|
- [GitHub repository](https://github.com/afterxleep/doc-bot)
|
|
381
|
-
- [Model Context Protocol](https://github.com/modelcontextprotocol/specification)
|
|
397
|
+
- [Model Context Protocol](https://github.com/modelcontextprotocol/specification)
|
package/bin/doc-bot.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { DocsServer } from '../src/index.js';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { dirname, join } from 'path';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
|
|
8
14
|
|
|
9
15
|
program
|
|
10
16
|
.name('doc-bot')
|
|
11
17
|
.description('Generic MCP server for intelligent documentation access')
|
|
12
18
|
.version(packageJson.version)
|
|
13
19
|
.option('-d, --docs <path>', 'Path to docs folder', 'doc-bot')
|
|
14
|
-
.option('-c, --config <path>', 'Path to manifest file')
|
|
15
20
|
.option('-v, --verbose', 'Enable verbose logging')
|
|
16
21
|
.option('-w, --watch', 'Watch for file changes')
|
|
17
22
|
.parse();
|
|
@@ -20,7 +25,6 @@ const options = program.opts();
|
|
|
20
25
|
|
|
21
26
|
async function main() {
|
|
22
27
|
const docsPath = path.resolve(options.docs);
|
|
23
|
-
const configPath = options.config ? path.resolve(options.config) : path.resolve(options.docs, 'manifest.json');
|
|
24
28
|
|
|
25
29
|
// Check if documentation folder exists
|
|
26
30
|
if (!await fs.pathExists(docsPath)) {
|
|
@@ -40,23 +44,8 @@ async function main() {
|
|
|
40
44
|
process.exit(1);
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
// Manifest is now optional - only create if explicitly requested
|
|
44
|
-
if (options.config && !await fs.pathExists(configPath)) {
|
|
45
|
-
if (options.verbose) {
|
|
46
|
-
console.error('📝 Creating default manifest.json...');
|
|
47
|
-
}
|
|
48
|
-
const defaultManifest = {
|
|
49
|
-
name: 'Project Documentation',
|
|
50
|
-
version: '1.0.0',
|
|
51
|
-
description: 'AI-powered documentation (auto-generated from frontmatter)',
|
|
52
|
-
note: 'This manifest is auto-generated. Configure rules using frontmatter in your markdown files.'
|
|
53
|
-
};
|
|
54
|
-
await fs.writeJSON(configPath, defaultManifest, { spaces: 2 });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
47
|
const server = new DocsServer({
|
|
58
48
|
docsPath,
|
|
59
|
-
configPath,
|
|
60
49
|
verbose: options.verbose,
|
|
61
50
|
watch: options.watch
|
|
62
51
|
});
|
|
@@ -64,11 +53,7 @@ async function main() {
|
|
|
64
53
|
if (options.verbose) {
|
|
65
54
|
console.error('🚀 Starting doc-bot...');
|
|
66
55
|
console.error(`📁 Documentation: ${docsPath}`);
|
|
67
|
-
|
|
68
|
-
console.error(`⚙️ Configuration: ${configPath}`);
|
|
69
|
-
} else {
|
|
70
|
-
console.error(`⚙️ Configuration: Auto-generated from frontmatter`);
|
|
71
|
-
}
|
|
56
|
+
console.error(`⚙️ Configuration: Frontmatter-based`);
|
|
72
57
|
|
|
73
58
|
if (options.watch) {
|
|
74
59
|
console.error('👀 Watching for file changes...');
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afterxleep/doc-bot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Generic MCP server for intelligent documentation access in any project",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "src/index.js",
|
|
6
7
|
"bin": {
|
|
7
8
|
"doc-bot": "bin/doc-bot.js"
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
"start:watch": "node bin/doc-bot.js --verbose --watch",
|
|
12
13
|
"start:examples": "node bin/doc-bot.js --docs ./samples --verbose",
|
|
13
14
|
"dev": "node bin/doc-bot.js --verbose --watch",
|
|
14
|
-
"test": "jest",
|
|
15
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
15
16
|
"test:watch": "jest --watch",
|
|
16
17
|
"test:coverage": "jest --coverage",
|
|
17
18
|
"lint": "eslint src/ bin/ --ext .js",
|
|
@@ -51,6 +52,10 @@
|
|
|
51
52
|
"jest": "^29.7.0",
|
|
52
53
|
"supertest": "^6.3.0"
|
|
53
54
|
},
|
|
55
|
+
"jest": {
|
|
56
|
+
"testEnvironment": "node",
|
|
57
|
+
"transform": {}
|
|
58
|
+
},
|
|
54
59
|
"engines": {
|
|
55
60
|
"node": ">=18.0.0"
|
|
56
61
|
},
|
package/src/index.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { DocumentationService } from './services/DocumentationService.js';
|
|
5
|
+
import { InferenceEngine } from './services/InferenceEngine.js';
|
|
6
|
+
import chokidar from 'chokidar';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname } from 'path';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
10
14
|
|
|
11
15
|
class DocsServer {
|
|
12
16
|
constructor(options = {}) {
|
|
13
17
|
this.options = {
|
|
14
18
|
docsPath: options.docsPath || './doc-bot',
|
|
15
|
-
configPath: options.configPath || './doc-bot/manifest.json', // Optional, for backward compatibility
|
|
16
19
|
verbose: options.verbose || false,
|
|
17
20
|
watch: options.watch || false,
|
|
18
21
|
...options
|
|
@@ -32,10 +35,8 @@ class DocsServer {
|
|
|
32
35
|
}
|
|
33
36
|
});
|
|
34
37
|
|
|
35
|
-
// ManifestLoader is now optional - only create if manifest exists
|
|
36
|
-
this.manifestLoader = null;
|
|
37
38
|
this.docService = new DocumentationService(this.options.docsPath);
|
|
38
|
-
this.inferenceEngine = new InferenceEngine(this.docService
|
|
39
|
+
this.inferenceEngine = new InferenceEngine(this.docService);
|
|
39
40
|
|
|
40
41
|
this.setupHandlers();
|
|
41
42
|
|
|
@@ -60,7 +61,6 @@ class DocsServer {
|
|
|
60
61
|
setupHandlers() {
|
|
61
62
|
// List available resources
|
|
62
63
|
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
63
|
-
const manifest = await this.manifestLoader.load();
|
|
64
64
|
return {
|
|
65
65
|
resources: [
|
|
66
66
|
{
|
|
@@ -81,12 +81,6 @@ class DocsServer {
|
|
|
81
81
|
description: 'Smart documentation suggestions based on your current context',
|
|
82
82
|
mimeType: 'application/json'
|
|
83
83
|
},
|
|
84
|
-
{
|
|
85
|
-
uri: 'docs://manifest',
|
|
86
|
-
name: 'Documentation Manifest',
|
|
87
|
-
description: 'Project documentation configuration',
|
|
88
|
-
mimeType: 'application/json'
|
|
89
|
-
},
|
|
90
84
|
{
|
|
91
85
|
uri: 'docs://system-prompt',
|
|
92
86
|
name: 'System Prompt Injection',
|
|
@@ -122,24 +116,6 @@ class DocsServer {
|
|
|
122
116
|
}]
|
|
123
117
|
};
|
|
124
118
|
|
|
125
|
-
case 'docs://manifest':
|
|
126
|
-
// Generate manifest from frontmatter data
|
|
127
|
-
const manifestDocs = await this.docService.getAllDocuments();
|
|
128
|
-
const generatedManifest = {
|
|
129
|
-
name: 'Project Documentation',
|
|
130
|
-
version: '1.0.0',
|
|
131
|
-
description: 'Auto-generated from frontmatter',
|
|
132
|
-
globalRules: manifestDocs.filter(doc => doc.metadata?.alwaysApply === true).map(doc => doc.fileName),
|
|
133
|
-
contextualRules: this.extractContextualRules(manifestDocs)
|
|
134
|
-
};
|
|
135
|
-
return {
|
|
136
|
-
contents: [{
|
|
137
|
-
uri,
|
|
138
|
-
mimeType: 'application/json',
|
|
139
|
-
text: JSON.stringify(generatedManifest, null, 2)
|
|
140
|
-
}]
|
|
141
|
-
};
|
|
142
|
-
|
|
143
119
|
case 'docs://system-prompt':
|
|
144
120
|
const systemPrompt = await this.generateSystemPrompt();
|
|
145
121
|
return {
|
|
@@ -407,7 +383,7 @@ class DocsServer {
|
|
|
407
383
|
}
|
|
408
384
|
|
|
409
385
|
setupWatcher() {
|
|
410
|
-
const watcher = chokidar.watch(
|
|
386
|
+
const watcher = chokidar.watch(this.options.docsPath, {
|
|
411
387
|
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
|
412
388
|
persistent: true
|
|
413
389
|
});
|
|
@@ -417,15 +393,8 @@ class DocsServer {
|
|
|
417
393
|
console.error(`📄 Documentation updated: ${path.relative(process.cwd(), filePath)}`);
|
|
418
394
|
}
|
|
419
395
|
|
|
420
|
-
// Reload manifest if config changed
|
|
421
|
-
if (filePath === this.options.configPath) {
|
|
422
|
-
await this.manifestLoader.reload();
|
|
423
|
-
}
|
|
424
|
-
|
|
425
396
|
// Reload docs if documentation changed
|
|
426
|
-
|
|
427
|
-
await this.docService.reload();
|
|
428
|
-
}
|
|
397
|
+
await this.docService.reload();
|
|
429
398
|
});
|
|
430
399
|
}
|
|
431
400
|
|
|
@@ -599,12 +568,11 @@ class DocsServer {
|
|
|
599
568
|
}
|
|
600
569
|
|
|
601
570
|
async createOrUpdateRule({ fileName, title, description, keywords, alwaysApply, content }) {
|
|
602
|
-
const
|
|
603
|
-
const path = require('path');
|
|
571
|
+
const { default: fsExtra } = await import('fs-extra');
|
|
604
572
|
|
|
605
573
|
try {
|
|
606
574
|
// Ensure the docs directory exists
|
|
607
|
-
await
|
|
575
|
+
await fsExtra.ensureDir(this.options.docsPath);
|
|
608
576
|
|
|
609
577
|
// Create the full file path
|
|
610
578
|
const filePath = path.join(this.options.docsPath, fileName);
|
|
@@ -625,7 +593,7 @@ class DocsServer {
|
|
|
625
593
|
const fullContent = frontmatter + content;
|
|
626
594
|
|
|
627
595
|
// Check if file exists to determine if this is create or update
|
|
628
|
-
const fileExists = await
|
|
596
|
+
const fileExists = await fsExtra.pathExists(filePath);
|
|
629
597
|
const action = fileExists ? 'updated' : 'created';
|
|
630
598
|
|
|
631
599
|
// Write the file
|
|
@@ -867,15 +835,6 @@ class DocsServer {
|
|
|
867
835
|
}
|
|
868
836
|
|
|
869
837
|
async start() {
|
|
870
|
-
// Initialize manifest loader if manifest exists (backward compatibility)
|
|
871
|
-
const fs = require('fs-extra');
|
|
872
|
-
if (await fs.pathExists(this.options.configPath)) {
|
|
873
|
-
this.manifestLoader = new ManifestLoader(this.options.configPath);
|
|
874
|
-
await this.manifestLoader.load();
|
|
875
|
-
// Update services with manifest loader
|
|
876
|
-
this.inferenceEngine = new InferenceEngine(this.docService, this.manifestLoader);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
838
|
// Initialize services
|
|
880
839
|
await this.docService.initialize();
|
|
881
840
|
await this.inferenceEngine.initialize();
|
|
@@ -886,13 +845,9 @@ class DocsServer {
|
|
|
886
845
|
|
|
887
846
|
if (this.options.verbose) {
|
|
888
847
|
console.error('🔧 Server initialized with MCP transport');
|
|
889
|
-
|
|
890
|
-
console.error('📄 Using manifest.json for additional configuration');
|
|
891
|
-
} else {
|
|
892
|
-
console.error('🚀 Using frontmatter-based configuration (no manifest needed)');
|
|
893
|
-
}
|
|
848
|
+
console.error('🚀 Using frontmatter-based configuration');
|
|
894
849
|
}
|
|
895
850
|
}
|
|
896
851
|
}
|
|
897
852
|
|
|
898
|
-
|
|
853
|
+
export { DocsServer };
|
package/src/index.test.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { DocumentationService } from './services/DocumentationService.js';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
4
9
|
|
|
5
10
|
describe('DocumentationService get_document_index functionality', () => {
|
|
6
11
|
let docService;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
5
|
|
|
6
6
|
class DocumentationService {
|
|
7
7
|
constructor(docsPath, manifestLoader = null) {
|
|
@@ -326,4 +326,4 @@ class DocumentationService {
|
|
|
326
326
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
export { DocumentationService };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { DocumentIndex } from './DocumentIndex.js';
|
|
3
3
|
|
|
4
4
|
class InferenceEngine {
|
|
5
|
-
constructor(documentationService
|
|
5
|
+
constructor(documentationService) {
|
|
6
6
|
this.docService = documentationService;
|
|
7
|
-
this.manifestLoader = manifestLoader;
|
|
8
7
|
this.documentIndex = new DocumentIndex();
|
|
9
8
|
this.isIndexBuilt = false;
|
|
10
9
|
}
|
|
@@ -106,52 +105,13 @@ class InferenceEngine {
|
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
async getDocsByKeywords(query) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const manifest = await this.manifestLoader.load();
|
|
114
|
-
const keywords = manifest.inference?.keywords || {};
|
|
115
|
-
|
|
116
|
-
const docs = [];
|
|
117
|
-
const queryLower = query.toLowerCase();
|
|
118
|
-
|
|
119
|
-
for (const [keyword, docPaths] of Object.entries(keywords)) {
|
|
120
|
-
if (queryLower.includes(keyword.toLowerCase())) {
|
|
121
|
-
for (const docPath of docPaths) {
|
|
122
|
-
const doc = this.docService.getDocument(docPath);
|
|
123
|
-
if (doc) {
|
|
124
|
-
docs.push(doc);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return docs;
|
|
108
|
+
// Now handled by DocumentIndex
|
|
109
|
+
return [];
|
|
131
110
|
}
|
|
132
111
|
|
|
133
112
|
async getDocsByPatterns(codeSnippet) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const manifest = await this.manifestLoader.load();
|
|
139
|
-
const patterns = manifest.inference?.patterns || {};
|
|
140
|
-
|
|
141
|
-
const docs = [];
|
|
142
|
-
|
|
143
|
-
for (const [pattern, docPaths] of Object.entries(patterns)) {
|
|
144
|
-
if (codeSnippet.includes(pattern)) {
|
|
145
|
-
for (const docPath of docPaths) {
|
|
146
|
-
const doc = this.docService.getDocument(docPath);
|
|
147
|
-
if (doc) {
|
|
148
|
-
docs.push(doc);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return docs;
|
|
113
|
+
// Now handled by DocumentIndex
|
|
114
|
+
return [];
|
|
155
115
|
}
|
|
156
116
|
|
|
157
117
|
async getDocsByFileExtension(filePath) {
|
|
@@ -245,4 +205,4 @@ class InferenceEngine {
|
|
|
245
205
|
}
|
|
246
206
|
}
|
|
247
207
|
|
|
248
|
-
|
|
208
|
+
export { InferenceEngine };
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { InferenceEngine } from '../InferenceEngine.js';
|
|
3
|
+
import { DocumentationService } from '../DocumentationService.js';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { dirname } from 'path';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
6
11
|
|
|
7
12
|
describe('InferenceEngine Integration', () => {
|
|
8
13
|
let inferenceEngine;
|
|
9
14
|
let mockDocService;
|
|
10
|
-
let mockManifestLoader;
|
|
11
15
|
let tempDir;
|
|
12
16
|
|
|
13
17
|
beforeEach(async () => {
|
|
@@ -109,19 +113,7 @@ Building REST APIs with Express.js.
|
|
|
109
113
|
getContextualDocs: jest.fn().mockResolvedValue([])
|
|
110
114
|
};
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
mockManifestLoader = {
|
|
114
|
-
load: jest.fn().mockResolvedValue({
|
|
115
|
-
globalRules: [],
|
|
116
|
-
contextualRules: {},
|
|
117
|
-
inference: {
|
|
118
|
-
keywords: {},
|
|
119
|
-
patterns: {}
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
inferenceEngine = new InferenceEngine(mockDocService, mockManifestLoader);
|
|
116
|
+
inferenceEngine = new InferenceEngine(mockDocService);
|
|
125
117
|
await inferenceEngine.initialize();
|
|
126
118
|
});
|
|
127
119
|
|
|
@@ -257,7 +249,7 @@ Building REST APIs with Express.js.
|
|
|
257
249
|
getContextualDocs: jest.fn().mockResolvedValue([])
|
|
258
250
|
};
|
|
259
251
|
|
|
260
|
-
const fallbackEngine = new InferenceEngine(failingDocService
|
|
252
|
+
const fallbackEngine = new InferenceEngine(failingDocService);
|
|
261
253
|
await fallbackEngine.initialize();
|
|
262
254
|
|
|
263
255
|
expect(fallbackEngine.isIndexBuilt).toBe(false);
|
|
@@ -1,135 +0,0 @@
|
|
|
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 };
|