@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.
- package/README.md +301 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +649 -0
- package/dist/cli.js.map +1 -0
- package/dist/generators/agents-md.d.ts +12 -0
- package/dist/generators/agents-md.d.ts.map +1 -0
- package/dist/generators/agents-md.js +641 -0
- package/dist/generators/agents-md.js.map +1 -0
- package/dist/generators/config-toml.d.ts +74 -0
- package/dist/generators/config-toml.d.ts.map +1 -0
- package/dist/generators/config-toml.js +910 -0
- package/dist/generators/config-toml.js.map +1 -0
- package/dist/generators/index.d.ts +9 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +9 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/skill-md.d.ts +20 -0
- package/dist/generators/skill-md.d.ts.map +1 -0
- package/dist/generators/skill-md.js +946 -0
- package/dist/generators/skill-md.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/initializer.d.ts +87 -0
- package/dist/initializer.d.ts.map +1 -0
- package/dist/initializer.js +666 -0
- package/dist/initializer.js.map +1 -0
- package/dist/migrations/index.d.ts +114 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +856 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/templates/index.d.ts +92 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +284 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/validators/index.d.ts +42 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/index.js +929 -0
- package/dist/validators/index.js.map +1 -0
- 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
|