@afterxleep/doc-bot 1.7.11 ā 1.8.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 +3 -3
- package/package.json +1 -1
- package/src/index.js +44 -0
- package/src/index.test.js +109 -0
- package/src/services/DocumentationService.js +16 -0
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
99
99
|
"mcpServers": {
|
|
100
100
|
"doc-bot": {
|
|
101
101
|
"command": "npx",
|
|
102
|
-
"args": ["@afterxleep/doc-bot"]
|
|
102
|
+
"args": ["@afterxleep/doc-bot@latest"]
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -111,7 +111,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
111
111
|
"mcpServers": {
|
|
112
112
|
"doc-bot": {
|
|
113
113
|
"command": "npx",
|
|
114
|
-
"args": ["@afterxleep/doc-bot", "--docs", "./my-custom-docs"]
|
|
114
|
+
"args": ["@afterxleep/doc-bot@latest", "--docs", "./my-custom-docs"]
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -123,7 +123,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
|
|
|
123
123
|
"mcpServers": {
|
|
124
124
|
"doc-bot": {
|
|
125
125
|
"command": "npx",
|
|
126
|
-
"args": ["@afterxleep/doc-bot", "--verbose"]
|
|
126
|
+
"args": ["@afterxleep/doc-bot@latest", "--verbose"]
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
}
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -267,6 +267,15 @@ class DocsServer {
|
|
|
267
267
|
properties: {},
|
|
268
268
|
additionalProperties: false
|
|
269
269
|
}
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'get_document_index',
|
|
273
|
+
description: 'Get an index of all documents in the store with title, description, and last updated date.',
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: 'object',
|
|
276
|
+
properties: {},
|
|
277
|
+
additionalProperties: false
|
|
278
|
+
}
|
|
270
279
|
}
|
|
271
280
|
]
|
|
272
281
|
};
|
|
@@ -372,6 +381,16 @@ class DocsServer {
|
|
|
372
381
|
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
382
|
}]
|
|
374
383
|
};
|
|
384
|
+
|
|
385
|
+
case 'get_document_index':
|
|
386
|
+
const documentIndex = await this.docService.getDocumentIndex();
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
content: [{
|
|
390
|
+
type: 'text',
|
|
391
|
+
text: await this.formatDocumentIndex(documentIndex)
|
|
392
|
+
}]
|
|
393
|
+
};
|
|
375
394
|
|
|
376
395
|
default:
|
|
377
396
|
throw new Error(`Unknown tool: ${name}`);
|
|
@@ -554,6 +573,31 @@ class DocsServer {
|
|
|
554
573
|
return output;
|
|
555
574
|
}
|
|
556
575
|
|
|
576
|
+
async formatDocumentIndex(documentIndex) {
|
|
577
|
+
if (!documentIndex || documentIndex.length === 0) {
|
|
578
|
+
return 'No documents found in the store.';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
let output = '# Document Index\n\n';
|
|
582
|
+
output += `Found ${documentIndex.length} document(s) in the store:\n\n`;
|
|
583
|
+
|
|
584
|
+
documentIndex.forEach((doc, index) => {
|
|
585
|
+
output += `## ${index + 1}. ${doc.title}\n\n`;
|
|
586
|
+
output += `**File:** ${doc.fileName}\n`;
|
|
587
|
+
|
|
588
|
+
if (doc.description) {
|
|
589
|
+
output += `**Description:** ${doc.description}\n`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
output += `**Last Updated:** ${new Date(doc.lastUpdated).toLocaleString()}\n\n`;
|
|
593
|
+
output += '---\n\n';
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
output += 'š” **Next Steps:** Use the `read_specific_document` tool with the file name to get the full content of any document above.\n';
|
|
597
|
+
|
|
598
|
+
return output;
|
|
599
|
+
}
|
|
600
|
+
|
|
557
601
|
async createOrUpdateRule({ fileName, title, description, keywords, alwaysApply, content }) {
|
|
558
602
|
const fs = require('fs-extra');
|
|
559
603
|
const path = require('path');
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { DocumentationService } = require('./services/DocumentationService');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
describe('DocumentationService get_document_index functionality', () => {
|
|
6
|
+
let docService;
|
|
7
|
+
let tempDocsPath;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
// Create a temporary directory for test documents
|
|
11
|
+
tempDocsPath = path.join(__dirname, '../test-docs');
|
|
12
|
+
await fs.ensureDir(tempDocsPath);
|
|
13
|
+
|
|
14
|
+
// Create test documents
|
|
15
|
+
const testDocs = [
|
|
16
|
+
{
|
|
17
|
+
fileName: 'react-guide.md',
|
|
18
|
+
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.'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
fileName: 'testing-standards.md',
|
|
22
|
+
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.'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
fileName: 'api-design.md',
|
|
26
|
+
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.'
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Write test documents to temp directory
|
|
31
|
+
for (const doc of testDocs) {
|
|
32
|
+
await fs.writeFile(path.join(tempDocsPath, doc.fileName), doc.content);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create DocumentationService instance
|
|
36
|
+
docService = new DocumentationService(tempDocsPath);
|
|
37
|
+
await docService.initialize();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
// Clean up temporary directory
|
|
42
|
+
await fs.remove(tempDocsPath);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('getDocumentIndex method', () => {
|
|
46
|
+
it('should be implemented and return document index', async () => {
|
|
47
|
+
expect(typeof docService.getDocumentIndex).toBe('function');
|
|
48
|
+
|
|
49
|
+
const index = await docService.getDocumentIndex();
|
|
50
|
+
|
|
51
|
+
expect(Array.isArray(index)).toBe(true);
|
|
52
|
+
expect(index.length).toBe(3);
|
|
53
|
+
|
|
54
|
+
// Check that each document has required fields
|
|
55
|
+
index.forEach(doc => {
|
|
56
|
+
expect(doc).toHaveProperty('title');
|
|
57
|
+
expect(doc).toHaveProperty('description');
|
|
58
|
+
expect(doc).toHaveProperty('fileName');
|
|
59
|
+
expect(doc).toHaveProperty('lastUpdated');
|
|
60
|
+
expect(typeof doc.lastUpdated).toBe('string');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return documents sorted by title', async () => {
|
|
65
|
+
const index = await docService.getDocumentIndex();
|
|
66
|
+
|
|
67
|
+
// Should be sorted alphabetically by title
|
|
68
|
+
expect(index[0].title).toBe('API Design Guidelines');
|
|
69
|
+
expect(index[1].title).toBe('React Component Guide');
|
|
70
|
+
expect(index[2].title).toBe('Testing Standards');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should include metadata from frontmatter', async () => {
|
|
74
|
+
const index = await docService.getDocumentIndex();
|
|
75
|
+
|
|
76
|
+
const reactDoc = index.find(doc => doc.fileName === 'react-guide.md');
|
|
77
|
+
expect(reactDoc.title).toBe('React Component Guide');
|
|
78
|
+
expect(reactDoc.description).toBe('Learn how to build React components');
|
|
79
|
+
|
|
80
|
+
const testingDoc = index.find(doc => doc.fileName === 'testing-standards.md');
|
|
81
|
+
expect(testingDoc.title).toBe('Testing Standards');
|
|
82
|
+
expect(testingDoc.description).toBe('Project testing requirements');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should use file name as title when no title in metadata', async () => {
|
|
86
|
+
// Create a document without title metadata
|
|
87
|
+
const docWithoutTitle = '---\ndescription: "A document without title"\n---\n\nSome content';
|
|
88
|
+
await fs.writeFile(path.join(tempDocsPath, 'no-title.md'), docWithoutTitle);
|
|
89
|
+
|
|
90
|
+
// Reload documents to pick up the new file
|
|
91
|
+
await docService.reload();
|
|
92
|
+
|
|
93
|
+
const index = await docService.getDocumentIndex();
|
|
94
|
+
const noTitleDoc = index.find(doc => doc.fileName === 'no-title.md');
|
|
95
|
+
|
|
96
|
+
expect(noTitleDoc.title).toBe('no-title.md');
|
|
97
|
+
expect(noTitleDoc.description).toBe('A document without title');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle empty description gracefully', async () => {
|
|
101
|
+
const index = await docService.getDocumentIndex();
|
|
102
|
+
|
|
103
|
+
// All test documents should have descriptions, but let's test the structure
|
|
104
|
+
index.forEach(doc => {
|
|
105
|
+
expect(typeof doc.description).toBe('string');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -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
|
module.exports = { DocumentationService };
|