@afterxleep/doc-bot 1.7.10 → 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.
@@ -53,6 +53,15 @@ Blocking: FALSE
53
53
  Timeout: 15s
54
54
  ```
55
55
 
56
+ **CLASS E: DOCUMENTATION_CREATION**
57
+ ```
58
+ Trigger Pattern: /\b(document|create rule|add rule|save knowledge|capture pattern|remember|note this|new rule)\b/i
59
+ Context Pattern: /\b(we should document|this should be a rule|add to documentation|create guideline)\b/i
60
+ Required Action: MANDATORY_CALL(create_or_update_rule, extract_rule_params())
61
+ Blocking: FALSE
62
+ Timeout: 30s
63
+ ```
64
+
56
65
  **Note**: For file-specific queries like "working on src/utils.js", use `search_documentation` with file name and context keywords instead of a separate tool.
57
66
 
58
67
  ## PHASE 3: EXECUTION ENFORCEMENT
@@ -126,6 +135,7 @@ IF response_generated() WITHOUT tool_execution():
126
135
  - "How do I implement auth?" → search_documentation("authentication")
127
136
  - "Create a new component" → check_project_rules("create component")
128
137
  - "Working on src/utils.js" → search_documentation("src/utils.js utils")
138
+ - "We should document this pattern" → create_or_update_rule({fileName: "pattern-name.md", title: "Pattern Name", content: "...", alwaysApply: false})
129
139
 
130
140
  **Expected Behavior**: Tool execution within 2 seconds, no general knowledge responses.
131
141
 
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
  }
@@ -134,6 +134,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
134
134
  4. **Ensure Agent Compliance** (Essential): Add the expert-engineered integration protocol to guarantee your agent uses doc-bot:
135
135
 
136
136
  **⚡ Setup**: Copy the rule from [`AGENT_INTEGRATION_RULE.txt`](./AGENT_INTEGRATION_RULE.txt) into your agent configuration.
137
+ **🎯 Why This Matters**: Without this rule, agents may default to general knowledge instead of your doc-bot documentation.
137
138
 
138
139
  **Platform-Specific Instructions**:
139
140
  - **Claude Code**: Add rule to your global `CLAUDE.md`
@@ -141,7 +142,7 @@ Traditional AI assistants use static rule files (like Cursor Rules or Copilot's
141
142
  - **GitHub Copilot**: Add rule to `.github/copilot-instructions.md`
142
143
  - **Continue.dev**: Add rule to system prompt configuration
143
144
 
144
- **🎯 Why This Matters**: Without this rule, agents may default to general knowledge instead of your doc-bot documentation.
145
+
145
146
 
146
147
  ## How to organize your documentation
147
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afterxleep/doc-bot",
3
- "version": "1.7.10",
3
+ "version": "1.8.0",
4
4
  "description": "Generic MCP server for intelligent documentation access in any project",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -14,6 +14,8 @@
14
14
  | `search_documentation` | Project-specific questions | Feature/architecture queries | Execute for project context |
15
15
  | `get_global_rules` | Documentation discovery | Rule/capability queries | Execute for overview |
16
16
  | `read_specific_document` | Document access | Full content needs | Execute after search results |
17
+ | `create_or_update_rule` | Rule creation/learning | New knowledge capture | Execute to document patterns/rules |
18
+ | `refresh_documentation` | Manual refresh | File detection issues | Execute after manual file additions |
17
19
 
18
20
  ## KEYWORD MAPPING:
19
21
 
@@ -35,6 +37,16 @@
35
37
  **Keywords**: working on, this file, specific file, directory, component path
36
38
  **Action**: Execute `search_documentation` with file name and context keywords
37
39
 
40
+ ### Documentation Creation Triggers:
41
+ **Keywords**: document, create rule, add rule, save knowledge, capture pattern, remember, note this, new rule
42
+ **Context Indicators**: "we should document", "this should be a rule", "add to documentation", "create guideline"
43
+ **Action**: Execute `create_or_update_rule` to capture new knowledge
44
+
45
+ ### Documentation Refresh Triggers:
46
+ **Keywords**: refresh, reload, update index, detect files, manual files, can't find, document not found
47
+ **Context Indicators**: "added files manually", "files not showing up", "refresh documentation", "reload docs"
48
+ **Action**: Execute `refresh_documentation` to reindex files
49
+
38
50
  ## EXECUTION ALGORITHM:
39
51
 
40
52
  1. **Input Analysis**: Extract keywords and context from user query
@@ -73,6 +85,16 @@ Input: "I'm working on src/components/Header.js"
73
85
  Analysis: Contains "working on" + file path (file context trigger)
74
86
  Action: search_documentation("src/components/Header.js component")
75
87
  Reason: File-specific context search requirement
88
+
89
+ Input: "We should document this pattern - always use TypeScript interfaces for API responses"
90
+ Analysis: Contains "should document" + "pattern" (documentation creation trigger)
91
+ Action: create_or_update_rule({fileName: "api-patterns.md", title: "API Response Patterns", content: "Always use TypeScript interfaces for API responses", alwaysApply: true})
92
+ Reason: New knowledge capture requirement
93
+
94
+ Input: "I added files manually but they're not showing up in search"
95
+ Analysis: Contains "added files manually" + "not showing up" (refresh trigger)
96
+ Action: refresh_documentation()
97
+ Reason: Manual file detection issue
76
98
  ```
77
99
 
78
100
  ## COMPLIANCE PROTOCOL:
package/src/index.js CHANGED
@@ -223,6 +223,59 @@ class DocsServer {
223
223
  },
224
224
  required: ['fileName']
225
225
  }
226
+ },
227
+ {
228
+ name: 'create_or_update_rule',
229
+ description: 'Create a new documentation rule or update an existing one. Use this to add new project knowledge or update existing documentation based on learnings.',
230
+ inputSchema: {
231
+ type: 'object',
232
+ properties: {
233
+ fileName: {
234
+ type: 'string',
235
+ description: 'File name for the rule (e.g., "react-patterns.md"). If file exists, it will be updated.'
236
+ },
237
+ title: {
238
+ type: 'string',
239
+ description: 'Title of the documentation rule'
240
+ },
241
+ description: {
242
+ type: 'string',
243
+ description: 'Brief description of what this rule covers'
244
+ },
245
+ keywords: {
246
+ type: 'array',
247
+ items: { type: 'string' },
248
+ description: 'Keywords for search indexing (e.g., ["react", "patterns", "components"])'
249
+ },
250
+ alwaysApply: {
251
+ type: 'boolean',
252
+ description: 'Whether this rule should always apply (true for global rules, false for contextual)'
253
+ },
254
+ content: {
255
+ type: 'string',
256
+ description: 'The markdown content of the rule'
257
+ }
258
+ },
259
+ required: ['fileName', 'title', 'content', 'alwaysApply']
260
+ }
261
+ },
262
+ {
263
+ name: 'refresh_documentation',
264
+ description: 'Manually refresh the documentation index to detect new or changed files. Use this after manually adding files to the docs folder.',
265
+ inputSchema: {
266
+ type: 'object',
267
+ properties: {},
268
+ additionalProperties: false
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
+ }
226
279
  }
227
280
  ]
228
281
  };
@@ -294,6 +347,50 @@ class DocsServer {
294
347
  text: await this.formatSingleDocument(doc)
295
348
  }]
296
349
  };
350
+
351
+ case 'create_or_update_rule':
352
+ const { fileName: ruleFileName, title, description, keywords, alwaysApply, content } = args || {};
353
+
354
+ if (!ruleFileName || !title || !content || alwaysApply === undefined) {
355
+ throw new Error('fileName, title, content, and alwaysApply parameters are required');
356
+ }
357
+
358
+ const result = await this.createOrUpdateRule({
359
+ fileName: ruleFileName,
360
+ title,
361
+ description,
362
+ keywords,
363
+ alwaysApply,
364
+ content
365
+ });
366
+
367
+ return {
368
+ content: [{
369
+ type: 'text',
370
+ text: result
371
+ }]
372
+ };
373
+
374
+ case 'refresh_documentation':
375
+ await this.docService.reload();
376
+ const docCount = this.docService.documents.size;
377
+
378
+ return {
379
+ content: [{
380
+ type: 'text',
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.`
382
+ }]
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
+ };
297
394
 
298
395
  default:
299
396
  throw new Error(`Unknown tool: ${name}`);
@@ -476,6 +573,80 @@ class DocsServer {
476
573
  return output;
477
574
  }
478
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
+
601
+ async createOrUpdateRule({ fileName, title, description, keywords, alwaysApply, content }) {
602
+ const fs = require('fs-extra');
603
+ const path = require('path');
604
+
605
+ try {
606
+ // Ensure the docs directory exists
607
+ await fs.ensureDir(this.options.docsPath);
608
+
609
+ // Create the full file path
610
+ const filePath = path.join(this.options.docsPath, fileName);
611
+
612
+ // Build frontmatter
613
+ let frontmatter = '---\n';
614
+ frontmatter += `alwaysApply: ${alwaysApply}\n`;
615
+ frontmatter += `title: "${title}"\n`;
616
+ if (description) {
617
+ frontmatter += `description: "${description}"\n`;
618
+ }
619
+ if (keywords && keywords.length > 0) {
620
+ frontmatter += `keywords: [${keywords.map(k => `"${k}"`).join(', ')}]\n`;
621
+ }
622
+ frontmatter += '---\n\n';
623
+
624
+ // Combine frontmatter and content
625
+ const fullContent = frontmatter + content;
626
+
627
+ // Check if file exists to determine if this is create or update
628
+ const fileExists = await fs.pathExists(filePath);
629
+ const action = fileExists ? 'updated' : 'created';
630
+
631
+ // Write the file
632
+ await fs.writeFile(filePath, fullContent, 'utf8');
633
+
634
+ // Reload the documentation service to pick up the new/updated file
635
+ await this.docService.reload();
636
+
637
+ return `✅ Documentation rule ${action} successfully: ${fileName}\n\n` +
638
+ `**Title**: ${title}\n` +
639
+ `**Type**: ${alwaysApply ? 'Global Rule (always applies)' : 'Contextual Rule (applies when relevant)'}\n` +
640
+ `**File**: ${fileName}\n` +
641
+ (description ? `**Description**: ${description}\n` : '') +
642
+ (keywords && keywords.length > 0 ? `**Keywords**: ${keywords.join(', ')}\n` : '') +
643
+ `\n**Content**:\n${content}`;
644
+
645
+ } catch (error) {
646
+ throw new Error(`Failed to ${fileName.includes('/') ? 'create' : 'update'} rule: ${error.message}`);
647
+ }
648
+ }
649
+
479
650
  async generateSystemPrompt() {
480
651
  const globalRules = await this.docService.getGlobalRules();
481
652
  const allDocs = await this.docService.getAllDocuments();
@@ -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 };