@redpanda-data/docs-extensions-and-macros 4.13.6 → 4.14.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/bin/doc-tools-mcp.js +84 -131
- package/bin/doc-tools.js +105 -192
- package/bin/mcp-tools/generated-docs-review.js +11 -158
- package/bin/mcp-tools/index.js +0 -4
- package/bin/mcp-tools/mcp-validation.js +44 -149
- package/mcp/COSTS.adoc +7 -17
- package/mcp/DEVELOPMENT.adoc +2 -175
- package/mcp/README.adoc +27 -61
- package/mcp/WRITER_EXTENSION_GUIDE.adoc +128 -738
- package/package.json +1 -1
- package/bin/mcp-tools/content-review.js +0 -196
- package/bin/mcp-tools/frontmatter.js +0 -138
- package/bin/mcp-tools/prompt-discovery.js +0 -283
- package/mcp/prompts/README.adoc +0 -183
- package/mcp/prompts/property-docs-guide.md +0 -283
- package/mcp/prompts/review-for-style.md +0 -128
- package/mcp/prompts/rpcn-connector-docs-guide.md +0 -126
- package/mcp/prompts/write-new-guide.md +0 -222
- package/mcp/team-standards/style-guide.md +0 -321
- package/mcp/templates/README.adoc +0 -212
- package/mcp/templates/prompt-review-template.md +0 -80
- package/mcp/templates/prompt-write-template.md +0 -110
- package/mcp/templates/resource-template.md +0 -76
package/package.json
CHANGED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content Review Tool
|
|
3
|
-
*
|
|
4
|
-
* Provides comprehensive content review with automatic style guide context.
|
|
5
|
-
* This tool automatically fetches the style guide and provides review instructions,
|
|
6
|
-
* so the LLM has everything needed in a single call.
|
|
7
|
-
*
|
|
8
|
-
* OPTIMIZATION: Caches style guide and review instructions.
|
|
9
|
-
* - Style guide cached for 1 hour (rarely changes)
|
|
10
|
-
* - Review instructions cached permanently (static)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const cache = require('./cache');
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Review content with automatic style guide context
|
|
19
|
-
* @param {Object} args - Tool arguments
|
|
20
|
-
* @param {string} args.content - Content to review
|
|
21
|
-
* @param {string} [args.focus] - What to focus on (comprehensive, style, terminology, clarity)
|
|
22
|
-
* @param {string} [baseDir] - Base directory for finding resources
|
|
23
|
-
* @returns {Object} Review context including style guide
|
|
24
|
-
*/
|
|
25
|
-
function reviewContent(args, baseDir) {
|
|
26
|
-
if (!args || !args.content) {
|
|
27
|
-
return {
|
|
28
|
-
success: false,
|
|
29
|
-
error: 'Missing required parameter: content'
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const focus = args.focus || 'comprehensive';
|
|
34
|
-
|
|
35
|
-
// Validate focus parameter
|
|
36
|
-
const validFocus = ['comprehensive', 'style', 'terminology', 'clarity'];
|
|
37
|
-
if (!validFocus.includes(focus)) {
|
|
38
|
-
return {
|
|
39
|
-
success: false,
|
|
40
|
-
error: `Invalid focus parameter. Must be one of: ${validFocus.join(', ')}`
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Find base directory (either provided or from repo root)
|
|
45
|
-
const actualBaseDir = baseDir || path.join(__dirname, '../..');
|
|
46
|
-
|
|
47
|
-
// Load style guide resource (with caching)
|
|
48
|
-
const styleGuidePath = path.join(actualBaseDir, 'mcp', 'team-standards', 'style-guide.md');
|
|
49
|
-
const cacheKey = 'style-guide-content';
|
|
50
|
-
|
|
51
|
-
let styleGuide = cache.get(cacheKey);
|
|
52
|
-
|
|
53
|
-
if (!styleGuide) {
|
|
54
|
-
if (!fs.existsSync(styleGuidePath)) {
|
|
55
|
-
return {
|
|
56
|
-
success: false,
|
|
57
|
-
error: `Style guide not found at: ${styleGuidePath}`,
|
|
58
|
-
suggestion: 'Ensure the MCP server is running from the correct directory'
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
styleGuide = fs.readFileSync(styleGuidePath, 'utf8');
|
|
64
|
-
cache.set(cacheKey, styleGuide, 60 * 60 * 1000); // Cache for 1 hour
|
|
65
|
-
} catch (err) {
|
|
66
|
-
return {
|
|
67
|
-
success: false,
|
|
68
|
-
error: `Failed to read style guide: ${err.message}`
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Build review instructions based on focus (cached permanently as they're static)
|
|
74
|
-
const instructionsCacheKey = `review-instructions:${focus}`;
|
|
75
|
-
let instructions = cache.get(instructionsCacheKey);
|
|
76
|
-
|
|
77
|
-
if (!instructions) {
|
|
78
|
-
instructions = buildReviewInstructions(focus);
|
|
79
|
-
cache.set(instructionsCacheKey, instructions, 24 * 60 * 60 * 1000); // Cache for 24 hours
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Return the bundled context
|
|
83
|
-
return {
|
|
84
|
-
success: true,
|
|
85
|
-
message: 'Style guide loaded. Please review the content according to the instructions below.',
|
|
86
|
-
styleGuide,
|
|
87
|
-
instructions,
|
|
88
|
-
content: args.content,
|
|
89
|
-
focus,
|
|
90
|
-
// Provide formatted output that LLM can easily parse
|
|
91
|
-
reviewContext: formatReviewContext(styleGuide, instructions, args.content, focus)
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Build review instructions based on focus area
|
|
97
|
-
* @param {string} focus - What to focus on
|
|
98
|
-
* @returns {string} Review instructions
|
|
99
|
-
*/
|
|
100
|
-
function buildReviewInstructions(focus) {
|
|
101
|
-
const baseInstructions = `
|
|
102
|
-
# Content Review Instructions
|
|
103
|
-
|
|
104
|
-
You are reviewing documentation for the Redpanda documentation team.
|
|
105
|
-
|
|
106
|
-
The style guide has been automatically loaded for your reference.
|
|
107
|
-
|
|
108
|
-
## Your task
|
|
109
|
-
|
|
110
|
-
Review the provided content and provide detailed, actionable feedback.
|
|
111
|
-
`;
|
|
112
|
-
|
|
113
|
-
const focusInstructions = {
|
|
114
|
-
comprehensive: `
|
|
115
|
-
Review all aspects of the content:
|
|
116
|
-
|
|
117
|
-
1. **Style violations** - Check against the style guide (capitalization, formatting, structure)
|
|
118
|
-
2. **Terminology issues** - Verify correct usage of approved terms
|
|
119
|
-
3. **Voice and tone** - Ensure consistent, appropriate tone
|
|
120
|
-
4. **Clarity and readability** - Identify confusing or unclear sections
|
|
121
|
-
5. **Technical accuracy** - Flag any technical issues (if detectable)
|
|
122
|
-
6. **Formatting** - Check AsciiDoc formatting, code blocks, lists
|
|
123
|
-
7. **Accessibility** - Verify heading hierarchy, alt text, link text
|
|
124
|
-
|
|
125
|
-
Provide specific line numbers or sections for each issue found.
|
|
126
|
-
`,
|
|
127
|
-
style: `
|
|
128
|
-
Focus on style guide compliance:
|
|
129
|
-
|
|
130
|
-
1. **Formatting violations** - Capitalization, punctuation, spacing
|
|
131
|
-
2. **Structural issues** - Heading hierarchy, section organization
|
|
132
|
-
3. **Voice and tone** - Too formal, too casual, or inconsistent
|
|
133
|
-
4. **Style guide rules** - Any violations of documented standards
|
|
134
|
-
|
|
135
|
-
Reference specific style guide sections when identifying issues.
|
|
136
|
-
`,
|
|
137
|
-
terminology: `
|
|
138
|
-
Focus on terminology:
|
|
139
|
-
|
|
140
|
-
1. **Incorrect terms** - Wrong capitalization, spelling, or usage
|
|
141
|
-
2. **Deprecated terms** - Outdated terms that should be replaced
|
|
142
|
-
3. **Inconsistent usage** - Same concept referred to differently
|
|
143
|
-
4. **Missing approved terms** - Concepts that should use glossary terms
|
|
144
|
-
|
|
145
|
-
Check against the terminology section of the style guide.
|
|
146
|
-
`,
|
|
147
|
-
clarity: `
|
|
148
|
-
Focus on clarity and readability:
|
|
149
|
-
|
|
150
|
-
1. **Complex sentences** - Sentences that are too long or convoluted
|
|
151
|
-
2. **Unclear explanations** - Technical concepts that need more context
|
|
152
|
-
3. **Poor structure** - Content organization issues
|
|
153
|
-
4. **Missing context** - Assumptions about reader knowledge
|
|
154
|
-
5. **Confusing language** - Jargon without explanation
|
|
155
|
-
|
|
156
|
-
Suggest specific improvements for each issue.
|
|
157
|
-
`
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
return baseInstructions + (focusInstructions[focus] || focusInstructions.comprehensive);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Format the complete review context for the LLM
|
|
165
|
-
* @param {string} styleGuide - Style guide content
|
|
166
|
-
* @param {string} instructions - Review instructions
|
|
167
|
-
* @param {string} content - Content to review
|
|
168
|
-
* @param {string} focus - Focus area
|
|
169
|
-
* @returns {string} Formatted review context
|
|
170
|
-
*/
|
|
171
|
-
function formatReviewContext(styleGuide, instructions, content, focus) {
|
|
172
|
-
return `${instructions}
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
# Style Guide Reference
|
|
177
|
-
|
|
178
|
-
${styleGuide}
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
# Content to Review
|
|
183
|
-
|
|
184
|
-
${content}
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
**Focus Area**: ${focus}
|
|
189
|
-
|
|
190
|
-
Please provide your review above, organized by issue type with specific line/section references.
|
|
191
|
-
`;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
module.exports = {
|
|
195
|
-
reviewContent
|
|
196
|
-
};
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Frontmatter Parser for MCP Prompts
|
|
3
|
-
*
|
|
4
|
-
* Parses YAML frontmatter from markdown files to extract metadata.
|
|
5
|
-
* Supports validation against JSON schemas.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const yaml = require('js-yaml');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Parse frontmatter from markdown content
|
|
12
|
-
* @param {string} content - Markdown content with optional frontmatter
|
|
13
|
-
* @returns {{ metadata: Object, content: string }} Parsed frontmatter and remaining content
|
|
14
|
-
*/
|
|
15
|
-
function parseFrontmatter(content) {
|
|
16
|
-
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
|
|
17
|
-
const match = content.match(frontmatterRegex);
|
|
18
|
-
|
|
19
|
-
if (!match) {
|
|
20
|
-
// No frontmatter found - return empty metadata and full content
|
|
21
|
-
return {
|
|
22
|
-
metadata: {},
|
|
23
|
-
content: content
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const metadata = yaml.load(match[1]) || {};
|
|
29
|
-
const remainingContent = match[2];
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
metadata,
|
|
33
|
-
content: remainingContent
|
|
34
|
-
};
|
|
35
|
-
} catch (err) {
|
|
36
|
-
throw new Error(`Failed to parse YAML frontmatter: ${err.message}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* JSON Schema for prompt frontmatter
|
|
42
|
-
*/
|
|
43
|
-
const promptFrontmatterSchema = {
|
|
44
|
-
type: 'object',
|
|
45
|
-
properties: {
|
|
46
|
-
description: {
|
|
47
|
-
type: 'string',
|
|
48
|
-
minLength: 10,
|
|
49
|
-
description: 'Description of what this prompt does'
|
|
50
|
-
},
|
|
51
|
-
version: {
|
|
52
|
-
type: 'string',
|
|
53
|
-
pattern: '^\\d+\\.\\d+\\.\\d+$',
|
|
54
|
-
description: 'Semantic version (for example, 1.0.0)'
|
|
55
|
-
},
|
|
56
|
-
arguments: {
|
|
57
|
-
type: 'array',
|
|
58
|
-
description: 'Arguments this prompt accepts',
|
|
59
|
-
items: {
|
|
60
|
-
type: 'object',
|
|
61
|
-
required: ['name', 'description', 'required'],
|
|
62
|
-
properties: {
|
|
63
|
-
name: {
|
|
64
|
-
type: 'string',
|
|
65
|
-
pattern: '^[a-z_][a-z0-9_]*$',
|
|
66
|
-
description: 'Argument name (lowercase, underscores allowed)'
|
|
67
|
-
},
|
|
68
|
-
description: {
|
|
69
|
-
type: 'string',
|
|
70
|
-
minLength: 5,
|
|
71
|
-
description: 'What this argument is for'
|
|
72
|
-
},
|
|
73
|
-
required: {
|
|
74
|
-
type: 'boolean',
|
|
75
|
-
description: 'Whether this argument is required'
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
additionalProperties: false
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
argumentFormat: {
|
|
82
|
-
type: 'string',
|
|
83
|
-
enum: ['content-append', 'structured'],
|
|
84
|
-
description: 'How to format arguments when building the prompt'
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
additionalProperties: false
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Validate frontmatter against schema
|
|
92
|
-
* @param {Object} metadata - Parsed frontmatter metadata
|
|
93
|
-
* @param {string} filename - File name (for error messages)
|
|
94
|
-
* @param {Object} schema - JSON schema to validate against
|
|
95
|
-
* @throws {Error} If validation fails
|
|
96
|
-
*/
|
|
97
|
-
function validateFrontmatter(metadata, filename, schema = promptFrontmatterSchema) {
|
|
98
|
-
const Ajv = require('ajv');
|
|
99
|
-
const ajv = new Ajv({ allErrors: true });
|
|
100
|
-
|
|
101
|
-
const valid = ajv.validate(schema, metadata);
|
|
102
|
-
|
|
103
|
-
if (!valid) {
|
|
104
|
-
const errors = ajv.errors.map(err => {
|
|
105
|
-
const path = err.instancePath || 'root';
|
|
106
|
-
return ` - ${path}: ${err.message}`;
|
|
107
|
-
}).join('\n');
|
|
108
|
-
|
|
109
|
-
throw new Error(`Invalid frontmatter in ${filename}:\n${errors}`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Parse and validate prompt file
|
|
115
|
-
* @param {string} content - File content
|
|
116
|
-
* @param {string} filename - File name
|
|
117
|
-
* @returns {{ metadata: Object, content: string }} Validated metadata and content
|
|
118
|
-
*/
|
|
119
|
-
function parsePromptFile(content, filename) {
|
|
120
|
-
const { metadata, content: promptContent } = parseFrontmatter(content);
|
|
121
|
-
|
|
122
|
-
// If metadata exists, validate it
|
|
123
|
-
if (Object.keys(metadata).length > 0) {
|
|
124
|
-
validateFrontmatter(metadata, filename);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
metadata,
|
|
129
|
-
content: promptContent
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = {
|
|
134
|
-
parseFrontmatter,
|
|
135
|
-
parsePromptFile,
|
|
136
|
-
validateFrontmatter,
|
|
137
|
-
promptFrontmatterSchema
|
|
138
|
-
};
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt Discovery and Caching
|
|
3
|
-
*
|
|
4
|
-
* Automatically discovers prompts from the mcp/prompts directory.
|
|
5
|
-
* Caches prompt content and metadata for performance.
|
|
6
|
-
* Supports file watching in development mode.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const { parsePromptFile, validateFrontmatter } = require('./frontmatter');
|
|
12
|
-
const { validatePromptName } = require('./mcp-validation');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* In-memory prompt cache
|
|
16
|
-
*/
|
|
17
|
-
class PromptCache {
|
|
18
|
-
constructor() {
|
|
19
|
-
this.prompts = new Map(); // name -> { metadata, content, filePath }
|
|
20
|
-
this.watchers = [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Set prompt in cache
|
|
25
|
-
* @param {string} name - Prompt name
|
|
26
|
-
* @param {Object} data - Prompt data
|
|
27
|
-
*/
|
|
28
|
-
set(name, data) {
|
|
29
|
-
this.prompts.set(name, data);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get prompt from cache
|
|
34
|
-
* @param {string} name - Prompt name
|
|
35
|
-
* @returns {Object|null} Prompt data or null
|
|
36
|
-
*/
|
|
37
|
-
get(name) {
|
|
38
|
-
return this.prompts.get(name) || null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get all prompts
|
|
43
|
-
* @returns {Array} Array of prompt objects
|
|
44
|
-
*/
|
|
45
|
-
getAll() {
|
|
46
|
-
return Array.from(this.prompts.values());
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Get all prompt names
|
|
51
|
-
* @returns {Array} Array of prompt names
|
|
52
|
-
*/
|
|
53
|
-
getNames() {
|
|
54
|
-
return Array.from(this.prompts.keys());
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Check if prompt exists
|
|
59
|
-
* @param {string} name - Prompt name
|
|
60
|
-
* @returns {boolean}
|
|
61
|
-
*/
|
|
62
|
-
has(name) {
|
|
63
|
-
return this.prompts.has(name);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Clear all prompts
|
|
68
|
-
*/
|
|
69
|
-
clear() {
|
|
70
|
-
this.prompts.clear();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Stop all file watchers
|
|
75
|
-
*/
|
|
76
|
-
stopWatching() {
|
|
77
|
-
this.watchers.forEach(watcher => watcher.close());
|
|
78
|
-
this.watchers = [];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Add a file watcher
|
|
83
|
-
* @param {FSWatcher} watcher - File system watcher
|
|
84
|
-
*/
|
|
85
|
-
addWatcher(watcher) {
|
|
86
|
-
this.watchers.push(watcher);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Default argument formatters for different types
|
|
92
|
-
*/
|
|
93
|
-
const argumentFormatters = {
|
|
94
|
-
'content-append': (args, schema) => {
|
|
95
|
-
if (!args) return '';
|
|
96
|
-
|
|
97
|
-
let result = '\n\n---\n\n';
|
|
98
|
-
|
|
99
|
-
// If there's a 'content' argument, add it specially
|
|
100
|
-
if (args.content) {
|
|
101
|
-
result += `**Content to review:**\n\n${args.content}`;
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Otherwise, format all arguments
|
|
106
|
-
Object.entries(args).forEach(([key, value]) => {
|
|
107
|
-
const argDef = schema?.find(a => a.name === key);
|
|
108
|
-
const label = argDef?.name || key;
|
|
109
|
-
result += `**${label.charAt(0).toUpperCase() + label.slice(1)}:** ${value}\n`;
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return result;
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
'structured': (args, schema) => {
|
|
116
|
-
if (!args) return '';
|
|
117
|
-
|
|
118
|
-
let result = '\n\n---\n\n';
|
|
119
|
-
Object.entries(args).forEach(([key, value]) => {
|
|
120
|
-
result += `**${key}:** ${value}\n`;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Discover all prompts in the prompts directory
|
|
129
|
-
* @param {string} promptsDir - Path to prompts directory
|
|
130
|
-
* @returns {Array} Array of discovered prompts
|
|
131
|
-
*/
|
|
132
|
-
function discoverPrompts(promptsDir) {
|
|
133
|
-
if (!fs.existsSync(promptsDir)) {
|
|
134
|
-
throw new Error(`Prompts directory not found: ${promptsDir}`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const files = fs.readdirSync(promptsDir).filter(f => f.endsWith('.md'));
|
|
138
|
-
const prompts = [];
|
|
139
|
-
|
|
140
|
-
for (const file of files) {
|
|
141
|
-
try {
|
|
142
|
-
const name = path.basename(file, '.md');
|
|
143
|
-
validatePromptName(name); // Ensure safe name
|
|
144
|
-
|
|
145
|
-
const filePath = path.join(promptsDir, file);
|
|
146
|
-
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
147
|
-
|
|
148
|
-
const { metadata, content } = parsePromptFile(fileContent, file);
|
|
149
|
-
|
|
150
|
-
// Build prompt object
|
|
151
|
-
const prompt = {
|
|
152
|
-
name,
|
|
153
|
-
description: metadata.description || `Prompt: ${name}`,
|
|
154
|
-
version: metadata.version || '1.0.0',
|
|
155
|
-
arguments: metadata.arguments || [],
|
|
156
|
-
argumentFormat: metadata.argumentFormat || 'content-append',
|
|
157
|
-
content,
|
|
158
|
-
filePath,
|
|
159
|
-
_rawMetadata: metadata
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
prompts.push(prompt);
|
|
163
|
-
} catch (err) {
|
|
164
|
-
console.error(`Error loading prompt ${file}: ${err.message}`);
|
|
165
|
-
// Continue loading other prompts
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return prompts;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Load all prompts into cache
|
|
174
|
-
* @param {string} baseDir - Base directory (repo root)
|
|
175
|
-
* @param {PromptCache} cache - Prompt cache instance
|
|
176
|
-
* @returns {Array} Loaded prompts
|
|
177
|
-
*/
|
|
178
|
-
function loadAllPrompts(baseDir, cache) {
|
|
179
|
-
const promptsDir = path.join(baseDir, 'mcp', 'prompts');
|
|
180
|
-
const prompts = discoverPrompts(promptsDir);
|
|
181
|
-
|
|
182
|
-
// Clear and reload cache
|
|
183
|
-
cache.clear();
|
|
184
|
-
|
|
185
|
-
prompts.forEach(prompt => {
|
|
186
|
-
cache.set(prompt.name, prompt);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
return prompts;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Watch prompts directory for changes (development mode)
|
|
194
|
-
* @param {string} baseDir - Base directory
|
|
195
|
-
* @param {PromptCache} cache - Prompt cache instance
|
|
196
|
-
* @param {Function} onChange - Callback when prompts change
|
|
197
|
-
*/
|
|
198
|
-
function watchPrompts(baseDir, cache, onChange) {
|
|
199
|
-
const promptsDir = path.join(baseDir, 'mcp', 'prompts');
|
|
200
|
-
|
|
201
|
-
if (!fs.existsSync(promptsDir)) {
|
|
202
|
-
console.error(`Cannot watch prompts directory (not found): ${promptsDir}`);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const watcher = fs.watch(promptsDir, (eventType, filename) => {
|
|
207
|
-
if (!filename || !filename.endsWith('.md')) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
console.error(`Prompt file changed: ${filename} (${eventType})`);
|
|
212
|
-
console.error('Reloading all prompts...');
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
const prompts = loadAllPrompts(baseDir, cache);
|
|
216
|
-
console.error(`Reloaded ${prompts.length} prompts`);
|
|
217
|
-
|
|
218
|
-
if (onChange) {
|
|
219
|
-
onChange(prompts);
|
|
220
|
-
}
|
|
221
|
-
} catch (err) {
|
|
222
|
-
console.error(`Error reloading prompts: ${err.message}`);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
cache.addWatcher(watcher);
|
|
227
|
-
console.error('File watching enabled for prompts (dev mode)');
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Build prompt text with arguments
|
|
232
|
-
* @param {Object} prompt - Prompt object from cache
|
|
233
|
-
* @param {Object} args - Arguments provided by user
|
|
234
|
-
* @returns {string} Complete prompt text
|
|
235
|
-
*/
|
|
236
|
-
function buildPromptWithArguments(prompt, args) {
|
|
237
|
-
let promptText = prompt.content;
|
|
238
|
-
|
|
239
|
-
if (!args || Object.keys(args).length === 0) {
|
|
240
|
-
return promptText;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Use the formatter specified by the prompt
|
|
244
|
-
const formatter = argumentFormatters[prompt.argumentFormat];
|
|
245
|
-
if (!formatter) {
|
|
246
|
-
console.error(
|
|
247
|
-
`Unknown argument format: ${prompt.argumentFormat} for prompt ${prompt.name}`
|
|
248
|
-
);
|
|
249
|
-
return promptText;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const formattedArgs = formatter(args, prompt.arguments);
|
|
253
|
-
promptText += formattedArgs;
|
|
254
|
-
|
|
255
|
-
return promptText;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Convert prompts to MCP protocol format
|
|
260
|
-
* @param {Array} prompts - Discovered prompts
|
|
261
|
-
* @returns {Array} Prompts in MCP format
|
|
262
|
-
*/
|
|
263
|
-
function promptsToMcpFormat(prompts) {
|
|
264
|
-
return prompts.map(prompt => ({
|
|
265
|
-
name: prompt.name,
|
|
266
|
-
description: prompt.description,
|
|
267
|
-
arguments: prompt.arguments.map(arg => ({
|
|
268
|
-
name: arg.name,
|
|
269
|
-
description: arg.description,
|
|
270
|
-
required: arg.required
|
|
271
|
-
}))
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
module.exports = {
|
|
276
|
-
PromptCache,
|
|
277
|
-
discoverPrompts,
|
|
278
|
-
loadAllPrompts,
|
|
279
|
-
watchPrompts,
|
|
280
|
-
buildPromptWithArguments,
|
|
281
|
-
promptsToMcpFormat,
|
|
282
|
-
argumentFormatters
|
|
283
|
-
};
|