@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.13.6",
3
+ "version": "4.14.0",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -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
- };