@claude-flow/codex 3.0.0-alpha.1

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.
Files changed (46) hide show
  1. package/README.md +301 -0
  2. package/dist/cli.d.ts +9 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +649 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/generators/agents-md.d.ts +12 -0
  7. package/dist/generators/agents-md.d.ts.map +1 -0
  8. package/dist/generators/agents-md.js +641 -0
  9. package/dist/generators/agents-md.js.map +1 -0
  10. package/dist/generators/config-toml.d.ts +74 -0
  11. package/dist/generators/config-toml.d.ts.map +1 -0
  12. package/dist/generators/config-toml.js +910 -0
  13. package/dist/generators/config-toml.js.map +1 -0
  14. package/dist/generators/index.d.ts +9 -0
  15. package/dist/generators/index.d.ts.map +1 -0
  16. package/dist/generators/index.js +9 -0
  17. package/dist/generators/index.js.map +1 -0
  18. package/dist/generators/skill-md.d.ts +20 -0
  19. package/dist/generators/skill-md.d.ts.map +1 -0
  20. package/dist/generators/skill-md.js +946 -0
  21. package/dist/generators/skill-md.js.map +1 -0
  22. package/dist/index.d.ts +45 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +46 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/initializer.d.ts +87 -0
  27. package/dist/initializer.d.ts.map +1 -0
  28. package/dist/initializer.js +666 -0
  29. package/dist/initializer.js.map +1 -0
  30. package/dist/migrations/index.d.ts +114 -0
  31. package/dist/migrations/index.d.ts.map +1 -0
  32. package/dist/migrations/index.js +856 -0
  33. package/dist/migrations/index.js.map +1 -0
  34. package/dist/templates/index.d.ts +92 -0
  35. package/dist/templates/index.d.ts.map +1 -0
  36. package/dist/templates/index.js +284 -0
  37. package/dist/templates/index.js.map +1 -0
  38. package/dist/types.d.ts +218 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +8 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/validators/index.d.ts +42 -0
  43. package/dist/validators/index.d.ts.map +1 -0
  44. package/dist/validators/index.js +929 -0
  45. package/dist/validators/index.js.map +1 -0
  46. package/package.json +88 -0
@@ -0,0 +1,929 @@
1
+ /**
2
+ * @claude-flow/codex - Validators
3
+ *
4
+ * Comprehensive validation functions for AGENTS.md, SKILL.md, and config.toml
5
+ * Provides detailed error messages and suggestions for fixes.
6
+ */
7
+ /**
8
+ * Secret patterns to detect
9
+ */
10
+ const SECRET_PATTERNS = [
11
+ { pattern: /sk-[a-zA-Z0-9]{32,}/, name: 'OpenAI API key' },
12
+ { pattern: /sk-ant-[a-zA-Z0-9-]{32,}/, name: 'Anthropic API key' },
13
+ { pattern: /ghp_[a-zA-Z0-9]{36}/, name: 'GitHub personal access token' },
14
+ { pattern: /gho_[a-zA-Z0-9]{36}/, name: 'GitHub OAuth token' },
15
+ { pattern: /github_pat_[a-zA-Z0-9_]{22,}/, name: 'GitHub fine-grained token' },
16
+ { pattern: /xox[baprs]-[a-zA-Z0-9-]{10,}/, name: 'Slack token' },
17
+ { pattern: /AKIA[A-Z0-9]{16}/, name: 'AWS access key' },
18
+ { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*["']?[a-zA-Z0-9_-]{20,}["']?/i, name: 'Generic API key' },
19
+ { pattern: /(?:password|passwd|pwd)\s*[:=]\s*["'][^"']{8,}["']/i, name: 'Hardcoded password' },
20
+ { pattern: /(?:secret|token)\s*[:=]\s*["'][a-zA-Z0-9_/-]{16,}["']/i, name: 'Hardcoded secret/token' },
21
+ { pattern: /Bearer\s+[a-zA-Z0-9_.-]{20,}/, name: 'Bearer token' },
22
+ { pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/, name: 'Private key' },
23
+ ];
24
+ /**
25
+ * Required sections for AGENTS.md
26
+ */
27
+ const AGENTS_MD_REQUIRED_SECTIONS = ['Setup', 'Code Standards', 'Security'];
28
+ /**
29
+ * Recommended sections for AGENTS.md
30
+ */
31
+ const AGENTS_MD_RECOMMENDED_SECTIONS = [
32
+ 'Project Overview',
33
+ 'Skills',
34
+ 'Agent Types',
35
+ 'Memory System',
36
+ 'Links',
37
+ ];
38
+ /**
39
+ * Valid approval policies
40
+ */
41
+ const VALID_APPROVAL_POLICIES = ['untrusted', 'on-failure', 'on-request', 'never'];
42
+ /**
43
+ * Valid sandbox modes
44
+ */
45
+ const VALID_SANDBOX_MODES = ['read-only', 'workspace-write', 'danger-full-access'];
46
+ /**
47
+ * Valid web search modes
48
+ */
49
+ const VALID_WEB_SEARCH_MODES = ['disabled', 'cached', 'live'];
50
+ /**
51
+ * Required config.toml fields
52
+ */
53
+ const CONFIG_TOML_REQUIRED_FIELDS = ['model', 'approval_policy', 'sandbox_mode'];
54
+ /**
55
+ * Validate an AGENTS.md file
56
+ */
57
+ export async function validateAgentsMd(content) {
58
+ const errors = [];
59
+ const warnings = [];
60
+ const lines = content.split('\n');
61
+ // Check for title (H1 heading)
62
+ if (!content.startsWith('# ')) {
63
+ const firstHeadingMatch = content.match(/^(#{1,6})\s+/m);
64
+ if (firstHeadingMatch && firstHeadingMatch[1]) {
65
+ if (firstHeadingMatch[1].length > 1) {
66
+ errors.push({
67
+ path: 'AGENTS.md',
68
+ message: 'AGENTS.md should start with a level-1 heading (# Title)',
69
+ line: 1,
70
+ });
71
+ }
72
+ }
73
+ else {
74
+ errors.push({
75
+ path: 'AGENTS.md',
76
+ message: 'AGENTS.md must start with a title heading',
77
+ line: 1,
78
+ });
79
+ }
80
+ }
81
+ // Check for empty content
82
+ if (content.trim().length < 50) {
83
+ errors.push({
84
+ path: 'AGENTS.md',
85
+ message: 'AGENTS.md content is too short - add meaningful instructions',
86
+ line: 1,
87
+ });
88
+ }
89
+ // Extract sections
90
+ const sections = extractSections(content);
91
+ const sectionTitles = sections.map((s) => s.title.toLowerCase());
92
+ // Check for required sections
93
+ for (const required of AGENTS_MD_REQUIRED_SECTIONS) {
94
+ const found = sectionTitles.some((t) => t.includes(required.toLowerCase()) || t === required.toLowerCase());
95
+ if (!found) {
96
+ warnings.push({
97
+ path: 'AGENTS.md',
98
+ message: `Missing recommended section: ## ${required}`,
99
+ suggestion: `Add a "## ${required}" section for better agent guidance`,
100
+ });
101
+ }
102
+ }
103
+ // Check for recommended sections
104
+ for (const recommended of AGENTS_MD_RECOMMENDED_SECTIONS) {
105
+ const found = sectionTitles.some((t) => t.includes(recommended.toLowerCase()) || t === recommended.toLowerCase());
106
+ if (!found) {
107
+ warnings.push({
108
+ path: 'AGENTS.md',
109
+ message: `Consider adding section: ## ${recommended}`,
110
+ suggestion: `A "${recommended}" section would improve agent understanding`,
111
+ });
112
+ }
113
+ }
114
+ // Check for hardcoded secrets
115
+ for (let i = 0; i < lines.length; i++) {
116
+ const line = lines[i];
117
+ for (const { pattern, name } of SECRET_PATTERNS) {
118
+ if (pattern.test(line)) {
119
+ errors.push({
120
+ path: 'AGENTS.md',
121
+ message: `Potential ${name} detected - never commit secrets`,
122
+ line: i + 1,
123
+ });
124
+ }
125
+ }
126
+ }
127
+ // Check for skill references
128
+ const dollarSkillPattern = /\$([a-z][a-z0-9-]+)/g;
129
+ const slashSkillPattern = /\/([a-z][a-z0-9-]+)/g;
130
+ const dollarSkills = content.match(dollarSkillPattern) || [];
131
+ const slashSkills = content.match(slashSkillPattern) || [];
132
+ if (dollarSkills.length === 0 && slashSkills.length === 0) {
133
+ warnings.push({
134
+ path: 'AGENTS.md',
135
+ message: 'No skill references found',
136
+ suggestion: 'Add skill references using $skill-name syntax (Codex) or /skill-name (Claude Code)',
137
+ });
138
+ }
139
+ // Warn about slash syntax (Claude Code style)
140
+ if (slashSkills.length > 0 && dollarSkills.length === 0) {
141
+ warnings.push({
142
+ path: 'AGENTS.md',
143
+ message: 'Using Claude Code skill syntax (/skill-name)',
144
+ suggestion: 'Codex uses $skill-name syntax. Consider migrating for full compatibility.',
145
+ });
146
+ }
147
+ // Check for code blocks
148
+ const codeBlockCount = (content.match(/```/g) || []).length / 2;
149
+ if (codeBlockCount < 1) {
150
+ warnings.push({
151
+ path: 'AGENTS.md',
152
+ message: 'No code examples found',
153
+ suggestion: 'Add code examples in fenced code blocks (```) to guide agent behavior',
154
+ });
155
+ }
156
+ // Check for common issues
157
+ checkCommonIssues(content, lines, errors, warnings);
158
+ // Check structure
159
+ validateMarkdownStructure(content, lines, errors, warnings);
160
+ return {
161
+ valid: errors.length === 0,
162
+ errors,
163
+ warnings,
164
+ };
165
+ }
166
+ /**
167
+ * Validate a SKILL.md file
168
+ */
169
+ export async function validateSkillMd(content) {
170
+ const errors = [];
171
+ const warnings = [];
172
+ const lines = content.split('\n');
173
+ // Check for YAML frontmatter
174
+ if (!content.startsWith('---')) {
175
+ errors.push({
176
+ path: 'SKILL.md',
177
+ message: 'SKILL.md must start with YAML frontmatter (---)',
178
+ line: 1,
179
+ });
180
+ return { valid: false, errors, warnings };
181
+ }
182
+ // Parse YAML frontmatter
183
+ const frontmatterResult = parseYamlFrontmatter(content);
184
+ if (!frontmatterResult.valid) {
185
+ for (const err of frontmatterResult.errors) {
186
+ errors.push({
187
+ path: 'SKILL.md',
188
+ message: err.message,
189
+ line: err.line,
190
+ });
191
+ }
192
+ return { valid: false, errors, warnings };
193
+ }
194
+ const frontmatter = frontmatterResult.data;
195
+ // Check required frontmatter fields
196
+ const requiredFields = ['name', 'description'];
197
+ for (const field of requiredFields) {
198
+ if (!(field in frontmatter)) {
199
+ errors.push({
200
+ path: 'SKILL.md',
201
+ message: `Missing required frontmatter field: ${field}`,
202
+ line: 2,
203
+ });
204
+ }
205
+ else if (typeof frontmatter[field] !== 'string' || frontmatter[field].trim() === '') {
206
+ errors.push({
207
+ path: 'SKILL.md',
208
+ message: `Field "${field}" must be a non-empty string`,
209
+ line: 2,
210
+ });
211
+ }
212
+ }
213
+ // Validate name format
214
+ if (frontmatter.name && typeof frontmatter.name === 'string') {
215
+ const name = frontmatter.name;
216
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
217
+ errors.push({
218
+ path: 'SKILL.md',
219
+ message: `Skill name "${name}" must be lowercase with hyphens only (e.g., my-skill)`,
220
+ line: 2,
221
+ });
222
+ }
223
+ if (name.length > 50) {
224
+ warnings.push({
225
+ path: 'SKILL.md',
226
+ message: 'Skill name is very long',
227
+ suggestion: 'Keep skill names under 50 characters for readability',
228
+ });
229
+ }
230
+ }
231
+ // Check optional but recommended fields
232
+ const recommendedFields = ['version', 'author', 'tags'];
233
+ for (const field of recommendedFields) {
234
+ if (!(field in frontmatter)) {
235
+ warnings.push({
236
+ path: 'SKILL.md',
237
+ message: `Consider adding field: ${field}`,
238
+ suggestion: `Adding "${field}" improves skill discoverability`,
239
+ });
240
+ }
241
+ }
242
+ // Check for model field (should specify min requirements)
243
+ if (frontmatter.model) {
244
+ warnings.push({
245
+ path: 'SKILL.md',
246
+ message: 'Model specification found in frontmatter',
247
+ suggestion: 'Model requirements are informational - skills work with any capable model',
248
+ });
249
+ }
250
+ // Get body content (after frontmatter)
251
+ const bodyStartLine = frontmatterResult.endLine + 1;
252
+ const body = lines.slice(bodyStartLine).join('\n');
253
+ // Check for Purpose section
254
+ if (!body.includes('## Purpose') && !body.includes('## Overview')) {
255
+ warnings.push({
256
+ path: 'SKILL.md',
257
+ message: 'Missing Purpose or Overview section',
258
+ suggestion: 'Add a "## Purpose" section to describe what the skill does',
259
+ });
260
+ }
261
+ // Check for trigger conditions
262
+ const hasTriggers = body.includes('When to Trigger') ||
263
+ body.includes('When to Use') ||
264
+ body.includes('Triggers') ||
265
+ (frontmatter.triggers && Array.isArray(frontmatter.triggers));
266
+ if (!hasTriggers) {
267
+ warnings.push({
268
+ path: 'SKILL.md',
269
+ message: 'Missing trigger conditions',
270
+ suggestion: 'Add a section or frontmatter field describing when to trigger this skill',
271
+ });
272
+ }
273
+ // Check for skip conditions
274
+ const hasSkipWhen = body.includes('Skip When') ||
275
+ body.includes('When to Skip') ||
276
+ (frontmatter.skip_when && Array.isArray(frontmatter.skip_when));
277
+ if (!hasSkipWhen) {
278
+ warnings.push({
279
+ path: 'SKILL.md',
280
+ message: 'No skip conditions defined',
281
+ suggestion: 'Consider adding skip conditions to prevent unnecessary skill invocation',
282
+ });
283
+ }
284
+ // Check for examples
285
+ const hasExamples = body.includes('## Example') || body.includes('```');
286
+ if (!hasExamples) {
287
+ warnings.push({
288
+ path: 'SKILL.md',
289
+ message: 'No examples provided',
290
+ suggestion: 'Add usage examples to help agents understand skill application',
291
+ });
292
+ }
293
+ // Check for secrets in content
294
+ for (let i = 0; i < lines.length; i++) {
295
+ const line = lines[i];
296
+ for (const { pattern, name } of SECRET_PATTERNS) {
297
+ if (pattern.test(line)) {
298
+ errors.push({
299
+ path: 'SKILL.md',
300
+ message: `Potential ${name} detected - never commit secrets`,
301
+ line: i + 1,
302
+ });
303
+ }
304
+ }
305
+ }
306
+ return {
307
+ valid: errors.length === 0,
308
+ errors,
309
+ warnings,
310
+ };
311
+ }
312
+ /**
313
+ * Validate a config.toml file
314
+ */
315
+ export async function validateConfigToml(content) {
316
+ const errors = [];
317
+ const warnings = [];
318
+ const lines = content.split('\n');
319
+ // Parse TOML
320
+ const parseResult = parseToml(content);
321
+ if (!parseResult.valid) {
322
+ for (const err of parseResult.errors) {
323
+ errors.push({
324
+ path: 'config.toml',
325
+ message: err.message,
326
+ line: err.line,
327
+ });
328
+ }
329
+ return { valid: false, errors, warnings };
330
+ }
331
+ const config = parseResult.data;
332
+ // Check for required fields
333
+ for (const field of CONFIG_TOML_REQUIRED_FIELDS) {
334
+ const fieldLine = findFieldLine(lines, field);
335
+ if (!content.includes(`${field} =`) && !content.includes(`${field}=`)) {
336
+ errors.push({
337
+ path: 'config.toml',
338
+ message: `Missing required field: ${field}`,
339
+ line: fieldLine,
340
+ });
341
+ }
342
+ }
343
+ // Validate model field
344
+ if (config.model) {
345
+ const model = config.model;
346
+ if (typeof model !== 'string') {
347
+ errors.push({
348
+ path: 'config.toml',
349
+ message: 'model must be a string',
350
+ line: findFieldLine(lines, 'model'),
351
+ });
352
+ }
353
+ }
354
+ // Validate approval_policy value
355
+ const approvalMatch = content.match(/approval_policy\s*=\s*"([^"]+)"/);
356
+ if (approvalMatch) {
357
+ const policy = approvalMatch[1];
358
+ if (!VALID_APPROVAL_POLICIES.includes(policy)) {
359
+ errors.push({
360
+ path: 'config.toml',
361
+ message: `Invalid approval_policy: "${policy}". Valid values: ${VALID_APPROVAL_POLICIES.join(', ')}`,
362
+ line: findFieldLine(lines, 'approval_policy'),
363
+ });
364
+ }
365
+ }
366
+ // Validate sandbox_mode value
367
+ const sandboxMatch = content.match(/sandbox_mode\s*=\s*"([^"]+)"/);
368
+ if (sandboxMatch) {
369
+ const mode = sandboxMatch[1];
370
+ if (!VALID_SANDBOX_MODES.includes(mode)) {
371
+ errors.push({
372
+ path: 'config.toml',
373
+ message: `Invalid sandbox_mode: "${mode}". Valid values: ${VALID_SANDBOX_MODES.join(', ')}`,
374
+ line: findFieldLine(lines, 'sandbox_mode'),
375
+ });
376
+ }
377
+ }
378
+ // Validate web_search value
379
+ const webSearchMatch = content.match(/web_search\s*=\s*"([^"]+)"/);
380
+ if (webSearchMatch) {
381
+ const mode = webSearchMatch[1];
382
+ if (!VALID_WEB_SEARCH_MODES.includes(mode)) {
383
+ errors.push({
384
+ path: 'config.toml',
385
+ message: `Invalid web_search: "${mode}". Valid values: ${VALID_WEB_SEARCH_MODES.join(', ')}`,
386
+ line: findFieldLine(lines, 'web_search'),
387
+ });
388
+ }
389
+ }
390
+ // Check for MCP servers section
391
+ if (!content.includes('[mcp_servers')) {
392
+ warnings.push({
393
+ path: 'config.toml',
394
+ message: 'No MCP servers configured',
395
+ suggestion: 'Add [mcp_servers.claude-flow] for Claude Flow integration',
396
+ });
397
+ }
398
+ else {
399
+ // Validate MCP server configurations
400
+ validateMcpServers(content, lines, errors, warnings);
401
+ }
402
+ // Check for features section
403
+ if (!content.includes('[features]')) {
404
+ warnings.push({
405
+ path: 'config.toml',
406
+ message: 'No [features] section found',
407
+ suggestion: 'Add [features] section to configure Codex behavior',
408
+ });
409
+ }
410
+ // Security warnings for dangerous settings
411
+ if (content.includes('approval_policy = "never"')) {
412
+ if (!content.includes('[profiles.')) {
413
+ warnings.push({
414
+ path: 'config.toml',
415
+ message: 'Using "never" approval policy globally',
416
+ suggestion: 'Consider restricting to dev profile: [profiles.dev] approval_policy = "never"',
417
+ });
418
+ }
419
+ }
420
+ if (content.includes('sandbox_mode = "danger-full-access"')) {
421
+ warnings.push({
422
+ path: 'config.toml',
423
+ message: 'Using "danger-full-access" sandbox mode',
424
+ suggestion: 'This gives unrestricted file system access. Use only in trusted environments.',
425
+ });
426
+ }
427
+ // Check for secrets
428
+ for (let i = 0; i < lines.length; i++) {
429
+ const line = lines[i];
430
+ // Skip comment lines
431
+ if (line.trim().startsWith('#'))
432
+ continue;
433
+ for (const { pattern, name } of SECRET_PATTERNS) {
434
+ if (pattern.test(line)) {
435
+ errors.push({
436
+ path: 'config.toml',
437
+ message: `Potential ${name} detected - use environment variables instead`,
438
+ line: i + 1,
439
+ });
440
+ }
441
+ }
442
+ // Check for inline secrets in env sections
443
+ if (line.includes('_KEY =') || line.includes('_SECRET =') || line.includes('_TOKEN =')) {
444
+ const valueMatch = line.match(/=\s*"([^"]+)"/);
445
+ if (valueMatch && valueMatch[1] && !valueMatch[1].startsWith('$')) {
446
+ warnings.push({
447
+ path: 'config.toml',
448
+ message: 'Hardcoded credential detected',
449
+ suggestion: `Use environment variable reference: $ENV_VAR_NAME instead of "${valueMatch[1]}"`,
450
+ });
451
+ }
452
+ }
453
+ }
454
+ // Validate project_doc_max_bytes if present
455
+ const maxBytesMatch = content.match(/project_doc_max_bytes\s*=\s*(\d+)/);
456
+ if (maxBytesMatch) {
457
+ const bytes = parseInt(maxBytesMatch[1], 10);
458
+ if (bytes < 1024) {
459
+ warnings.push({
460
+ path: 'config.toml',
461
+ message: `project_doc_max_bytes is very low (${bytes} bytes)`,
462
+ suggestion: 'Consider increasing to at least 65536 for reasonable AGENTS.md support',
463
+ });
464
+ }
465
+ else if (bytes > 1048576) {
466
+ warnings.push({
467
+ path: 'config.toml',
468
+ message: `project_doc_max_bytes is very high (${bytes} bytes = ${(bytes / 1024 / 1024).toFixed(1)} MB)`,
469
+ suggestion: 'Large values may impact performance. Default is 65536.',
470
+ });
471
+ }
472
+ }
473
+ // Check profiles
474
+ validateProfiles(content, lines, errors, warnings);
475
+ return {
476
+ valid: errors.length === 0,
477
+ errors,
478
+ warnings,
479
+ };
480
+ }
481
+ /**
482
+ * Validate all files in a project
483
+ */
484
+ export async function validateProject(files) {
485
+ const results = {};
486
+ let totalErrors = 0;
487
+ let totalWarnings = 0;
488
+ if (files.agentsMd) {
489
+ results['AGENTS.md'] = await validateAgentsMd(files.agentsMd);
490
+ totalErrors += results['AGENTS.md'].errors.length;
491
+ totalWarnings += results['AGENTS.md'].warnings.length;
492
+ }
493
+ if (files.skillMds) {
494
+ for (const skill of files.skillMds) {
495
+ const key = `skills/${skill.name}`;
496
+ results[key] = await validateSkillMd(skill.content);
497
+ totalErrors += results[key].errors.length;
498
+ totalWarnings += results[key].warnings.length;
499
+ }
500
+ }
501
+ if (files.configToml) {
502
+ results['config.toml'] = await validateConfigToml(files.configToml);
503
+ totalErrors += results['config.toml'].errors.length;
504
+ totalWarnings += results['config.toml'].warnings.length;
505
+ }
506
+ return {
507
+ valid: totalErrors === 0,
508
+ results,
509
+ summary: { errors: totalErrors, warnings: totalWarnings },
510
+ };
511
+ }
512
+ /**
513
+ * Generate a validation report
514
+ */
515
+ export function generateValidationReport(results) {
516
+ const lines = [];
517
+ lines.push('# Validation Report');
518
+ lines.push('');
519
+ let totalErrors = 0;
520
+ let totalWarnings = 0;
521
+ for (const [file, result] of Object.entries(results)) {
522
+ totalErrors += result.errors.length;
523
+ totalWarnings += result.warnings.length;
524
+ lines.push(`## ${file}`);
525
+ lines.push('');
526
+ lines.push(`**Status**: ${result.valid ? 'Valid' : 'Invalid'}`);
527
+ lines.push('');
528
+ if (result.errors.length > 0) {
529
+ lines.push('### Errors');
530
+ lines.push('');
531
+ for (const error of result.errors) {
532
+ const lineInfo = error.line ? ` (line ${error.line})` : '';
533
+ lines.push(`- ${error.message}${lineInfo}`);
534
+ }
535
+ lines.push('');
536
+ }
537
+ if (result.warnings.length > 0) {
538
+ lines.push('### Warnings');
539
+ lines.push('');
540
+ for (const warning of result.warnings) {
541
+ lines.push(`- ${warning.message}`);
542
+ if (warning.suggestion) {
543
+ lines.push(` - Suggestion: ${warning.suggestion}`);
544
+ }
545
+ }
546
+ lines.push('');
547
+ }
548
+ if (result.errors.length === 0 && result.warnings.length === 0) {
549
+ lines.push('No issues found.');
550
+ lines.push('');
551
+ }
552
+ }
553
+ lines.push('## Summary');
554
+ lines.push('');
555
+ lines.push(`- Total Errors: ${totalErrors}`);
556
+ lines.push(`- Total Warnings: ${totalWarnings}`);
557
+ lines.push(`- Overall Status: ${totalErrors === 0 ? 'PASS' : 'FAIL'}`);
558
+ lines.push('');
559
+ return lines.join('\n');
560
+ }
561
+ // ============================================================================
562
+ // Helper Functions
563
+ // ============================================================================
564
+ /**
565
+ * Extract sections from markdown content
566
+ */
567
+ function extractSections(content) {
568
+ const sections = [];
569
+ const lines = content.split('\n');
570
+ for (let i = 0; i < lines.length; i++) {
571
+ const line = lines[i];
572
+ if (!line)
573
+ continue;
574
+ const match = line.match(/^(#{1,6})\s+(.+)$/);
575
+ if (match && match[1] && match[2]) {
576
+ sections.push({
577
+ level: match[1].length,
578
+ title: match[2].trim(),
579
+ line: i + 1,
580
+ });
581
+ }
582
+ }
583
+ return sections;
584
+ }
585
+ /**
586
+ * Parse YAML frontmatter
587
+ */
588
+ function parseYamlFrontmatter(content) {
589
+ const result = {
590
+ valid: false,
591
+ errors: [],
592
+ data: {},
593
+ endLine: 0,
594
+ };
595
+ if (!content.startsWith('---')) {
596
+ result.errors.push({ line: 1, message: 'Missing opening ---' });
597
+ return result;
598
+ }
599
+ const lines = content.split('\n');
600
+ let endLineIndex = -1;
601
+ // Find closing ---
602
+ for (let i = 1; i < lines.length; i++) {
603
+ if (lines[i].trim() === '---') {
604
+ endLineIndex = i;
605
+ break;
606
+ }
607
+ }
608
+ if (endLineIndex === -1) {
609
+ result.errors.push({ line: 1, message: 'YAML frontmatter not properly closed (missing closing ---)' });
610
+ return result;
611
+ }
612
+ result.endLine = endLineIndex;
613
+ // Parse YAML content (simple key: value parsing)
614
+ const yamlLines = lines.slice(1, endLineIndex);
615
+ for (let i = 0; i < yamlLines.length; i++) {
616
+ const line = yamlLines[i].trim();
617
+ if (line === '' || line.startsWith('#'))
618
+ continue;
619
+ // Simple key: value parsing
620
+ const colonIndex = line.indexOf(':');
621
+ if (colonIndex === -1) {
622
+ // Could be a list item or continuation
623
+ continue;
624
+ }
625
+ const key = line.substring(0, colonIndex).trim();
626
+ let value = line.substring(colonIndex + 1).trim();
627
+ // Parse value type
628
+ if (value === '') {
629
+ value = null;
630
+ }
631
+ else if (value === 'true') {
632
+ value = true;
633
+ }
634
+ else if (value === 'false') {
635
+ value = false;
636
+ }
637
+ else if (/^-?\d+$/.test(value)) {
638
+ value = parseInt(value, 10);
639
+ }
640
+ else if (/^-?\d+\.\d+$/.test(value)) {
641
+ value = parseFloat(value);
642
+ }
643
+ else if (value.startsWith('"') && value.endsWith('"')) {
644
+ value = value.slice(1, -1);
645
+ }
646
+ else if (value.startsWith("'") && value.endsWith("'")) {
647
+ value = value.slice(1, -1);
648
+ }
649
+ else if (value.startsWith('[') && value.endsWith(']')) {
650
+ // Simple inline array
651
+ try {
652
+ value = JSON.parse(value.replace(/'/g, '"'));
653
+ }
654
+ catch {
655
+ // Keep as string if not valid JSON
656
+ }
657
+ }
658
+ if (key) {
659
+ result.data[key] = value;
660
+ }
661
+ }
662
+ result.valid = true;
663
+ return result;
664
+ }
665
+ /**
666
+ * Parse TOML content (simplified parser)
667
+ */
668
+ function parseToml(content) {
669
+ const result = {
670
+ valid: true,
671
+ errors: [],
672
+ data: {},
673
+ };
674
+ const lines = content.split('\n');
675
+ let currentSection = '';
676
+ for (let i = 0; i < lines.length; i++) {
677
+ const line = lines[i].trim();
678
+ // Skip empty lines and comments
679
+ if (line === '' || line.startsWith('#'))
680
+ continue;
681
+ // Section header
682
+ if (line.startsWith('[')) {
683
+ if (!line.endsWith(']')) {
684
+ result.errors.push({
685
+ line: i + 1,
686
+ message: `Invalid section header: ${line} (missing closing bracket)`,
687
+ });
688
+ result.valid = false;
689
+ continue;
690
+ }
691
+ currentSection = line.slice(1, -1);
692
+ continue;
693
+ }
694
+ // Key = value
695
+ const eqIndex = line.indexOf('=');
696
+ if (eqIndex === -1) {
697
+ // Could be array continuation or error
698
+ if (!line.startsWith('"') && !line.startsWith("'") && !line.startsWith(']')) {
699
+ result.errors.push({
700
+ line: i + 1,
701
+ message: `Invalid line: ${line} (expected key = value)`,
702
+ });
703
+ result.valid = false;
704
+ }
705
+ continue;
706
+ }
707
+ const key = line.substring(0, eqIndex).trim();
708
+ const valueStr = line.substring(eqIndex + 1).trim();
709
+ // Validate key format
710
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
711
+ result.errors.push({
712
+ line: i + 1,
713
+ message: `Invalid key format: ${key}`,
714
+ });
715
+ result.valid = false;
716
+ continue;
717
+ }
718
+ // Parse value
719
+ let value = valueStr;
720
+ if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
721
+ value = valueStr.slice(1, -1);
722
+ }
723
+ else if (valueStr.startsWith("'") && valueStr.endsWith("'")) {
724
+ value = valueStr.slice(1, -1);
725
+ }
726
+ else if (valueStr === 'true') {
727
+ value = true;
728
+ }
729
+ else if (valueStr === 'false') {
730
+ value = false;
731
+ }
732
+ else if (/^-?\d+$/.test(valueStr)) {
733
+ value = parseInt(valueStr, 10);
734
+ }
735
+ else if (/^-?\d+\.\d+$/.test(valueStr)) {
736
+ value = parseFloat(valueStr);
737
+ }
738
+ else if (valueStr.startsWith('[')) {
739
+ // Array - simplified handling
740
+ value = valueStr;
741
+ }
742
+ // Store in nested structure
743
+ if (currentSection) {
744
+ if (!result.data[currentSection]) {
745
+ result.data[currentSection] = {};
746
+ }
747
+ result.data[currentSection][key] = value;
748
+ }
749
+ else {
750
+ result.data[key] = value;
751
+ }
752
+ }
753
+ return result;
754
+ }
755
+ /**
756
+ * Find the line number for a field
757
+ */
758
+ function findFieldLine(lines, field) {
759
+ for (let i = 0; i < lines.length; i++) {
760
+ if (lines[i].includes(`${field} =`) || lines[i].includes(`${field}=`)) {
761
+ return i + 1;
762
+ }
763
+ }
764
+ return 1;
765
+ }
766
+ /**
767
+ * Check for common issues in content
768
+ */
769
+ function checkCommonIssues(content, lines, errors, warnings) {
770
+ // Check for broken links
771
+ const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
772
+ let match;
773
+ while ((match = linkPattern.exec(content)) !== null) {
774
+ const url = match[2];
775
+ if (url.startsWith('http') && !url.startsWith('https://')) {
776
+ const line = findLineNumber(content, match.index);
777
+ warnings.push({
778
+ path: 'AGENTS.md',
779
+ message: `Non-HTTPS URL found: ${url}`,
780
+ suggestion: 'Use HTTPS URLs for security',
781
+ });
782
+ }
783
+ }
784
+ // Check for TODO/FIXME comments
785
+ for (let i = 0; i < lines.length; i++) {
786
+ const line = lines[i];
787
+ if (/\b(TODO|FIXME|XXX|HACK)\b/i.test(line)) {
788
+ warnings.push({
789
+ path: 'AGENTS.md',
790
+ message: `Incomplete item found: ${line.trim().substring(0, 50)}...`,
791
+ suggestion: 'Complete or remove TODO/FIXME items before deployment',
792
+ });
793
+ }
794
+ }
795
+ // Check for placeholder content
796
+ const placeholderPatterns = [
797
+ /\[your[- ].*\]/i,
798
+ /\[insert[- ].*\]/i,
799
+ /\[add[- ].*\]/i,
800
+ /\{your[- ].*\}/i,
801
+ /<your[- ].*>/i,
802
+ ];
803
+ for (const pattern of placeholderPatterns) {
804
+ if (pattern.test(content)) {
805
+ warnings.push({
806
+ path: 'AGENTS.md',
807
+ message: 'Placeholder content detected',
808
+ suggestion: 'Replace placeholder text with actual content',
809
+ });
810
+ break;
811
+ }
812
+ }
813
+ }
814
+ /**
815
+ * Validate markdown structure
816
+ */
817
+ function validateMarkdownStructure(content, lines, errors, warnings) {
818
+ // Check heading hierarchy
819
+ const headings = extractSections(content);
820
+ let prevLevel = 0;
821
+ for (const heading of headings) {
822
+ if (heading.level > prevLevel + 1 && prevLevel > 0) {
823
+ warnings.push({
824
+ path: 'AGENTS.md',
825
+ message: `Heading level jumps from H${prevLevel} to H${heading.level}`,
826
+ suggestion: `Use H${prevLevel + 1} instead of H${heading.level} for proper hierarchy`,
827
+ });
828
+ }
829
+ prevLevel = heading.level;
830
+ }
831
+ // Check for unclosed code blocks
832
+ // Count all triple backticks - they should come in pairs
833
+ const tripleBackticks = (content.match(/```/g) || []).length;
834
+ if (tripleBackticks % 2 !== 0) {
835
+ errors.push({
836
+ path: 'AGENTS.md',
837
+ message: 'Unclosed code block detected (odd number of ``` markers)',
838
+ line: 1,
839
+ });
840
+ }
841
+ // Check for very long lines
842
+ for (let i = 0; i < lines.length; i++) {
843
+ if (lines[i].length > 500) {
844
+ warnings.push({
845
+ path: 'AGENTS.md',
846
+ message: `Very long line (${lines[i].length} chars) at line ${i + 1}`,
847
+ suggestion: 'Consider breaking into multiple lines for readability',
848
+ });
849
+ }
850
+ }
851
+ }
852
+ /**
853
+ * Find line number for a character index
854
+ */
855
+ function findLineNumber(content, index) {
856
+ return content.substring(0, index).split('\n').length;
857
+ }
858
+ /**
859
+ * Validate MCP server configurations
860
+ */
861
+ function validateMcpServers(content, lines, errors, warnings) {
862
+ // Find all MCP server sections
863
+ const serverRegex = /\[mcp_servers\.([^\]]+)\]/g;
864
+ const servers = [];
865
+ let match;
866
+ while ((match = serverRegex.exec(content)) !== null) {
867
+ servers.push(match[1]);
868
+ }
869
+ for (const serverName of servers) {
870
+ // Check if server has command
871
+ const serverSection = content.match(new RegExp(`\\[mcp_servers\\.${serverName.replace('.', '\\.')}\\][\\s\\S]*?(?=\\[|$)`));
872
+ if (serverSection) {
873
+ const section = serverSection[0];
874
+ if (!section.includes('command =')) {
875
+ errors.push({
876
+ path: 'config.toml',
877
+ message: `MCP server "${serverName}" missing required "command" field`,
878
+ line: findFieldLine(lines, `[mcp_servers.${serverName}]`),
879
+ });
880
+ }
881
+ // Check for enabled = false (info)
882
+ if (section.includes('enabled = false')) {
883
+ warnings.push({
884
+ path: 'config.toml',
885
+ message: `MCP server "${serverName}" is disabled`,
886
+ suggestion: 'Set enabled = true to activate this server',
887
+ });
888
+ }
889
+ }
890
+ }
891
+ }
892
+ /**
893
+ * Validate profiles
894
+ */
895
+ function validateProfiles(content, lines, errors, warnings) {
896
+ const profileRegex = /\[profiles\.([^\]]+)\]/g;
897
+ const profiles = [];
898
+ let match;
899
+ while ((match = profileRegex.exec(content)) !== null) {
900
+ profiles.push(match[1]);
901
+ }
902
+ // Suggest common profiles if missing
903
+ const recommendedProfiles = ['dev', 'safe', 'ci'];
904
+ for (const profile of recommendedProfiles) {
905
+ if (!profiles.includes(profile)) {
906
+ warnings.push({
907
+ path: 'config.toml',
908
+ message: `Consider adding "${profile}" profile`,
909
+ suggestion: `Add [profiles.${profile}] for ${profile === 'dev' ? 'development' : profile === 'safe' ? 'restricted' : 'CI/CD'} environment`,
910
+ });
911
+ }
912
+ }
913
+ // Check profile settings
914
+ for (const profile of profiles) {
915
+ const profileSection = content.match(new RegExp(`\\[profiles\\.${profile}\\][\\s\\S]*?(?=\\[profiles|$)`));
916
+ if (profileSection) {
917
+ const section = profileSection[0];
918
+ // Check if profile has any settings
919
+ if (!section.includes('=')) {
920
+ warnings.push({
921
+ path: 'config.toml',
922
+ message: `Profile "${profile}" has no settings`,
923
+ suggestion: 'Add approval_policy, sandbox_mode, or web_search settings',
924
+ });
925
+ }
926
+ }
927
+ }
928
+ }
929
+ //# sourceMappingURL=index.js.map