@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.
@@ -1,16 +1,16 @@
1
1
  /**
2
- * MCP Tools - Documentation Review
2
+ * MCP Tools - Generated Documentation Review
3
3
  *
4
- * OPTIMIZATION: This tool uses programmatic checks instead of LLM for most validations.
5
- * - Caches style guide content
6
- * - Programmatic checks for: missing descriptions, invalid xrefs, DRY violations
7
- * - Only style/tone review requires LLM processing
4
+ * Programmatic validation for auto-generated documentation.
5
+ * Checks for: missing descriptions, invalid xrefs, DRY violations, quality scoring.
6
+ *
7
+ * Note: Style and tone review is handled by the style-guide skill in docs-team-standards.
8
+ * This tool focuses on structural and technical validation only.
8
9
  */
9
10
 
10
11
  const fs = require('fs');
11
12
  const path = require('path');
12
13
  const { findRepoRoot, formatDate } = require('./utils');
13
- const cache = require('./cache');
14
14
 
15
15
  /**
16
16
  * Sanitize version string to prevent path traversal
@@ -53,54 +53,6 @@ function sanitizeVersion(version) {
53
53
  return sanitized;
54
54
  }
55
55
 
56
- /**
57
- * Build style review instructions for LLM-based review
58
- * @param {string} docType - Type of documentation being reviewed
59
- * @returns {string} Review instructions
60
- */
61
- function buildStyleReviewInstructions(docType) {
62
- const baseInstructions = `
63
- # Style Guide Review Instructions
64
-
65
- You are reviewing autogenerated ${docType} documentation for Redpanda.
66
-
67
- ## Your Task
68
-
69
- Review the content sample below against the style guide and identify:
70
-
71
- 1. **Terminology issues**
72
- - Incorrect capitalization (e.g., "RedPanda" should be "Redpanda")
73
- - Deprecated terms (e.g., "whitelist/blacklist" should be "allowlist/denylist")
74
- - Incorrect technical terms
75
-
76
- 2. **Voice and tone issues**
77
- - Passive voice (should be active)
78
- - Wrong tense (should be present tense)
79
- - Wrong person (should be second person "you")
80
-
81
- 3. **Formatting issues**
82
- - Heading capitalization (should be sentence case, not title case)
83
- - Overuse of bold
84
- - Use of em dashes (should use parentheses or colons instead)
85
-
86
- 4. **Structural issues**
87
- - Incorrect heading hierarchy
88
- - Missing descriptions or context
89
- - Unclear explanations
90
-
91
- ## Output Format
92
-
93
- For each issue found, provide:
94
- - Line number or section reference
95
- - Issue type (terminology/voice/formatting/structure)
96
- - Specific problem
97
- - Suggested fix
98
-
99
- If no issues are found in a category, state "No issues found."
100
- `;
101
-
102
- return baseInstructions;
103
- }
104
56
 
105
57
  /**
106
58
  * Generate an AsciiDoc report from review results
@@ -337,22 +289,6 @@ function reviewGeneratedDocs(args) {
337
289
  const repoRoot = findRepoRoot();
338
290
  const { doc_type, version, generate_report } = args;
339
291
 
340
- // Load style guide from cache or file (cache for 1 hour)
341
- const styleGuidePath = path.join(repoRoot.root, 'mcp', 'team-standards', 'style-guide.md');
342
- let styleGuide = null;
343
-
344
- const cacheKey = 'style-guide-content';
345
- styleGuide = cache.get(cacheKey);
346
-
347
- if (!styleGuide && fs.existsSync(styleGuidePath)) {
348
- try {
349
- styleGuide = fs.readFileSync(styleGuidePath, 'utf8');
350
- cache.set(cacheKey, styleGuide, 60 * 60 * 1000); // Cache for 1 hour
351
- } catch (err) {
352
- console.error(`Warning: Could not load style guide: ${err.message}`);
353
- }
354
- }
355
-
356
292
  if (!doc_type) {
357
293
  return {
358
294
  success: false,
@@ -365,9 +301,6 @@ function reviewGeneratedDocs(args) {
365
301
  const suggestions = [];
366
302
  let filesAnalyzed = 0;
367
303
  let qualityScore = 100;
368
- let contentSample = ''; // For storing content samples for LLM review
369
- let contentFilePath = null; // Path to full content file for deep review if needed
370
- let contentTotalLines = 0; // Total lines in the content file
371
304
 
372
305
  try {
373
306
  switch (doc_type) {
@@ -587,19 +520,6 @@ function reviewGeneratedDocs(args) {
587
520
  qualityScore -= Math.min(5, Math.floor(propertiesNeedingExamples.length / 5));
588
521
  }
589
522
 
590
- // Sample generated property docs for style review (hybrid approach)
591
- const clusterPropsPath = path.join(repoRoot.root, propertiesBaseDir, 'partials', 'properties', 'cluster-properties.adoc');
592
- if (fs.existsSync(clusterPropsPath)) {
593
- const fullContent = fs.readFileSync(clusterPropsPath, 'utf8');
594
- const lines = fullContent.split('\n');
595
- // Sample first 300 lines to catch systematic issues
596
- // (enough to see multiple properties and their patterns)
597
- contentSample = lines.slice(0, 300).join('\n');
598
- // Store file path for full review if needed
599
- contentFilePath = clusterPropsPath;
600
- contentTotalLines = lines.length;
601
- }
602
-
603
523
  break;
604
524
  }
605
525
 
@@ -699,29 +619,12 @@ function reviewGeneratedDocs(args) {
699
619
  suggestions.push('All $ref references are valid and DRY principles are maintained');
700
620
  }
701
621
 
702
- // Sample connector descriptions for style review
703
- // Collect sample descriptions from inputs/outputs
704
- const sampleDescriptions = [];
705
- ['inputs', 'outputs'].forEach(section => {
706
- if (overrides[section]) {
707
- Object.entries(overrides[section]).slice(0, 5).forEach(([name, config]) => {
708
- if (config.description) {
709
- sampleDescriptions.push(`\n== ${name} (${section})\n\n${config.description}`);
710
- }
711
- });
712
- }
713
- });
714
-
715
- if (sampleDescriptions.length > 0) {
716
- contentSample = `= RPCN Connector Descriptions Sample\n${sampleDescriptions.join('\n\n')}`;
717
- }
718
-
719
622
  break;
720
623
  }
721
624
 
722
625
  case 'metrics':
723
626
  case 'rpk': {
724
- // For metrics and RPK, check files exist and sample content for style review
627
+ // For metrics and RPK, check files exist
725
628
  if (!version) {
726
629
  return {
727
630
  success: false,
@@ -740,11 +643,9 @@ function reviewGeneratedDocs(args) {
740
643
  };
741
644
  }
742
645
 
743
- let filePath;
744
-
745
646
  if (doc_type === 'metrics') {
746
647
  // Use custom path or default
747
- filePath = args.metrics_file ?
648
+ const filePath = args.metrics_file ?
748
649
  path.join(repoRoot.root, args.metrics_file) :
749
650
  path.join(repoRoot.root, 'modules', 'reference', 'pages', 'public-metrics-reference.adoc');
750
651
 
@@ -755,14 +656,6 @@ function reviewGeneratedDocs(args) {
755
656
  suggestion: 'Generate metrics docs first using generate_metrics_docs tool, or specify custom path with metrics_file parameter'
756
657
  };
757
658
  }
758
-
759
- // Read first 300 lines as sample for style review (hybrid approach)
760
- const fullContent = fs.readFileSync(filePath, 'utf8');
761
- const lines = fullContent.split('\n');
762
- contentSample = lines.slice(0, 300).join('\n');
763
- contentFilePath = filePath;
764
- contentTotalLines = lines.length;
765
-
766
659
  } else {
767
660
  // RPK files are version-specific
768
661
  const normalizedVersion = sanitizedVersion.startsWith('v') ? sanitizedVersion : `v${sanitizedVersion}`;
@@ -778,22 +671,6 @@ function reviewGeneratedDocs(args) {
778
671
  suggestion: 'Generate RPK docs first using generate_rpk_docs tool, or specify custom path with rpk_dir parameter'
779
672
  };
780
673
  }
781
- filePath = rpkDir;
782
-
783
- // Sample 3 command files for review (hybrid approach)
784
- const rpkFiles = fs.readdirSync(rpkDir).filter(f => f.endsWith('.adoc'));
785
- if (rpkFiles.length > 0) {
786
- const samplesToRead = Math.min(3, rpkFiles.length);
787
- const samples = [];
788
- for (let i = 0; i < samplesToRead; i++) {
789
- const sampleFile = path.join(rpkDir, rpkFiles[i]);
790
- const content = fs.readFileSync(sampleFile, 'utf8');
791
- samples.push(`\n// File: ${rpkFiles[i]}\n${content}`);
792
- }
793
- contentSample = samples.join('\n\n---\n\n');
794
- contentFilePath = rpkDir;
795
- contentTotalLines = rpkFiles.length; // Total number of command files
796
- }
797
674
  }
798
675
 
799
676
  filesAnalyzed++;
@@ -822,33 +699,9 @@ function reviewGeneratedDocs(args) {
822
699
  quality_score: qualityScore,
823
700
  suggestions,
824
701
  summary: `Reviewed ${doc_type} documentation. Quality score: ${qualityScore}/100. Found ${issues.length} issues.`,
825
- style_guide_loaded: styleGuide !== null,
826
- style_guide: styleGuide,
827
- // Hybrid review approach: sample for quick check, full content path if deeper review needed
828
- content_sample: contentSample || null,
829
- content_file_path: contentFilePath,
830
- content_total_lines: contentTotalLines || null,
831
- content_sample_info: contentSample ?
832
- (doc_type === 'rpk' ?
833
- `Sample includes ${Math.min(3, contentTotalLines)} of ${contentTotalLines} command files` :
834
- `Sample includes first 300 of ${contentTotalLines} lines`) : null,
835
- // Instructions for LLM-based review
836
- review_instructions: styleGuide && contentSample ? buildStyleReviewInstructions(doc_type) : null,
837
- additional_review_needed: styleGuide && contentSample ?
838
- 'IMPORTANT: HYBRID REVIEW APPROACH\n\n' +
839
- '1. First, review the content sample below against the style guide:\n' +
840
- ' - Terminology compliance (correct capitalization, approved terms)\n' +
841
- ' - Voice and tone (active voice, present tense, second person)\n' +
842
- ' - Formatting (heading capitalization, bold usage, em dashes)\n' +
843
- ' - Clarity and readability\n\n' +
844
- '2. If you find style issues in the sample:\n' +
845
- ' - Report the issues found in the sample\n' +
846
- ' - Use the Read tool on content_file_path to review more content\n' +
847
- ' - Look for patterns that indicate systematic problems\n\n' +
848
- '3. If no issues found in sample, the docs are likely good throughout.' :
849
- styleGuide ?
850
- 'IMPORTANT: The programmatic review above checks for technical quality issues (missing descriptions, invalid xrefs, etc.). You should ALSO review the generated content against the style guide below for terminology, tone, and formatting compliance.' :
851
- 'Note: Style guide not available for additional review.'
702
+ // Note: Style review is handled by the style-guide skill in docs-team-standards
703
+ next_steps: 'This tool validates structural issues (missing descriptions, invalid refs, DRY violations). ' +
704
+ 'For style and tone review, use the content-reviewer agent from docs-team-standards plugin.'
852
705
  };
853
706
 
854
707
  // Generate AsciiDoc report if requested
@@ -25,7 +25,6 @@ const { generateBundleOpenApi } = require('./openapi');
25
25
 
26
26
  // Review tools
27
27
  const { reviewGeneratedDocs, generateReviewReport } = require('./generated-docs-review');
28
- const { reviewContent } = require('./content-review');
29
28
 
30
29
  // Job queue
31
30
  const { initializeJobQueue, createJob, getJob, listJobs, cleanupOldJobs } = require('./job-queue');
@@ -77,9 +76,6 @@ function executeTool(toolName, args = {}) {
77
76
  case 'review_generated_docs':
78
77
  return reviewGeneratedDocs(args);
79
78
 
80
- case 'review_content':
81
- return reviewContent(args, repoRoot.root);
82
-
83
79
  case 'run_doc_tools_command': {
84
80
  // Validate and execute raw doc-tools command
85
81
  if (!args || typeof args !== 'object') {
@@ -1,171 +1,78 @@
1
1
  /**
2
2
  * MCP Configuration Validation
3
3
  *
4
- * Validates prompts, resources, and overall MCP server configuration.
5
- * Used at startup and by the validate-mcp CLI command.
4
+ * Validates MCP server configuration.
5
+ * Note: Prompts and most resources have been migrated to docs-team-standards plugin.
6
+ * This now only validates the personas resource and tools configuration.
6
7
  */
7
8
 
8
9
  const fs = require('fs');
9
10
  const path = require('path');
10
- const { parsePromptFile } = require('./frontmatter');
11
11
 
12
12
  /**
13
- * Validate that a prompt name is safe (no path traversal)
14
- * @param {string} name - Prompt name to validate
15
- * @throws {Error} If name is invalid
13
+ * Resources that are loaded dynamically (from current working directory)
16
14
  */
17
- function validatePromptName(name) {
18
- if (!name || typeof name !== 'string') {
19
- throw new Error('Prompt name must be a non-empty string');
20
- }
21
-
22
- // Only allow alphanumeric, hyphens, and underscores
23
- if (!/^[a-z0-9_-]+$/i.test(name)) {
24
- throw new Error(
25
- `Invalid prompt name: ${name}. Use only letters, numbers, hyphens, and underscores.`
26
- );
27
- }
28
-
29
- return name;
30
- }
31
-
32
- /**
33
- * Validate that required arguments are provided
34
- * @param {string} promptName - Name of the prompt
35
- * @param {Object} providedArgs - Arguments provided by user
36
- * @param {Array} schema - Argument schema from prompt metadata
37
- * @throws {Error} If validation fails
38
- */
39
- function validatePromptArguments(promptName, providedArgs, schema) {
40
- if (!schema || schema.length === 0) {
41
- return; // No validation needed
42
- }
43
-
44
- const errors = [];
45
-
46
- // Check required arguments
47
- schema
48
- .filter(arg => arg.required)
49
- .forEach(arg => {
50
- if (!providedArgs || !providedArgs[arg.name]) {
51
- errors.push(`Missing required argument: ${arg.name}`);
52
- }
53
- });
54
-
55
- if (errors.length > 0) {
56
- throw new Error(
57
- `Invalid arguments for prompt "${promptName}":\n${errors.join('\n')}`
58
- );
59
- }
60
- }
15
+ const DYNAMIC_RESOURCES = ['redpanda://personas'];
61
16
 
62
17
  /**
63
- * Validate all resources are accessible
18
+ * Validate resources configuration
64
19
  * @param {Array} resources - Resource definitions
65
- * @param {Object} resourceMap - Resource file mappings
66
- * @param {string} baseDir - Base directory for resources
67
20
  * @returns {{ errors: string[], warnings: string[] }}
68
21
  */
69
- function validateResources(resources, resourceMap, baseDir) {
22
+ function validateResources(resources) {
70
23
  const errors = [];
71
24
  const warnings = [];
72
25
 
73
26
  for (const resource of resources) {
74
- // Check mapping exists
75
- const mapping = resourceMap[resource.uri];
76
- if (!mapping) {
77
- errors.push(`Resource ${resource.uri} has no file mapping in resourceMap`);
78
- continue;
79
- }
80
-
81
- // Check file exists
82
- const filePath = path.join(baseDir, 'mcp', 'team-standards', mapping.file);
83
- if (!fs.existsSync(filePath)) {
84
- errors.push(`Resource file missing: ${mapping.file} for ${resource.uri}`);
27
+ // All remaining resources should be dynamic (loaded from cwd)
28
+ if (!DYNAMIC_RESOURCES.includes(resource.uri)) {
29
+ warnings.push(`Unexpected resource: ${resource.uri} - static resources should be in docs-team-standards plugin`);
85
30
  continue;
86
31
  }
87
32
 
88
- // Check file is readable
89
- try {
90
- fs.readFileSync(filePath, 'utf8');
91
- } catch (err) {
92
- errors.push(`Resource file not readable: ${mapping.file} - ${err.message}`);
93
- continue;
94
- }
95
-
96
- // Validate resource metadata
33
+ // Validate metadata for dynamic resources
97
34
  if (!resource.name || resource.name.length < 3) {
98
35
  warnings.push(`Resource ${resource.uri} has a very short name`);
99
36
  }
100
-
101
37
  if (!resource.description || resource.description.length < 10) {
102
38
  warnings.push(`Resource ${resource.uri} has a very short description`);
103
39
  }
104
-
105
- // Check for versioning
106
- if (!resource.version) {
107
- warnings.push(`Resource ${resource.uri} missing version metadata`);
108
- }
109
-
110
- if (!resource.lastUpdated) {
111
- warnings.push(`Resource ${resource.uri} missing lastUpdated metadata`);
112
- }
113
40
  }
114
41
 
115
42
  return { errors, warnings };
116
43
  }
117
44
 
118
45
  /**
119
- * Validate all prompts are loadable
120
- * @param {Array} prompts - Discovered prompts
46
+ * Validate tools configuration
47
+ * @param {Array} tools - Tool definitions
121
48
  * @returns {{ errors: string[], warnings: string[] }}
122
49
  */
123
- function validatePrompts(prompts) {
50
+ function validateTools(tools) {
124
51
  const errors = [];
125
52
  const warnings = [];
126
53
 
127
- for (const prompt of prompts) {
128
- // Check basic metadata
129
- if (!prompt.description || prompt.description.length < 10) {
130
- warnings.push(`Prompt "${prompt.name}" has a very short description`);
131
- }
132
-
133
- if (!prompt.content || prompt.content.trim().length < 100) {
134
- warnings.push(`Prompt "${prompt.name}" has very little content (< 100 chars)`);
54
+ for (const tool of tools) {
55
+ if (!tool.name) {
56
+ errors.push('Tool missing name');
57
+ continue;
135
58
  }
136
59
 
137
- // Check for version
138
- if (!prompt.version) {
139
- warnings.push(`Prompt "${prompt.name}" missing version metadata`);
60
+ if (!tool.description || tool.description.length < 10) {
61
+ warnings.push(`Tool "${tool.name}" has a very short description`);
140
62
  }
141
63
 
142
- // Validate argument format if specified
143
- if (prompt.argumentFormat) {
144
- const validFormats = ['content-append', 'structured'];
145
- if (!validFormats.includes(prompt.argumentFormat)) {
146
- errors.push(
147
- `Prompt "${prompt.name}" has invalid argumentFormat: ${prompt.argumentFormat}`
148
- );
149
- }
64
+ if (!tool.inputSchema) {
65
+ warnings.push(`Tool "${tool.name}" missing inputSchema`);
150
66
  }
67
+ }
151
68
 
152
- // Check for argument schema consistency
153
- if (prompt.arguments && prompt.arguments.length > 0) {
154
- if (!prompt.argumentFormat) {
155
- warnings.push(
156
- `Prompt "${prompt.name}" has arguments but no argumentFormat specified`
157
- );
158
- }
159
-
160
- // Check for duplicate argument names
161
- const argNames = new Set();
162
- for (const arg of prompt.arguments) {
163
- if (argNames.has(arg.name)) {
164
- errors.push(`Prompt "${prompt.name}" has duplicate argument: ${arg.name}`);
165
- }
166
- argNames.add(arg.name);
167
- }
69
+ // Check for duplicate names
70
+ const toolNames = new Set();
71
+ for (const tool of tools) {
72
+ if (tool.name && toolNames.has(tool.name)) {
73
+ errors.push(`Duplicate tool name: ${tool.name}`);
168
74
  }
75
+ toolNames.add(tool.name);
169
76
  }
170
77
 
171
78
  return { errors, warnings };
@@ -175,9 +82,7 @@ function validatePrompts(prompts) {
175
82
  * Validate entire MCP configuration
176
83
  * @param {Object} config - Configuration object
177
84
  * @param {Array} config.resources - Resources
178
- * @param {Object} config.resourceMap - Resource mappings
179
- * @param {Array} config.prompts - Prompts
180
- * @param {string} config.baseDir - Base directory
85
+ * @param {Array} config.tools - Tools
181
86
  * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
182
87
  */
183
88
  function validateMcpConfiguration(config) {
@@ -185,26 +90,17 @@ function validateMcpConfiguration(config) {
185
90
  const allWarnings = [];
186
91
 
187
92
  // Validate resources
188
- const resourceValidation = validateResources(
189
- config.resources,
190
- config.resourceMap,
191
- config.baseDir
192
- );
193
- allErrors.push(...resourceValidation.errors);
194
- allWarnings.push(...resourceValidation.warnings);
195
-
196
- // Validate prompts
197
- const promptValidation = validatePrompts(config.prompts);
198
- allErrors.push(...promptValidation.errors);
199
- allWarnings.push(...promptValidation.warnings);
93
+ if (config.resources) {
94
+ const resourceValidation = validateResources(config.resources);
95
+ allErrors.push(...resourceValidation.errors);
96
+ allWarnings.push(...resourceValidation.warnings);
97
+ }
200
98
 
201
- // Check for name collisions
202
- const promptNames = new Set();
203
- for (const prompt of config.prompts) {
204
- if (promptNames.has(prompt.name)) {
205
- allErrors.push(`Duplicate prompt name: ${prompt.name}`);
206
- }
207
- promptNames.add(prompt.name);
99
+ // Validate tools
100
+ if (config.tools) {
101
+ const toolValidation = validateTools(config.tools);
102
+ allErrors.push(...toolValidation.errors);
103
+ allWarnings.push(...toolValidation.warnings);
208
104
  }
209
105
 
210
106
  return {
@@ -217,6 +113,7 @@ function validateMcpConfiguration(config) {
217
113
  /**
218
114
  * Format validation results for display
219
115
  * @param {{ valid: boolean, errors: string[], warnings: string[] }} results
116
+ * @param {Object} config - Configuration object
220
117
  * @returns {string} Formatted output
221
118
  */
222
119
  function formatValidationResults(results, config) {
@@ -227,8 +124,8 @@ function formatValidationResults(results, config) {
227
124
  lines.push('');
228
125
 
229
126
  // Summary
230
- lines.push(`Prompts found: ${config.prompts.length}`);
231
- lines.push(`Resources found: ${config.resources.length}`);
127
+ lines.push(`Tools found: ${config.tools?.length || 0}`);
128
+ lines.push(`Resources found: ${config.resources?.length || 0}`);
232
129
  lines.push('');
233
130
 
234
131
  // Warnings
@@ -257,10 +154,8 @@ function formatValidationResults(results, config) {
257
154
  }
258
155
 
259
156
  module.exports = {
260
- validatePromptName,
261
- validatePromptArguments,
262
157
  validateResources,
263
- validatePrompts,
158
+ validateTools,
264
159
  validateMcpConfiguration,
265
160
  formatValidationResults
266
161
  };
package/mcp/COSTS.adoc CHANGED
@@ -1,9 +1,9 @@
1
1
  = MCP Tool Costs
2
2
  :description: Cost reference for Redpanda documentation MCP server tools
3
3
 
4
- The typical cost range is $0.017 - $0.190 per operation.
4
+ The typical cost range is $0.03 - $0.19 per operation.
5
5
 
6
- Most operations cost between $0.03 - $0.08. Content review operations range from $0.017 (simple terminology checks) to $0.19 (comprehensive generated docs reviews).
6
+ Most operations cost between $0.03 - $0.08. Generated docs review costs up to $0.19 for comprehensive reviews.
7
7
 
8
8
  == Cost factors
9
9
 
@@ -66,20 +66,10 @@ You control costs through:
66
66
  |review_generated_docs
67
67
  |Review the autogenerated metrics docs
68
68
  |0.104 - 0.190
69
-
70
- |review_content (terminology)
71
- |Check terminology in a sentence
72
- |0.017
73
-
74
- |review_content (style)
75
- |Review content for style guide compliance
76
- |0.065
77
-
78
- |review_content (comprehensive)
79
- |Comprehensive content review
80
- |0.062
81
69
  |===
82
70
 
71
+ NOTE: The `review_content` tool was migrated to the `content-reviewer` agent in docs-team-standards. Content review now uses skills directly without MCP tool costs.
72
+
83
73
  == How to measure costs
84
74
 
85
75
  To track costs during testing:
@@ -162,6 +152,6 @@ claude --model opus
162
152
  == Cost optimization tips
163
153
 
164
154
  * *Choose the right model*: Use Haiku for simple generation tasks, Sonnet for most work
165
- * *Leverage caching*: The MCP server caches style guides and prompts - repeated operations in the same session are cheaper
166
- * *Batch similar work*: Do multiple reviews or generations in one session to maximize cache benefits
167
- * *Use focused reviews*: For content review, use `focus: "terminology"` or `focus: "style"` instead of `focus: "comprehensive"` when you only need specific feedback
155
+ * *Leverage caching*: Repeated operations in the same session benefit from prompt caching
156
+ * *Batch similar work*: Do multiple generations in one session to maximize cache benefits
157
+ * *Use skills for review*: Content review now uses the docs-team-standards plugin (no MCP tool costs)