@afterxleep/doc-bot 1.7.11 → 1.8.1
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 +47 -31
- package/bin/doc-bot.js +12 -27
- package/package.json +7 -2
- package/src/index.js +65 -66
- package/src/index.test.js +114 -0
- package/src/services/DocumentIndex.js +1 -1
- package/src/services/DocumentationService.js +21 -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
|
|
|
@@ -99,7 +95,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
99
95
|
"mcpServers": {
|
|
100
96
|
"doc-bot": {
|
|
101
97
|
"command": "npx",
|
|
102
|
-
"args": ["@afterxleep/doc-bot"]
|
|
98
|
+
"args": ["@afterxleep/doc-bot@latest"]
|
|
103
99
|
}
|
|
104
100
|
}
|
|
105
101
|
}
|
|
@@ -111,11 +107,13 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
111
107
|
"mcpServers": {
|
|
112
108
|
"doc-bot": {
|
|
113
109
|
"command": "npx",
|
|
114
|
-
"args": ["@afterxleep/doc-bot", "--docs", "./my-custom-docs"]
|
|
110
|
+
"args": ["@afterxleep/doc-bot@latest", "--docs", "./my-custom-docs"]
|
|
115
111
|
}
|
|
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
|
|
@@ -123,7 +121,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
123
121
|
"mcpServers": {
|
|
124
122
|
"doc-bot": {
|
|
125
123
|
"command": "npx",
|
|
126
|
-
"args": ["@afterxleep/doc-bot", "--verbose"]
|
|
124
|
+
"args": ["@afterxleep/doc-bot@latest", "--verbose"]
|
|
127
125
|
}
|
|
128
126
|
}
|
|
129
127
|
}
|
|
@@ -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.8.1",
|
|
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 {
|
|
@@ -267,6 +243,15 @@ class DocsServer {
|
|
|
267
243
|
properties: {},
|
|
268
244
|
additionalProperties: false
|
|
269
245
|
}
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'get_document_index',
|
|
249
|
+
description: 'Get an index of all documents in the store with title, description, and last updated date.',
|
|
250
|
+
inputSchema: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {},
|
|
253
|
+
additionalProperties: false
|
|
254
|
+
}
|
|
270
255
|
}
|
|
271
256
|
]
|
|
272
257
|
};
|
|
@@ -372,6 +357,16 @@ class DocsServer {
|
|
|
372
357
|
text: `✅ Documentation refreshed successfully!\n\n**Files indexed:** ${docCount}\n**Last updated:** ${new Date().toLocaleString()}\n\n💡 All manually added files should now be available for search and reading.`
|
|
373
358
|
}]
|
|
374
359
|
};
|
|
360
|
+
|
|
361
|
+
case 'get_document_index':
|
|
362
|
+
const documentIndex = await this.docService.getDocumentIndex();
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
content: [{
|
|
366
|
+
type: 'text',
|
|
367
|
+
text: await this.formatDocumentIndex(documentIndex)
|
|
368
|
+
}]
|
|
369
|
+
};
|
|
375
370
|
|
|
376
371
|
default:
|
|
377
372
|
throw new Error(`Unknown tool: ${name}`);
|
|
@@ -388,7 +383,7 @@ class DocsServer {
|
|
|
388
383
|
}
|
|
389
384
|
|
|
390
385
|
setupWatcher() {
|
|
391
|
-
const watcher = chokidar.watch(
|
|
386
|
+
const watcher = chokidar.watch(this.options.docsPath, {
|
|
392
387
|
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
|
393
388
|
persistent: true
|
|
394
389
|
});
|
|
@@ -398,15 +393,8 @@ class DocsServer {
|
|
|
398
393
|
console.error(`📄 Documentation updated: ${path.relative(process.cwd(), filePath)}`);
|
|
399
394
|
}
|
|
400
395
|
|
|
401
|
-
// Reload manifest if config changed
|
|
402
|
-
if (filePath === this.options.configPath) {
|
|
403
|
-
await this.manifestLoader.reload();
|
|
404
|
-
}
|
|
405
|
-
|
|
406
396
|
// Reload docs if documentation changed
|
|
407
|
-
|
|
408
|
-
await this.docService.reload();
|
|
409
|
-
}
|
|
397
|
+
await this.docService.reload();
|
|
410
398
|
});
|
|
411
399
|
}
|
|
412
400
|
|
|
@@ -554,13 +542,37 @@ class DocsServer {
|
|
|
554
542
|
return output;
|
|
555
543
|
}
|
|
556
544
|
|
|
545
|
+
async formatDocumentIndex(documentIndex) {
|
|
546
|
+
if (!documentIndex || documentIndex.length === 0) {
|
|
547
|
+
return 'No documents found in the store.';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
let output = '# Document Index\n\n';
|
|
551
|
+
output += `Found ${documentIndex.length} document(s) in the store:\n\n`;
|
|
552
|
+
|
|
553
|
+
documentIndex.forEach((doc, index) => {
|
|
554
|
+
output += `## ${index + 1}. ${doc.title}\n\n`;
|
|
555
|
+
output += `**File:** ${doc.fileName}\n`;
|
|
556
|
+
|
|
557
|
+
if (doc.description) {
|
|
558
|
+
output += `**Description:** ${doc.description}\n`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
output += `**Last Updated:** ${new Date(doc.lastUpdated).toLocaleString()}\n\n`;
|
|
562
|
+
output += '---\n\n';
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
output += '💡 **Next Steps:** Use the `read_specific_document` tool with the file name to get the full content of any document above.\n';
|
|
566
|
+
|
|
567
|
+
return output;
|
|
568
|
+
}
|
|
569
|
+
|
|
557
570
|
async createOrUpdateRule({ fileName, title, description, keywords, alwaysApply, content }) {
|
|
558
|
-
const
|
|
559
|
-
const path = require('path');
|
|
571
|
+
const { default: fsExtra } = await import('fs-extra');
|
|
560
572
|
|
|
561
573
|
try {
|
|
562
574
|
// Ensure the docs directory exists
|
|
563
|
-
await
|
|
575
|
+
await fsExtra.ensureDir(this.options.docsPath);
|
|
564
576
|
|
|
565
577
|
// Create the full file path
|
|
566
578
|
const filePath = path.join(this.options.docsPath, fileName);
|
|
@@ -581,7 +593,7 @@ class DocsServer {
|
|
|
581
593
|
const fullContent = frontmatter + content;
|
|
582
594
|
|
|
583
595
|
// Check if file exists to determine if this is create or update
|
|
584
|
-
const fileExists = await
|
|
596
|
+
const fileExists = await fsExtra.pathExists(filePath);
|
|
585
597
|
const action = fileExists ? 'updated' : 'created';
|
|
586
598
|
|
|
587
599
|
// Write the file
|
|
@@ -823,15 +835,6 @@ class DocsServer {
|
|
|
823
835
|
}
|
|
824
836
|
|
|
825
837
|
async start() {
|
|
826
|
-
// Initialize manifest loader if manifest exists (backward compatibility)
|
|
827
|
-
const fs = require('fs-extra');
|
|
828
|
-
if (await fs.pathExists(this.options.configPath)) {
|
|
829
|
-
this.manifestLoader = new ManifestLoader(this.options.configPath);
|
|
830
|
-
await this.manifestLoader.load();
|
|
831
|
-
// Update services with manifest loader
|
|
832
|
-
this.inferenceEngine = new InferenceEngine(this.docService, this.manifestLoader);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
838
|
// Initialize services
|
|
836
839
|
await this.docService.initialize();
|
|
837
840
|
await this.inferenceEngine.initialize();
|
|
@@ -842,13 +845,9 @@ class DocsServer {
|
|
|
842
845
|
|
|
843
846
|
if (this.options.verbose) {
|
|
844
847
|
console.error('🔧 Server initialized with MCP transport');
|
|
845
|
-
|
|
846
|
-
console.error('📄 Using manifest.json for additional configuration');
|
|
847
|
-
} else {
|
|
848
|
-
console.error('🚀 Using frontmatter-based configuration (no manifest needed)');
|
|
849
|
-
}
|
|
848
|
+
console.error('🚀 Using frontmatter-based configuration');
|
|
850
849
|
}
|
|
851
850
|
}
|
|
852
851
|
}
|
|
853
852
|
|
|
854
|
-
|
|
853
|
+
export { DocsServer };
|
|
@@ -0,0 +1,114 @@
|
|
|
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);
|
|
9
|
+
|
|
10
|
+
describe('DocumentationService get_document_index functionality', () => {
|
|
11
|
+
let docService;
|
|
12
|
+
let tempDocsPath;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
// Create a temporary directory for test documents
|
|
16
|
+
tempDocsPath = path.join(__dirname, '../test-docs');
|
|
17
|
+
await fs.ensureDir(tempDocsPath);
|
|
18
|
+
|
|
19
|
+
// Create test documents
|
|
20
|
+
const testDocs = [
|
|
21
|
+
{
|
|
22
|
+
fileName: 'react-guide.md',
|
|
23
|
+
content: '---\nalwaysApply: false\ntitle: "React Component Guide"\ndescription: "Learn how to build React components"\nkeywords: ["react", "components", "jsx"]\n---\n\n# React Components\n\nThis guide covers React components.'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
fileName: 'testing-standards.md',
|
|
27
|
+
content: '---\nalwaysApply: true\ntitle: "Testing Standards"\ndescription: "Project testing requirements"\nkeywords: ["testing", "jest", "standards"]\n---\n\n# Testing Standards\n\nAll code must have tests.'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
fileName: 'api-design.md',
|
|
31
|
+
content: '---\nalwaysApply: false\ntitle: "API Design Guidelines"\ndescription: "REST API design patterns"\nkeywords: ["api", "rest", "design"]\n---\n\n# API Design\n\nFollow REST principles.'
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// Write test documents to temp directory
|
|
36
|
+
for (const doc of testDocs) {
|
|
37
|
+
await fs.writeFile(path.join(tempDocsPath, doc.fileName), doc.content);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create DocumentationService instance
|
|
41
|
+
docService = new DocumentationService(tempDocsPath);
|
|
42
|
+
await docService.initialize();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(async () => {
|
|
46
|
+
// Clean up temporary directory
|
|
47
|
+
await fs.remove(tempDocsPath);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('getDocumentIndex method', () => {
|
|
51
|
+
it('should be implemented and return document index', async () => {
|
|
52
|
+
expect(typeof docService.getDocumentIndex).toBe('function');
|
|
53
|
+
|
|
54
|
+
const index = await docService.getDocumentIndex();
|
|
55
|
+
|
|
56
|
+
expect(Array.isArray(index)).toBe(true);
|
|
57
|
+
expect(index.length).toBe(3);
|
|
58
|
+
|
|
59
|
+
// Check that each document has required fields
|
|
60
|
+
index.forEach(doc => {
|
|
61
|
+
expect(doc).toHaveProperty('title');
|
|
62
|
+
expect(doc).toHaveProperty('description');
|
|
63
|
+
expect(doc).toHaveProperty('fileName');
|
|
64
|
+
expect(doc).toHaveProperty('lastUpdated');
|
|
65
|
+
expect(typeof doc.lastUpdated).toBe('string');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should return documents sorted by title', async () => {
|
|
70
|
+
const index = await docService.getDocumentIndex();
|
|
71
|
+
|
|
72
|
+
// Should be sorted alphabetically by title
|
|
73
|
+
expect(index[0].title).toBe('API Design Guidelines');
|
|
74
|
+
expect(index[1].title).toBe('React Component Guide');
|
|
75
|
+
expect(index[2].title).toBe('Testing Standards');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should include metadata from frontmatter', async () => {
|
|
79
|
+
const index = await docService.getDocumentIndex();
|
|
80
|
+
|
|
81
|
+
const reactDoc = index.find(doc => doc.fileName === 'react-guide.md');
|
|
82
|
+
expect(reactDoc.title).toBe('React Component Guide');
|
|
83
|
+
expect(reactDoc.description).toBe('Learn how to build React components');
|
|
84
|
+
|
|
85
|
+
const testingDoc = index.find(doc => doc.fileName === 'testing-standards.md');
|
|
86
|
+
expect(testingDoc.title).toBe('Testing Standards');
|
|
87
|
+
expect(testingDoc.description).toBe('Project testing requirements');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should use file name as title when no title in metadata', async () => {
|
|
91
|
+
// Create a document without title metadata
|
|
92
|
+
const docWithoutTitle = '---\ndescription: "A document without title"\n---\n\nSome content';
|
|
93
|
+
await fs.writeFile(path.join(tempDocsPath, 'no-title.md'), docWithoutTitle);
|
|
94
|
+
|
|
95
|
+
// Reload documents to pick up the new file
|
|
96
|
+
await docService.reload();
|
|
97
|
+
|
|
98
|
+
const index = await docService.getDocumentIndex();
|
|
99
|
+
const noTitleDoc = index.find(doc => doc.fileName === 'no-title.md');
|
|
100
|
+
|
|
101
|
+
expect(noTitleDoc.title).toBe('no-title.md');
|
|
102
|
+
expect(noTitleDoc.description).toBe('A document without title');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should handle empty description gracefully', async () => {
|
|
106
|
+
const index = await docService.getDocumentIndex();
|
|
107
|
+
|
|
108
|
+
// All test documents should have descriptions, but let's test the structure
|
|
109
|
+
index.forEach(doc => {
|
|
110
|
+
expect(typeof doc.description).toBe('string');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -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) {
|
|
@@ -308,6 +308,22 @@ class DocumentationService {
|
|
|
308
308
|
return results;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
async getDocumentIndex() {
|
|
312
|
+
const index = [];
|
|
313
|
+
|
|
314
|
+
for (const doc of this.documents.values()) {
|
|
315
|
+
index.push({
|
|
316
|
+
title: doc.metadata?.title || doc.fileName,
|
|
317
|
+
description: doc.metadata?.description || '',
|
|
318
|
+
fileName: doc.fileName,
|
|
319
|
+
lastUpdated: doc.lastModified.toISOString()
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Sort by title for consistent ordering
|
|
324
|
+
return index.sort((a, b) => a.title.localeCompare(b.title));
|
|
325
|
+
}
|
|
326
|
+
|
|
311
327
|
}
|
|
312
328
|
|
|
313
|
-
|
|
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 };
|