claude-flow-novice 1.5.4 → 1.5.5

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.
@@ -0,0 +1,841 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Agent Profile Validator
5
+ *
6
+ * Validates agent profiles against CLAUDE.md standards and design principles.
7
+ * Checks frontmatter, prompt format, quality, and provides actionable recommendations.
8
+ *
9
+ * Usage:
10
+ * node validate-agent.js <path-to-agent.md>
11
+ * node validate-agent.js --all
12
+ *
13
+ * @module validate-agent
14
+ */
15
+
16
+ import { readFile, readdir } from 'fs/promises';
17
+ import { resolve, dirname, basename, join } from 'path';
18
+ import { fileURLToPath } from 'url';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+
23
+ // ============================================================================
24
+ // Configuration
25
+ // ============================================================================
26
+
27
+ const APPROVED_TOOLS = [
28
+ 'Read', 'Write', 'Edit', 'MultiEdit', 'Bash', 'Glob', 'Grep', 'TodoWrite'
29
+ ];
30
+
31
+ const APPROVED_MODELS = [
32
+ 'sonnet', 'haiku', 'opus', 'sonnet-3-5', 'sonnet-4-5', 'claude-3-5-sonnet-20241022'
33
+ ];
34
+
35
+ const VALID_COLOR_FORMATS = [
36
+ /^[a-z]+$/i, // Named colors: "orange", "green"
37
+ /^#[0-9A-F]{6}$/i, // Hex colors: "#FF9800"
38
+ /^rgb\(\d+,\s*\d+,\s*\d+\)$/i // RGB colors: "rgb(255, 152, 0)"
39
+ ];
40
+
41
+ const AGENT_TYPES = [
42
+ 'coder', 'reviewer', 'tester', 'planner', 'researcher', 'coordinator',
43
+ 'backend-dev', 'api-docs', 'system-architect', 'code-analyzer',
44
+ 'mobile-dev', 'tdd-london-swarm', 'production-validator',
45
+ 'perf-analyzer', 'performance-benchmarker', 'task-orchestrator'
46
+ ];
47
+
48
+ // ============================================================================
49
+ // YAML Parser (Simplified - handles basic YAML)
50
+ // ============================================================================
51
+
52
+ function parseYAML(yamlString) {
53
+ const lines = yamlString.split('\n');
54
+ const result = {};
55
+ let currentKey = null;
56
+ let currentArray = null;
57
+ let inMultiline = false;
58
+ let multilineKey = null;
59
+ let multilineContent = [];
60
+
61
+ for (const line of lines) {
62
+ const trimmed = line.trim();
63
+
64
+ // Skip empty lines and comments
65
+ if (!trimmed || trimmed.startsWith('#')) continue;
66
+
67
+ // Handle multiline blocks
68
+ if (trimmed.endsWith('|') || trimmed.endsWith('>')) {
69
+ multilineKey = trimmed.slice(0, -1).replace(':', '').trim();
70
+ inMultiline = true;
71
+ multilineContent = [];
72
+ continue;
73
+ }
74
+
75
+ if (inMultiline) {
76
+ if (line.startsWith(' ')) {
77
+ multilineContent.push(line.slice(2));
78
+ } else {
79
+ result[multilineKey] = multilineContent.join('\n');
80
+ inMultiline = false;
81
+ multilineKey = null;
82
+ }
83
+ }
84
+
85
+ // Handle array items
86
+ if (trimmed.startsWith('- ')) {
87
+ const value = trimmed.slice(2).trim();
88
+ if (currentArray) {
89
+ currentArray.push(value);
90
+ }
91
+ continue;
92
+ }
93
+
94
+ // Handle key-value pairs
95
+ if (trimmed.includes(':')) {
96
+ const [key, ...valueParts] = trimmed.split(':');
97
+ const value = valueParts.join(':').trim();
98
+
99
+ currentKey = key.trim();
100
+
101
+ if (value === '') {
102
+ // Start of array or object
103
+ currentArray = [];
104
+ result[currentKey] = currentArray;
105
+ } else {
106
+ // Simple value
107
+ currentArray = null;
108
+ // Try to parse as number or boolean
109
+ if (value === 'true') result[currentKey] = true;
110
+ else if (value === 'false') result[currentKey] = false;
111
+ else if (!isNaN(value) && value !== '') result[currentKey] = Number(value);
112
+ else result[currentKey] = value.replace(/^['"]|['"]$/g, ''); // Remove quotes
113
+ }
114
+ }
115
+ }
116
+
117
+ // Close any remaining multiline
118
+ if (inMultiline && multilineKey) {
119
+ result[multilineKey] = multilineContent.join('\n');
120
+ }
121
+
122
+ return result;
123
+ }
124
+
125
+ // ============================================================================
126
+ // Format Detection & Classification
127
+ // ============================================================================
128
+
129
+ /**
130
+ * Classifies agent prompt format based on content analysis
131
+ */
132
+ function classifyFormat(content, frontmatter) {
133
+ const promptContent = content.split('---').slice(2).join('---');
134
+ const lines = promptContent.split('\n').filter(line => line.trim());
135
+ const wordCount = promptContent.split(/\s+/).length;
136
+
137
+ // Count code blocks
138
+ const codeBlockCount = (promptContent.match(/```/g) || []).length / 2;
139
+
140
+ // Count structured sections
141
+ const hasCapabilities = frontmatter.capabilities !== undefined;
142
+ const hasHooks = frontmatter.hooks !== undefined;
143
+ const hasLifecycle = frontmatter.lifecycle !== undefined;
144
+
145
+ // Classification logic based on empirical findings
146
+ const classification = {
147
+ format: 'unknown',
148
+ confidence: 0,
149
+ characteristics: {},
150
+ tokens: 0,
151
+ words: wordCount
152
+ };
153
+
154
+ // CODE-HEAVY: 2000+ tokens, multiple code examples, implementation patterns
155
+ if (codeBlockCount >= 3 && wordCount > 1500) {
156
+ classification.format = 'code-heavy';
157
+ classification.confidence = 0.9;
158
+ classification.tokens = 2000 + Math.floor(wordCount * 0.75);
159
+ classification.characteristics = {
160
+ codeBlocks: codeBlockCount,
161
+ hasExamples: true,
162
+ hasImplementationPatterns: true,
163
+ verbosity: 'high'
164
+ };
165
+ }
166
+ // METADATA: 1000-1500 tokens, structured frontmatter, YAML/TS blocks
167
+ else if ((hasCapabilities || hasHooks || hasLifecycle) && codeBlockCount >= 1) {
168
+ classification.format = 'metadata';
169
+ classification.confidence = 0.85;
170
+ classification.tokens = 1000 + Math.floor(wordCount * 0.5);
171
+ classification.characteristics = {
172
+ codeBlocks: codeBlockCount,
173
+ hasStructuredMetadata: true,
174
+ hasCapabilities,
175
+ hasHooks,
176
+ hasLifecycle,
177
+ verbosity: 'medium'
178
+ };
179
+ }
180
+ // MINIMAL: 500-800 tokens, lean prompts, reasoning-focused
181
+ else if (wordCount < 1000 && codeBlockCount <= 1) {
182
+ classification.format = 'minimal';
183
+ classification.confidence = 0.8;
184
+ classification.tokens = 500 + Math.floor(wordCount * 0.33);
185
+ classification.characteristics = {
186
+ codeBlocks: codeBlockCount,
187
+ reasoningFocused: true,
188
+ verbosity: 'low'
189
+ };
190
+ }
191
+ // METADATA as default middle ground
192
+ else {
193
+ classification.format = 'metadata';
194
+ classification.confidence = 0.6;
195
+ classification.tokens = 1000;
196
+ classification.characteristics = {
197
+ codeBlocks: codeBlockCount,
198
+ verbosity: 'medium'
199
+ };
200
+ }
201
+
202
+ return classification;
203
+ }
204
+
205
+ /**
206
+ * Estimates task complexity based on agent type and description
207
+ */
208
+ function estimateComplexity(frontmatter, content) {
209
+ const description = (frontmatter.description || '').toLowerCase();
210
+ const promptContent = content.toLowerCase();
211
+
212
+ const indicators = {
213
+ basic: ['simple', 'basic', 'string', 'array', 'validation', 'parse', 'format', 'convert'],
214
+ medium: ['multiple', 'integrate', 'refactor', 'concurrent', 'cache', 'queue', 'worker', 'pipeline'],
215
+ complex: ['architecture', 'system', 'distributed', 'scalable', 'design', 'trade-off', 'performance-critical', 'zero-copy']
216
+ };
217
+
218
+ const scores = {
219
+ basic: 0,
220
+ medium: 0,
221
+ complex: 0
222
+ };
223
+
224
+ // Score based on keywords
225
+ for (const [level, keywords] of Object.entries(indicators)) {
226
+ for (const keyword of keywords) {
227
+ if (description.includes(keyword)) scores[level]++;
228
+ if (promptContent.includes(keyword)) scores[level] += 0.5;
229
+ }
230
+ }
231
+
232
+ // Determine complexity
233
+ const maxScore = Math.max(...Object.values(scores));
234
+ if (maxScore === 0) return { complexity: 'medium', confidence: 'low', scores };
235
+
236
+ const complexity = Object.keys(scores).find(key => scores[key] === maxScore);
237
+ const confidence = maxScore >= 2 ? 'high' : maxScore >= 1 ? 'medium' : 'low';
238
+
239
+ return { complexity, confidence, scores };
240
+ }
241
+
242
+ /**
243
+ * Recommends optimal format based on agent type and complexity
244
+ */
245
+ function recommendFormat(agentType, complexity) {
246
+ // Always minimal for architectural reasoning
247
+ if (agentType === 'architect' || agentType === 'system-architect') {
248
+ return {
249
+ recommended: 'minimal',
250
+ reason: 'Architectural agents need reasoning freedom, not examples',
251
+ confidence: 'high'
252
+ };
253
+ }
254
+
255
+ // Always minimal for reviewers
256
+ if (agentType === 'reviewer' || agentType === 'code-analyzer') {
257
+ return {
258
+ recommended: 'minimal',
259
+ reason: 'Review agents need to reason about code, not follow patterns',
260
+ confidence: 'high'
261
+ };
262
+ }
263
+
264
+ // Always metadata for researchers
265
+ if (agentType === 'researcher') {
266
+ return {
267
+ recommended: 'metadata',
268
+ reason: 'Research needs structured output format',
269
+ confidence: 'high'
270
+ };
271
+ }
272
+
273
+ // Coder agents: complexity-based selection (validated)
274
+ if (agentType === 'coder' || agentType === 'backend-dev' || agentType === 'mobile-dev') {
275
+ if (complexity === 'basic') {
276
+ return {
277
+ recommended: 'code-heavy',
278
+ reason: 'Basic tasks benefit from examples (+43% quality boost validated)',
279
+ confidence: 'high',
280
+ evidence: 'Empirically validated on Rust benchmarks'
281
+ };
282
+ }
283
+ if (complexity === 'complex') {
284
+ return {
285
+ recommended: 'minimal',
286
+ reason: 'Complex tasks need reasoning, examples constrain solution space',
287
+ confidence: 'high',
288
+ evidence: 'Validated: 0% quality gap between formats on complex tasks'
289
+ };
290
+ }
291
+ return {
292
+ recommended: 'metadata',
293
+ reason: 'Medium complexity benefits from structure without over-constraining',
294
+ confidence: 'medium'
295
+ };
296
+ }
297
+
298
+ // Tester agents: similar to coders
299
+ if (agentType === 'tester' || agentType === 'tdd-london-swarm') {
300
+ if (complexity === 'basic') {
301
+ return {
302
+ recommended: 'code-heavy',
303
+ reason: 'Test structure and patterns benefit from examples',
304
+ confidence: 'medium'
305
+ };
306
+ }
307
+ return {
308
+ recommended: 'metadata',
309
+ reason: 'Test organization needs structure',
310
+ confidence: 'medium'
311
+ };
312
+ }
313
+
314
+ // Default: metadata as safe middle ground
315
+ return {
316
+ recommended: 'metadata',
317
+ reason: 'Balanced approach for general tasks',
318
+ confidence: 'medium'
319
+ };
320
+ }
321
+
322
+ // ============================================================================
323
+ // Validation Functions
324
+ // ============================================================================
325
+
326
+ /**
327
+ * Validates frontmatter structure and required fields
328
+ */
329
+ function validateFrontmatter(frontmatter) {
330
+ const issues = [];
331
+ const warnings = [];
332
+
333
+ // Required fields
334
+ if (!frontmatter.name) {
335
+ issues.push({
336
+ severity: 'error',
337
+ field: 'name',
338
+ message: 'Missing required field: name',
339
+ fix: 'Add "name: agent-name" to frontmatter'
340
+ });
341
+ }
342
+
343
+ if (!frontmatter.description) {
344
+ issues.push({
345
+ severity: 'error',
346
+ field: 'description',
347
+ message: 'Missing required field: description',
348
+ fix: 'Add "description: ..." to frontmatter'
349
+ });
350
+ } else if (frontmatter.description.length < 50) {
351
+ warnings.push({
352
+ severity: 'warning',
353
+ field: 'description',
354
+ message: 'Description is too short (< 50 chars)',
355
+ fix: 'Expand description to include key capabilities and use cases'
356
+ });
357
+ }
358
+
359
+ // Tools validation
360
+ if (!frontmatter.tools) {
361
+ warnings.push({
362
+ severity: 'warning',
363
+ field: 'tools',
364
+ message: 'Missing tools specification',
365
+ fix: 'Add "tools: Read, Write, Edit, ..." to frontmatter'
366
+ });
367
+ } else {
368
+ const tools = typeof frontmatter.tools === 'string'
369
+ ? frontmatter.tools.split(',').map(t => t.trim())
370
+ : frontmatter.tools;
371
+
372
+ const invalidTools = tools.filter(tool => !APPROVED_TOOLS.includes(tool));
373
+ if (invalidTools.length > 0) {
374
+ issues.push({
375
+ severity: 'error',
376
+ field: 'tools',
377
+ message: `Invalid tools: ${invalidTools.join(', ')}`,
378
+ fix: `Use only approved tools: ${APPROVED_TOOLS.join(', ')}`
379
+ });
380
+ }
381
+ }
382
+
383
+ // Model validation
384
+ if (!frontmatter.model) {
385
+ warnings.push({
386
+ severity: 'warning',
387
+ field: 'model',
388
+ message: 'Missing model specification',
389
+ fix: 'Add "model: sonnet" (or haiku, opus) to frontmatter'
390
+ });
391
+ } else if (!APPROVED_MODELS.includes(frontmatter.model)) {
392
+ warnings.push({
393
+ severity: 'warning',
394
+ field: 'model',
395
+ message: `Uncommon model: ${frontmatter.model}`,
396
+ fix: `Consider using: ${APPROVED_MODELS.slice(0, 3).join(', ')}`
397
+ });
398
+ }
399
+
400
+ // Color validation
401
+ if (!frontmatter.color) {
402
+ warnings.push({
403
+ severity: 'warning',
404
+ field: 'color',
405
+ message: 'Missing color specification',
406
+ fix: 'Add "color: blue" (or hex "#0000FF") to frontmatter'
407
+ });
408
+ } else {
409
+ const colorValid = VALID_COLOR_FORMATS.some(regex => regex.test(frontmatter.color));
410
+ if (!colorValid) {
411
+ issues.push({
412
+ severity: 'error',
413
+ field: 'color',
414
+ message: `Invalid color format: ${frontmatter.color}`,
415
+ fix: 'Use named color (e.g., "orange"), hex (e.g., "#FF9800"), or RGB (e.g., "rgb(255, 152, 0)")'
416
+ });
417
+ }
418
+ }
419
+
420
+ return { issues, warnings };
421
+ }
422
+
423
+ /**
424
+ * Analyzes prompt quality and structure
425
+ */
426
+ function analyzePromptQuality(content) {
427
+ const promptContent = content.split('---').slice(2).join('---');
428
+ const recommendations = [];
429
+
430
+ // Check for clear role definition
431
+ const hasRoleDefinition = /you are|your role|you specialize in/i.test(promptContent.slice(0, 500));
432
+ if (!hasRoleDefinition) {
433
+ recommendations.push({
434
+ category: 'role-clarity',
435
+ message: 'Add clear role definition in first paragraph',
436
+ example: 'Start with "You are a [Role] specialized in..."'
437
+ });
438
+ }
439
+
440
+ // Check for specific responsibilities
441
+ const hasResponsibilities = /responsibilities|duties|tasks|core functions/i.test(promptContent);
442
+ if (!hasResponsibilities) {
443
+ recommendations.push({
444
+ category: 'structure',
445
+ message: 'Add clear responsibilities section',
446
+ example: '## Core Responsibilities\n1. [Responsibility 1]\n2. [Responsibility 2]'
447
+ });
448
+ }
449
+
450
+ // Check for anti-patterns
451
+ const negativeCount = (promptContent.match(/don't|never|avoid(?!\s+memory leaks)/gi) || []).length;
452
+ if (negativeCount > 5) {
453
+ recommendations.push({
454
+ category: 'anti-pattern',
455
+ message: `Excessive negative instructions (${negativeCount} found)`,
456
+ fix: 'Rephrase as positive guidance: "Use X instead of Y"'
457
+ });
458
+ }
459
+
460
+ return { recommendations };
461
+ }
462
+
463
+ /**
464
+ * Checks format alignment with task complexity
465
+ */
466
+ function checkFormatAlignment(agentType, format, complexity) {
467
+ const recommendation = recommendFormat(agentType, complexity.complexity);
468
+ const alignment = {
469
+ aligned: format.format === recommendation.recommended,
470
+ currentFormat: format.format,
471
+ recommendedFormat: recommendation.recommended,
472
+ reason: recommendation.reason,
473
+ confidence: recommendation.confidence,
474
+ evidence: recommendation.evidence || 'Hypothesized from validated coder agent patterns'
475
+ };
476
+
477
+ if (!alignment.aligned) {
478
+ alignment.impact = estimateImpact(format.format, recommendation.recommended, complexity.complexity);
479
+ }
480
+
481
+ return alignment;
482
+ }
483
+
484
+ /**
485
+ * Estimates impact of format mismatch
486
+ */
487
+ function estimateImpact(currentFormat, recommendedFormat, complexity) {
488
+ const impacts = {
489
+ 'basic-minimal-to-code-heavy': '+43% quality boost (validated)',
490
+ 'basic-metadata-to-code-heavy': '+10-15% quality improvement',
491
+ 'complex-code-heavy-to-minimal': '0% quality gap, 10% faster response',
492
+ 'complex-metadata-to-minimal': '0-3% quality gap, 5% faster response'
493
+ };
494
+
495
+ const key = `${complexity}-${currentFormat}-to-${recommendedFormat}`;
496
+ return impacts[key] || 'Marginal impact expected';
497
+ }
498
+
499
+ // ============================================================================
500
+ // Validation Orchestration
501
+ // ============================================================================
502
+
503
+ /**
504
+ * Performs complete validation of an agent profile
505
+ */
506
+ async function validateAgent(filePath) {
507
+ const content = await readFile(filePath, 'utf-8');
508
+
509
+ // Parse frontmatter (handle both \n--- and ---\n endings)
510
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
511
+ if (!frontmatterMatch) {
512
+ return {
513
+ valid: false,
514
+ file: filePath,
515
+ agentType: 'unknown',
516
+ complianceScore: 0,
517
+ frontmatter: {
518
+ valid: false,
519
+ issues: [{
520
+ severity: 'error',
521
+ field: 'frontmatter',
522
+ message: 'No frontmatter found',
523
+ fix: 'Add YAML frontmatter at the beginning of the file'
524
+ }],
525
+ warnings: []
526
+ },
527
+ format: {
528
+ classification: { format: 'unknown', confidence: 0, characteristics: {}, tokens: 0, words: 0 },
529
+ complexity: { complexity: 'unknown', confidence: 'low', scores: { basic: 0, medium: 0, complex: 0 } },
530
+ alignment: { aligned: false, currentFormat: 'unknown', recommendedFormat: 'minimal', reason: 'No frontmatter', confidence: 'low', evidence: 'N/A' }
531
+ },
532
+ quality: { recommendations: [] },
533
+ summary: 'CRITICAL ERROR: No frontmatter found'
534
+ };
535
+ }
536
+
537
+ let frontmatter;
538
+ try {
539
+ frontmatter = parseYAML(frontmatterMatch[1]);
540
+ } catch (err) {
541
+ return {
542
+ valid: false,
543
+ file: filePath,
544
+ agentType: 'unknown',
545
+ complianceScore: 0,
546
+ frontmatter: {
547
+ valid: false,
548
+ issues: [{
549
+ severity: 'error',
550
+ field: 'frontmatter',
551
+ message: `Invalid YAML syntax: ${err.message}`,
552
+ fix: 'Fix YAML syntax errors in frontmatter'
553
+ }],
554
+ warnings: []
555
+ },
556
+ format: {
557
+ classification: { format: 'unknown', confidence: 0, characteristics: {}, tokens: 0, words: 0 },
558
+ complexity: { complexity: 'unknown', confidence: 'low', scores: { basic: 0, medium: 0, complex: 0 } },
559
+ alignment: { aligned: false, currentFormat: 'unknown', recommendedFormat: 'minimal', reason: 'Invalid YAML', confidence: 'low', evidence: 'N/A' }
560
+ },
561
+ quality: { recommendations: [] },
562
+ summary: `CRITICAL ERROR: Invalid YAML syntax`
563
+ };
564
+ }
565
+
566
+ // Run validations
567
+ const frontmatterValidation = validateFrontmatter(frontmatter);
568
+ const formatClassification = classifyFormat(content, frontmatter);
569
+ const complexityEstimate = estimateComplexity(frontmatter, content);
570
+ const qualityAnalysis = analyzePromptQuality(content);
571
+
572
+ // Determine agent type
573
+ const agentType = frontmatter.name || 'unknown';
574
+ const detectedType = AGENT_TYPES.find(type => agentType.toLowerCase().includes(type)) || 'unknown';
575
+
576
+ const formatAlignment = checkFormatAlignment(detectedType, formatClassification, complexityEstimate);
577
+
578
+ // Calculate compliance score
579
+ const totalIssues = frontmatterValidation.issues.length;
580
+ const totalWarnings = frontmatterValidation.warnings.length;
581
+ const totalRecommendations = qualityAnalysis.recommendations.length;
582
+
583
+ const complianceScore = Math.max(0, 100 - (totalIssues * 20) - (totalWarnings * 5) - (totalRecommendations * 2));
584
+
585
+ return {
586
+ valid: totalIssues === 0,
587
+ file: filePath,
588
+ agentType: detectedType || 'unknown',
589
+ complianceScore,
590
+ frontmatter: {
591
+ valid: frontmatterValidation.issues.length === 0,
592
+ issues: frontmatterValidation.issues,
593
+ warnings: frontmatterValidation.warnings
594
+ },
595
+ format: {
596
+ classification: formatClassification,
597
+ complexity: complexityEstimate,
598
+ alignment: formatAlignment
599
+ },
600
+ quality: qualityAnalysis,
601
+ summary: generateSummary(complianceScore, formatAlignment, frontmatterValidation, qualityAnalysis)
602
+ };
603
+ }
604
+
605
+ /**
606
+ * Generates human-readable summary
607
+ */
608
+ function generateSummary(score, alignment, frontmatter, quality) {
609
+ const status = score >= 90 ? 'Excellent' : score >= 75 ? 'Good' : score >= 60 ? 'Fair' : 'Needs Improvement';
610
+
611
+ const summary = [`Agent Profile Status: ${status} (${score}/100)`];
612
+
613
+ if (frontmatter.issues.length > 0) {
614
+ summary.push(`⚠️ ${frontmatter.issues.length} critical issue(s) found`);
615
+ }
616
+
617
+ if (!alignment.aligned) {
618
+ summary.push(`📊 Format mismatch: Using ${alignment.currentFormat}, recommend ${alignment.recommendedFormat}`);
619
+ summary.push(` Reason: ${alignment.reason}`);
620
+ summary.push(` Impact: ${alignment.impact || 'See recommendations'}`);
621
+ } else {
622
+ summary.push(`✅ Format aligned with best practices (${alignment.currentFormat})`);
623
+ }
624
+
625
+ if (quality.recommendations.length > 0) {
626
+ summary.push(`💡 ${quality.recommendations.length} improvement recommendation(s)`);
627
+ }
628
+
629
+ return summary.join('\n');
630
+ }
631
+
632
+ // ============================================================================
633
+ // CLI Interface
634
+ // ============================================================================
635
+
636
+ /**
637
+ * Formats validation results for console output
638
+ */
639
+ function formatOutput(result) {
640
+ const sections = [];
641
+
642
+ // Header
643
+ sections.push('═'.repeat(80));
644
+ sections.push(`AGENT VALIDATION REPORT: ${basename(result.file)}`);
645
+ sections.push('═'.repeat(80));
646
+ sections.push('');
647
+
648
+ // Summary
649
+ sections.push('SUMMARY');
650
+ sections.push('─'.repeat(80));
651
+ sections.push(result.summary);
652
+ sections.push('');
653
+
654
+ // Format Analysis
655
+ sections.push('FORMAT ANALYSIS');
656
+ sections.push('─'.repeat(80));
657
+ sections.push(`Detected Format: ${result.format.classification.format.toUpperCase()}`);
658
+ sections.push(`Confidence: ${(result.format.classification.confidence * 100).toFixed(0)}%`);
659
+ sections.push(`Estimated Tokens: ~${result.format.classification.tokens}`);
660
+ sections.push(`Word Count: ${result.format.classification.words}`);
661
+ sections.push('');
662
+ sections.push('Characteristics:');
663
+ for (const [key, value] of Object.entries(result.format.classification.characteristics)) {
664
+ sections.push(` • ${key}: ${value}`);
665
+ }
666
+ sections.push('');
667
+
668
+ // Complexity Estimate
669
+ sections.push('COMPLEXITY ANALYSIS');
670
+ sections.push('─'.repeat(80));
671
+ sections.push(`Estimated Complexity: ${result.format.complexity.complexity.toUpperCase()}`);
672
+ sections.push(`Confidence: ${result.format.complexity.confidence.toUpperCase()}`);
673
+ sections.push('Indicator Scores:');
674
+ for (const [level, score] of Object.entries(result.format.complexity.scores)) {
675
+ sections.push(` • ${level}: ${score.toFixed(1)}`);
676
+ }
677
+ sections.push('');
678
+
679
+ // Format Alignment
680
+ sections.push('FORMAT RECOMMENDATION');
681
+ sections.push('─'.repeat(80));
682
+ sections.push(`Current Format: ${result.format.alignment.currentFormat.toUpperCase()}`);
683
+ sections.push(`Recommended Format: ${result.format.alignment.recommendedFormat.toUpperCase()}`);
684
+ sections.push(`Alignment: ${result.format.alignment.aligned ? '✅ ALIGNED' : '⚠️ MISALIGNED'}`);
685
+ sections.push(`Confidence: ${result.format.alignment.confidence.toUpperCase()}`);
686
+ sections.push(`Reason: ${result.format.alignment.reason}`);
687
+ if (result.format.alignment.evidence) {
688
+ sections.push(`Evidence: ${result.format.alignment.evidence}`);
689
+ }
690
+ if (result.format.alignment.impact) {
691
+ sections.push(`Expected Impact: ${result.format.alignment.impact}`);
692
+ }
693
+ sections.push('');
694
+
695
+ // Issues
696
+ if (result.frontmatter.issues.length > 0) {
697
+ sections.push('CRITICAL ISSUES');
698
+ sections.push('─'.repeat(80));
699
+ result.frontmatter.issues.forEach((issue, i) => {
700
+ sections.push(`${i + 1}. [${issue.field}] ${issue.message}`);
701
+ sections.push(` Fix: ${issue.fix}`);
702
+ sections.push('');
703
+ });
704
+ }
705
+
706
+ // Warnings
707
+ if (result.frontmatter.warnings.length > 0) {
708
+ sections.push('WARNINGS');
709
+ sections.push('─'.repeat(80));
710
+ result.frontmatter.warnings.forEach((warning, i) => {
711
+ sections.push(`${i + 1}. [${warning.field}] ${warning.message}`);
712
+ sections.push(` Fix: ${warning.fix}`);
713
+ sections.push('');
714
+ });
715
+ }
716
+
717
+ // Recommendations
718
+ if (result.quality.recommendations.length > 0) {
719
+ sections.push('IMPROVEMENT RECOMMENDATIONS');
720
+ sections.push('─'.repeat(80));
721
+ result.quality.recommendations.forEach((rec, i) => {
722
+ sections.push(`${i + 1}. [${rec.category}] ${rec.message}`);
723
+ if (rec.impact) sections.push(` Impact: ${rec.impact}`);
724
+ if (rec.example) sections.push(` Example: ${rec.example}`);
725
+ if (rec.fix) sections.push(` Fix: ${rec.fix}`);
726
+ sections.push('');
727
+ });
728
+ }
729
+
730
+ // Footer
731
+ sections.push('═'.repeat(80));
732
+ sections.push(`Compliance Score: ${result.complianceScore}/100`);
733
+ sections.push('═'.repeat(80));
734
+
735
+ return sections.join('\n');
736
+ }
737
+
738
+ /**
739
+ * Find all agent markdown files recursively
740
+ */
741
+ async function findAgentFiles(dir, results = []) {
742
+ const entries = await readdir(dir, { withFileTypes: true });
743
+
744
+ for (const entry of entries) {
745
+ const fullPath = join(dir, entry.name);
746
+
747
+ if (entry.isDirectory() && entry.name !== 'node_modules') {
748
+ await findAgentFiles(fullPath, results);
749
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
750
+ // Skip certain files
751
+ if (!entry.name.includes('README') && !entry.name.includes('CLAUDE_AGENT_DESIGN_PRINCIPLES')) {
752
+ results.push(fullPath);
753
+ }
754
+ }
755
+ }
756
+
757
+ return results;
758
+ }
759
+
760
+ /**
761
+ * Main CLI entry point
762
+ */
763
+ async function main() {
764
+ const args = process.argv.slice(2);
765
+
766
+ if (args.length === 0) {
767
+ console.error('Usage: node validate-agent.js <path-to-agent.md>');
768
+ console.error(' node validate-agent.js --all');
769
+ process.exit(1);
770
+ }
771
+
772
+ if (args[0] === '--all') {
773
+ // Validate all agents
774
+ const agentsDir = resolve(__dirname);
775
+ const agentFiles = await findAgentFiles(agentsDir);
776
+
777
+ console.log(`Found ${agentFiles.length} agent files to validate\n`);
778
+
779
+ const results = [];
780
+ for (const file of agentFiles) {
781
+ try {
782
+ const result = await validateAgent(file);
783
+ results.push(result);
784
+
785
+ // Print summary for each
786
+ const relPath = file.replace(agentsDir + '/', '');
787
+ console.log(`${relPath}: ${result.valid ? '✅' : '❌'} Score: ${result.complianceScore}/100`);
788
+ } catch (err) {
789
+ console.error(`Error validating ${file}: ${err.message}`);
790
+ }
791
+ }
792
+
793
+ // Summary statistics
794
+ console.log('\n' + '═'.repeat(80));
795
+ console.log('VALIDATION SUMMARY');
796
+ console.log('═'.repeat(80));
797
+ console.log(`Total Agents: ${results.length}`);
798
+ console.log(`Passed: ${results.filter(r => r.valid).length}`);
799
+ console.log(`Failed: ${results.filter(r => !r.valid).length}`);
800
+ console.log(`Average Score: ${(results.reduce((sum, r) => sum + r.complianceScore, 0) / results.length).toFixed(1)}/100`);
801
+ console.log('');
802
+
803
+ // Top performers
804
+ const topPerformers = results.sort((a, b) => b.complianceScore - a.complianceScore).slice(0, 5);
805
+ console.log('Top 5 Performers:');
806
+ topPerformers.forEach((r, i) => {
807
+ console.log(` ${i + 1}. ${basename(r.file)}: ${r.complianceScore}/100`);
808
+ });
809
+ console.log('');
810
+
811
+ // Needs improvement
812
+ const needsImprovement = results.filter(r => r.complianceScore < 75).length;
813
+ if (needsImprovement > 0) {
814
+ console.log(`⚠️ ${needsImprovement} agent(s) need improvement (score < 75)`);
815
+ }
816
+
817
+ } else {
818
+ // Validate single agent
819
+ const filePath = resolve(process.cwd(), args[0]);
820
+
821
+ try {
822
+ const result = await validateAgent(filePath);
823
+ console.log(formatOutput(result));
824
+
825
+ // Exit code based on validation result
826
+ process.exit(result.valid ? 0 : 1);
827
+ } catch (err) {
828
+ console.error(`Error: ${err.message}`);
829
+ console.error('Stack:', err.stack);
830
+ process.exit(1);
831
+ }
832
+ }
833
+ }
834
+
835
+ // Run if called directly
836
+ main().catch(err => {
837
+ console.error('Fatal error:', err);
838
+ process.exit(1);
839
+ });
840
+
841
+ export { validateAgent, classifyFormat, estimateComplexity, recommendFormat };