@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.
- package/AGENT_INTEGRATION_RULE.txt +10 -0
- package/README.md +5 -4
- package/package.json +1 -1
- package/prompts/system-prompt.txt +22 -0
- package/src/index.js +171 -0
- package/src/index.test.js +109 -0
- package/src/services/DocumentationService.js +16 -0
|
@@ -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
|
-
|
|
145
|
+
|
|
145
146
|
|
|
146
147
|
## How to organize your documentation
|
|
147
148
|
|
package/package.json
CHANGED
|
@@ -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 };
|