claude-flow-novice 1.5.3 → 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.
- package/.claude/agents/CLAUDE.md +2617 -0
- package/.claude/agents/CLAUDE_AGENT_DESIGN_PRINCIPLES.md +1312 -0
- package/.claude/agents/README-VALIDATION.md +243 -0
- package/.claude/agents/SPARSE_LANGUAGE_FINDINGS.md +991 -0
- package/.claude/agents/specialized/CODER_AGENT_GUIDELINES.md +1245 -0
- package/.claude/agents/validate-agent.js +841 -0
- package/.claude-flow-novice/.claude/agents/CLAUDE.md +2617 -0
- package/.claude-flow-novice/.claude/agents/CLAUDE_AGENT_DESIGN_PRINCIPLES.md +1312 -0
- package/.claude-flow-novice/.claude/agents/SPARSE_LANGUAGE_FINDINGS.md +991 -0
- package/.claude-flow-novice/.claude/agents/specialized/CODER_AGENT_GUIDELINES.md +1245 -0
- package/.claude-flow-novice/.claude/agents/validate-agent.js +841 -0
- package/.claude-flow-novice/dist/src/cli/simple-commands/init/index.js +1896 -0
- package/.claude-flow-novice/dist/src/cli/simple-commands/init.js +4 -0
- package/.claude-flow-novice/dist/src/slash-commands/claude-md.js +22 -9
- package/package.json +3 -2
- package/scripts/migrate-to-sdk.sh +520 -0
- package/scripts/monitor-migration.js +339 -0
- package/scripts/rollback-sdk.sh +444 -0
- package/scripts/verify-sdk-phase1.cjs +293 -0
- package/src/cli/simple-commands/init/index.js +3 -3
- package/src/slash-commands/claude-md.js +22 -9
|
@@ -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 };
|