claude-flow-novice 2.15.2 → 2.15.3
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/hooks/cfn-BACKUP_USAGE.md +243 -243
- package/.claude/hooks/cfn-invoke-security-validation.sh +69 -69
- package/.claude/hooks/cfn-post-edit-cfn-retrospective.sh +78 -78
- package/.claude/hooks/cfn-post-edit.config.json +44 -44
- package/.claude/skills/agent-lifecycle/SKILL.md +60 -0
- package/.claude/skills/agent-lifecycle/execute-lifecycle-hook.sh +573 -0
- package/.claude/skills/agent-lifecycle/simple-audit.sh +31 -0
- package/.claude/skills/cfn-hybrid-routing/check-dependencies.sh +51 -51
- package/.claude/skills/cfn-loop-validation/orchestrate-cfn-loop.sh +252 -252
- package/.claude/skills/cfn-redis-coordination/agent-recovery.sh +74 -74
- package/.claude/skills/cfn-redis-coordination/get-context.sh +112 -112
- package/.claude/skills/cfn-transparency-middleware/middleware-config.sh +28 -28
- package/.claude/skills/cfn-transparency-middleware/performance-benchmark.sh +78 -78
- package/.claude/skills/cfn-transparency-middleware/test-integration.sh +161 -161
- package/.claude/skills/cfn-transparency-middleware/test-transparency-skill.sh +367 -367
- package/.claude/skills/cfn-transparency-middleware/tests/input-validation.sh +92 -92
- package/.claude/skills/cfn-transparency-middleware/wrap-agent.sh +131 -131
- package/claude-assets/hooks/cfn-BACKUP_USAGE.md +243 -243
- package/claude-assets/hooks/cfn-invoke-security-validation.sh +69 -69
- package/claude-assets/hooks/cfn-post-edit-cfn-retrospective.sh +78 -78
- package/claude-assets/hooks/cfn-post-edit.config.json +44 -44
- package/claude-assets/hooks/cfn-post-execution/memory-cleanup.sh +19 -19
- package/claude-assets/hooks/cfn-pre-execution/memory-check.sh +19 -19
- package/claude-assets/skills/agent-lifecycle/execute-lifecycle-hook.sh +572 -572
- package/claude-assets/skills/agent-lifecycle/simple-audit.sh +30 -30
- package/claude-assets/skills/cfn-automatic-memory-persistence/persist-agent-output.sh +48 -48
- package/claude-assets/skills/cfn-automatic-memory-persistence/query-agent-history.sh +34 -34
- package/claude-assets/skills/cfn-deliverable-validation/confidence-calculator.sh +261 -261
- package/claude-assets/skills/cfn-expert-update/update-expert.sh +345 -345
- package/claude-assets/skills/cfn-hybrid-routing/check-dependencies.sh +51 -51
- package/claude-assets/skills/cfn-intervention-detector/detect-intervention.sh +110 -110
- package/claude-assets/skills/cfn-intervention-orchestrator/execute-intervention.sh +58 -58
- package/claude-assets/skills/cfn-loop-validation/orchestrate-cfn-loop.sh +252 -252
- package/claude-assets/skills/cfn-loop2-output-processing/process-validator-output.sh +275 -275
- package/claude-assets/skills/cfn-memory-management/check-memory.sh +159 -159
- package/claude-assets/skills/cfn-memory-management/cleanup-memory.sh +196 -196
- package/claude-assets/skills/cfn-node-heap-sizer/task-mode-heap-limiter.sh +325 -325
- package/claude-assets/skills/cfn-playbook-auto-update/auto-update-playbook.sh +85 -85
- package/claude-assets/skills/cfn-redis-coordination/agent-recovery.sh +74 -74
- package/claude-assets/skills/cfn-redis-coordination/get-context.sh +112 -112
- package/claude-assets/skills/cfn-scope-simplifier/simplify-scope.sh +67 -67
- package/claude-assets/skills/cfn-specialist-injection/recommend-specialist.sh +56 -56
- package/claude-assets/skills/cfn-standardized-error-handling/capture-agent-error.sh +86 -86
- package/claude-assets/skills/cfn-standardized-error-handling/test-error-handling.sh +165 -165
- package/claude-assets/skills/cfn-task-config-init/initialize-config.sh +264 -264
- package/claude-assets/skills/cfn-task-decomposition/task-decomposer.sh +278 -278
- package/claude-assets/skills/cfn-transparency-middleware/middleware-config.sh +28 -28
- package/claude-assets/skills/cfn-transparency-middleware/performance-benchmark.sh +78 -78
- package/claude-assets/skills/cfn-transparency-middleware/test-integration.sh +161 -161
- package/claude-assets/skills/cfn-transparency-middleware/test-transparency-skill.sh +367 -367
- package/claude-assets/skills/cfn-transparency-middleware/tests/input-validation.sh +92 -92
- package/claude-assets/skills/cfn-transparency-middleware/wrap-agent.sh +131 -131
- package/claude-assets/skills/docker-build/SKILL.md +96 -203
- package/claude-assets/skills/docker-build/build.sh +73 -73
- package/claude-assets/skills/integration/agent-handoff.sh +494 -0
- package/claude-assets/skills/integration/file-operations.sh +414 -0
- package/claude-assets/skills/workflow-codification/APPROVAL_WORKFLOW.md +806 -0
- package/claude-assets/skills/workflow-codification/COST_TRACKING.md +637 -0
- package/claude-assets/skills/workflow-codification/EDGE_CASE_TRACKING.md +404 -0
- package/claude-assets/skills/workflow-codification/README_PHASE4.md +457 -0
- package/claude-assets/skills/workflow-codification/SKILL.md +110 -0
- package/claude-assets/skills/workflow-codification/analyze-patterns.sh +899 -0
- package/claude-assets/skills/workflow-codification/approval-workflow.sh +514 -0
- package/claude-assets/skills/workflow-codification/generate-skill-update.sh +525 -0
- package/claude-assets/skills/workflow-codification/review-skill.sh +643 -0
- package/claude-assets/skills/workflow-codification/templates/email-notification.txt +114 -0
- package/claude-assets/skills/workflow-codification/templates/slack-notification.md +85 -0
- package/claude-assets/skills/workflow-codification/test-integration.sh +281 -0
- package/claude-assets/skills/workflow-codification/track-cost-savings.sh +445 -0
- package/claude-assets/skills/workflow-codification/track-edge-case.sh +323 -0
- package/dist/cli/config-manager.js +91 -109
- package/dist/cli/config-manager.js.map +1 -1
- package/dist/integration/DatabaseHandoff.js +507 -0
- package/dist/integration/DatabaseHandoff.js.map +1 -0
- package/dist/integration/StandardAdapter.js +291 -0
- package/dist/integration/StandardAdapter.js.map +1 -0
- package/dist/lib/agent-output-parser.js +518 -0
- package/dist/lib/agent-output-parser.js.map +1 -0
- package/dist/lib/agent-output-validator.js +950 -0
- package/dist/lib/agent-output-validator.js.map +1 -0
- package/dist/lib/artifact-registry.js +443 -0
- package/dist/lib/artifact-registry.js.map +1 -0
- package/dist/lib/config-validator.js +687 -0
- package/dist/lib/config-validator.js.map +1 -0
- package/dist/types/agent-output.js +44 -0
- package/dist/types/agent-output.js.map +1 -0
- package/dist/types/config.js +28 -0
- package/dist/types/config.js.map +1 -0
- package/package.json +2 -1
- package/scripts/artifact-cleanup.sh +392 -0
- package/scripts/deploy-production.sh +355 -355
- package/scripts/docker-playwright-fix.sh +311 -311
- package/scripts/docker-rebuild-all-agents.sh +127 -127
- package/scripts/memory-leak-prevention.sh +305 -305
- package/scripts/migrate-artifacts.sh +563 -0
- package/scripts/migrate-yaml-to-json.sh +465 -0
- package/scripts/run-marketing-tests.sh +42 -42
- package/scripts/update_paths.sh +46 -46
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CFN Configuration Validator
|
|
3
|
+
* Validates JSON configurations against the CFN schema with detailed error reporting
|
|
4
|
+
*
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
* @description Type-safe validation library with 95%+ accuracy
|
|
7
|
+
*/ /**
|
|
8
|
+
* ConfigValidator: Main validation class
|
|
9
|
+
* Provides schema validation, error reporting, and env var export functionality
|
|
10
|
+
*/ export class ConfigValidator {
|
|
11
|
+
schema;
|
|
12
|
+
initialized = true;
|
|
13
|
+
constructor(schema){
|
|
14
|
+
if (schema) {
|
|
15
|
+
this.schema = schema;
|
|
16
|
+
} else {
|
|
17
|
+
// Use the default schema embedded below
|
|
18
|
+
this.schema = this.getDefaultSchema();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get default schema definition
|
|
23
|
+
*/ getDefaultSchema() {
|
|
24
|
+
return {
|
|
25
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
26
|
+
$id: 'https://claude-flow-novice.local/schemas/cfn-config-v1.json',
|
|
27
|
+
title: 'CFN Configuration Schema v1.0',
|
|
28
|
+
description: 'Canonical JSON schema for all Claude Flow Novice configuration files',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
type: 'object'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Main validation method using built-in validators
|
|
35
|
+
*/ validate(config) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
const warnings = [];
|
|
38
|
+
if (typeof config !== 'object' || config === null) {
|
|
39
|
+
return {
|
|
40
|
+
valid: false,
|
|
41
|
+
errors: [
|
|
42
|
+
{
|
|
43
|
+
field: 'root',
|
|
44
|
+
message: 'Configuration must be a valid JSON object',
|
|
45
|
+
path: '/',
|
|
46
|
+
code: 'INVALID_TYPE'
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
warnings,
|
|
50
|
+
configType: 'unknown'
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const configObj = config;
|
|
54
|
+
const configType = this.detectConfigType(configObj);
|
|
55
|
+
// Validate based on detected type
|
|
56
|
+
switch(configType){
|
|
57
|
+
case 'agent-whitelist':
|
|
58
|
+
return this.validateAgentWhitelist(configObj);
|
|
59
|
+
case 'mcp-servers':
|
|
60
|
+
return this.validateMCPServers(configObj);
|
|
61
|
+
case 'skill-requirements':
|
|
62
|
+
return this.validateSkillRequirements(configObj);
|
|
63
|
+
case 'runtime-contract':
|
|
64
|
+
return this.validateRuntimeContract(configObj);
|
|
65
|
+
case 'team':
|
|
66
|
+
return this.validateTeamConfig(configObj);
|
|
67
|
+
default:
|
|
68
|
+
return {
|
|
69
|
+
valid: false,
|
|
70
|
+
errors: [
|
|
71
|
+
{
|
|
72
|
+
field: 'root',
|
|
73
|
+
message: 'Unknown configuration type. Must include agents, servers, tools, variables, or team',
|
|
74
|
+
path: '/',
|
|
75
|
+
code: 'UNKNOWN_CONFIG_TYPE'
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
warnings,
|
|
79
|
+
configType: 'unknown'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validate JSON string
|
|
85
|
+
*/ validateJSON(jsonString) {
|
|
86
|
+
try {
|
|
87
|
+
const config = JSON.parse(jsonString);
|
|
88
|
+
return this.validate(config);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
errors: [
|
|
93
|
+
{
|
|
94
|
+
field: 'json',
|
|
95
|
+
message: `Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
96
|
+
path: 'root',
|
|
97
|
+
code: 'JSON_PARSE_ERROR'
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
warnings: [],
|
|
101
|
+
configType: 'unknown'
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Validate Agent Whitelist Configuration
|
|
107
|
+
*/ validateAgentWhitelist(config) {
|
|
108
|
+
const errors = [];
|
|
109
|
+
const warnings = [];
|
|
110
|
+
// Check version
|
|
111
|
+
if (!config.version || typeof config.version !== 'string') {
|
|
112
|
+
errors.push({
|
|
113
|
+
field: 'version',
|
|
114
|
+
message: 'Missing required field: version (string)',
|
|
115
|
+
path: '/version',
|
|
116
|
+
code: 'MISSING_REQUIRED'
|
|
117
|
+
});
|
|
118
|
+
} else if (!/^\d+\.\d+\.\d+$/.test(config.version)) {
|
|
119
|
+
errors.push({
|
|
120
|
+
field: 'version',
|
|
121
|
+
message: 'Invalid version format. Expected X.Y.Z format',
|
|
122
|
+
path: '/version',
|
|
123
|
+
value: config.version,
|
|
124
|
+
code: 'INVALID_FORMAT'
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// Check agents array
|
|
128
|
+
if (!Array.isArray(config.agents)) {
|
|
129
|
+
errors.push({
|
|
130
|
+
field: 'agents',
|
|
131
|
+
message: 'Missing required field: agents (array)',
|
|
132
|
+
path: '/agents',
|
|
133
|
+
code: 'MISSING_REQUIRED'
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
for(let i = 0; i < config.agents.length; i++){
|
|
137
|
+
const agent = config.agents[i];
|
|
138
|
+
if (!agent.type || typeof agent.type !== 'string' || agent.type.length === 0) {
|
|
139
|
+
errors.push({
|
|
140
|
+
field: `agents[${i}].type`,
|
|
141
|
+
message: 'Agent type is required and must be non-empty string',
|
|
142
|
+
path: `/agents/${i}/type`,
|
|
143
|
+
code: 'INVALID_AGENT'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!agent.displayName || typeof agent.displayName !== 'string') {
|
|
147
|
+
errors.push({
|
|
148
|
+
field: `agents[${i}].displayName`,
|
|
149
|
+
message: 'Agent displayName is required',
|
|
150
|
+
path: `/agents/${i}/displayName`,
|
|
151
|
+
code: 'INVALID_AGENT'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (!Array.isArray(agent.skills)) {
|
|
155
|
+
errors.push({
|
|
156
|
+
field: `agents[${i}].skills`,
|
|
157
|
+
message: 'Agent skills must be an array',
|
|
158
|
+
path: `/agents/${i}/skills`,
|
|
159
|
+
code: 'INVALID_AGENT'
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!config.lastUpdated) {
|
|
165
|
+
warnings.push('Agent configuration missing lastUpdated field (recommended)');
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
valid: errors.length === 0,
|
|
169
|
+
errors,
|
|
170
|
+
warnings,
|
|
171
|
+
configType: 'agent-whitelist'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Validate MCP Servers Configuration
|
|
176
|
+
*/ validateMCPServers(config) {
|
|
177
|
+
const errors = [];
|
|
178
|
+
const warnings = [];
|
|
179
|
+
// Check version
|
|
180
|
+
if (!config.version || typeof config.version !== 'string') {
|
|
181
|
+
errors.push({
|
|
182
|
+
field: 'version',
|
|
183
|
+
message: 'Missing required field: version',
|
|
184
|
+
path: '/version',
|
|
185
|
+
code: 'MISSING_REQUIRED'
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// Check servers object
|
|
189
|
+
if (!config.servers || typeof config.servers !== 'object') {
|
|
190
|
+
errors.push({
|
|
191
|
+
field: 'servers',
|
|
192
|
+
message: 'Missing required field: servers (object)',
|
|
193
|
+
path: '/servers',
|
|
194
|
+
code: 'MISSING_REQUIRED'
|
|
195
|
+
});
|
|
196
|
+
} else {
|
|
197
|
+
const servers = config.servers;
|
|
198
|
+
for(const serverName in servers){
|
|
199
|
+
if (Object.prototype.hasOwnProperty.call(servers, serverName)) {
|
|
200
|
+
const serverConfig = servers[serverName];
|
|
201
|
+
if (!serverConfig.endpoint || typeof serverConfig.endpoint !== 'string') {
|
|
202
|
+
errors.push({
|
|
203
|
+
field: `servers.${serverName}.endpoint`,
|
|
204
|
+
message: 'Server endpoint is required',
|
|
205
|
+
path: `/servers/${serverName}/endpoint`,
|
|
206
|
+
code: 'INVALID_SERVER'
|
|
207
|
+
});
|
|
208
|
+
} else if (!this.isValidURL(serverConfig.endpoint)) {
|
|
209
|
+
errors.push({
|
|
210
|
+
field: `servers.${serverName}.endpoint`,
|
|
211
|
+
message: 'Server endpoint must be a valid URL',
|
|
212
|
+
path: `/servers/${serverName}/endpoint`,
|
|
213
|
+
value: serverConfig.endpoint,
|
|
214
|
+
code: 'INVALID_URL'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (!Array.isArray(serverConfig.requiredSkills)) {
|
|
218
|
+
errors.push({
|
|
219
|
+
field: `servers.${serverName}.requiredSkills`,
|
|
220
|
+
message: 'Server requiredSkills must be an array',
|
|
221
|
+
path: `/servers/${serverName}/requiredSkills`,
|
|
222
|
+
code: 'INVALID_SERVER'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (!serverConfig.auth || typeof serverConfig.auth !== 'object') {
|
|
226
|
+
errors.push({
|
|
227
|
+
field: `servers.${serverName}.auth`,
|
|
228
|
+
message: 'Server auth is required',
|
|
229
|
+
path: `/servers/${serverName}/auth`,
|
|
230
|
+
code: 'INVALID_SERVER'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (serverConfig.timeoutMs !== undefined && typeof serverConfig.timeoutMs !== 'number') {
|
|
234
|
+
errors.push({
|
|
235
|
+
field: `servers.${serverName}.timeoutMs`,
|
|
236
|
+
message: 'timeoutMs must be a number',
|
|
237
|
+
path: `/servers/${serverName}/timeoutMs`,
|
|
238
|
+
code: 'INVALID_TYPE'
|
|
239
|
+
});
|
|
240
|
+
} else if (serverConfig.timeoutMs !== undefined && serverConfig.timeoutMs < 1000) {
|
|
241
|
+
errors.push({
|
|
242
|
+
field: `servers.${serverName}.timeoutMs`,
|
|
243
|
+
message: 'timeoutMs must be at least 1000ms',
|
|
244
|
+
path: `/servers/${serverName}/timeoutMs`,
|
|
245
|
+
value: serverConfig.timeoutMs,
|
|
246
|
+
code: 'CONSTRAINT_VIOLATION'
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
valid: errors.length === 0,
|
|
254
|
+
errors,
|
|
255
|
+
warnings,
|
|
256
|
+
configType: 'mcp-servers'
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Validate Skill Requirements Configuration
|
|
261
|
+
*/ validateSkillRequirements(config) {
|
|
262
|
+
const errors = [];
|
|
263
|
+
const warnings = [];
|
|
264
|
+
if (!config.version || typeof config.version !== 'string') {
|
|
265
|
+
errors.push({
|
|
266
|
+
field: 'version',
|
|
267
|
+
message: 'Missing required field: version',
|
|
268
|
+
path: '/version',
|
|
269
|
+
code: 'MISSING_REQUIRED'
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (!config.tools || typeof config.tools !== 'object') {
|
|
273
|
+
errors.push({
|
|
274
|
+
field: 'tools',
|
|
275
|
+
message: 'Missing required field: tools (object)',
|
|
276
|
+
path: '/tools',
|
|
277
|
+
code: 'MISSING_REQUIRED'
|
|
278
|
+
});
|
|
279
|
+
} else {
|
|
280
|
+
const tools = config.tools;
|
|
281
|
+
for(const toolName in tools){
|
|
282
|
+
if (Object.prototype.hasOwnProperty.call(tools, toolName)) {
|
|
283
|
+
const toolConfig = tools[toolName];
|
|
284
|
+
if (!toolConfig.displayName || typeof toolConfig.displayName !== 'string') {
|
|
285
|
+
errors.push({
|
|
286
|
+
field: `tools.${toolName}.displayName`,
|
|
287
|
+
message: 'Tool displayName is required',
|
|
288
|
+
path: `/tools/${toolName}/displayName`,
|
|
289
|
+
code: 'INVALID_TOOL'
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (!Array.isArray(toolConfig.requiredSkills) || toolConfig.requiredSkills.length === 0) {
|
|
293
|
+
errors.push({
|
|
294
|
+
field: `tools.${toolName}.requiredSkills`,
|
|
295
|
+
message: 'Tool requiredSkills must be a non-empty array',
|
|
296
|
+
path: `/tools/${toolName}/requiredSkills`,
|
|
297
|
+
code: 'INVALID_TOOL'
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
valid: errors.length === 0,
|
|
305
|
+
errors,
|
|
306
|
+
warnings,
|
|
307
|
+
configType: 'skill-requirements'
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Validate Runtime Contract Configuration
|
|
312
|
+
*/ validateRuntimeContract(config) {
|
|
313
|
+
const errors = [];
|
|
314
|
+
const warnings = [];
|
|
315
|
+
if (!config.version || typeof config.version !== 'string') {
|
|
316
|
+
errors.push({
|
|
317
|
+
field: 'version',
|
|
318
|
+
message: 'Missing required field: version',
|
|
319
|
+
path: '/version',
|
|
320
|
+
code: 'MISSING_REQUIRED'
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (config.variables && typeof config.variables === 'object') {
|
|
324
|
+
const variables = config.variables;
|
|
325
|
+
for(const varName in variables){
|
|
326
|
+
if (Object.prototype.hasOwnProperty.call(variables, varName)) {
|
|
327
|
+
const varConfig = variables[varName];
|
|
328
|
+
if (!varConfig.description || typeof varConfig.description !== 'string') {
|
|
329
|
+
errors.push({
|
|
330
|
+
field: `variables.${varName}.description`,
|
|
331
|
+
message: 'Variable description is required',
|
|
332
|
+
path: `/variables/${varName}/description`,
|
|
333
|
+
code: 'INVALID_VARIABLE'
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const varType = varConfig.type;
|
|
337
|
+
const validTypes = [
|
|
338
|
+
'string',
|
|
339
|
+
'integer',
|
|
340
|
+
'number',
|
|
341
|
+
'boolean'
|
|
342
|
+
];
|
|
343
|
+
if (!varConfig.type || validTypes.indexOf(varType) === -1) {
|
|
344
|
+
errors.push({
|
|
345
|
+
field: `variables.${varName}.type`,
|
|
346
|
+
message: 'Variable type must be one of: string, integer, number, boolean',
|
|
347
|
+
path: `/variables/${varName}/type`,
|
|
348
|
+
code: 'INVALID_VARIABLE'
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
valid: errors.length === 0,
|
|
356
|
+
errors,
|
|
357
|
+
warnings,
|
|
358
|
+
configType: 'runtime-contract'
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Validate Team Configuration
|
|
363
|
+
*/ validateTeamConfig(config) {
|
|
364
|
+
const errors = [];
|
|
365
|
+
const warnings = [];
|
|
366
|
+
if (!config.team || typeof config.team !== 'object') {
|
|
367
|
+
errors.push({
|
|
368
|
+
field: 'team',
|
|
369
|
+
message: 'Missing required field: team (object)',
|
|
370
|
+
path: '/team',
|
|
371
|
+
code: 'MISSING_REQUIRED'
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
valid: false,
|
|
375
|
+
errors,
|
|
376
|
+
warnings,
|
|
377
|
+
configType: 'team'
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const team = config.team;
|
|
381
|
+
if (!team.id || typeof team.id !== 'string' || team.id.length === 0) {
|
|
382
|
+
errors.push({
|
|
383
|
+
field: 'team.id',
|
|
384
|
+
message: 'Team id is required and must be non-empty',
|
|
385
|
+
path: '/team/id',
|
|
386
|
+
code: 'INVALID_TEAM'
|
|
387
|
+
});
|
|
388
|
+
} else if (!/^[a-z0-9-]+$/.test(team.id)) {
|
|
389
|
+
errors.push({
|
|
390
|
+
field: 'team.id',
|
|
391
|
+
message: 'Team id must contain only lowercase letters, numbers, and hyphens',
|
|
392
|
+
path: '/team/id',
|
|
393
|
+
value: team.id,
|
|
394
|
+
code: 'INVALID_FORMAT'
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (!team.name || typeof team.name !== 'string') {
|
|
398
|
+
errors.push({
|
|
399
|
+
field: 'team.name',
|
|
400
|
+
message: 'Team name is required',
|
|
401
|
+
path: '/team/name',
|
|
402
|
+
code: 'INVALID_TEAM'
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
// Validate workspace if present (supports both diskQuota and disk_quota)
|
|
406
|
+
if (team.workspace && typeof team.workspace === 'object') {
|
|
407
|
+
const workspace = team.workspace;
|
|
408
|
+
const diskQuota = this.getPropertyValue(workspace, 'diskQuota');
|
|
409
|
+
if (diskQuota !== undefined && !this.isValidDiskQuota(diskQuota)) {
|
|
410
|
+
errors.push({
|
|
411
|
+
field: 'team.workspace.diskQuota',
|
|
412
|
+
message: 'Invalid disk quota format. Expected format: <number><UNIT> (e.g., 100GB)',
|
|
413
|
+
path: '/team/workspace/diskQuota',
|
|
414
|
+
value: diskQuota,
|
|
415
|
+
code: 'INVALID_FORMAT'
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Validate resources if present (supports both camelCase and snake_case naming)
|
|
420
|
+
if (team.resources && typeof team.resources === 'object') {
|
|
421
|
+
const resources = team.resources;
|
|
422
|
+
const cpuCores = this.getPropertyValue(resources, 'cpuCores');
|
|
423
|
+
const maxAgents = this.getPropertyValue(resources, 'maxAgents');
|
|
424
|
+
// Validate cpuCores (supports cpuCores and cpu_cores)
|
|
425
|
+
if (cpuCores !== undefined) {
|
|
426
|
+
if (typeof cpuCores !== 'number' || cpuCores < 0) {
|
|
427
|
+
errors.push({
|
|
428
|
+
field: 'team.resources.cpuCores',
|
|
429
|
+
message: 'cpuCores must be a non-negative number',
|
|
430
|
+
path: '/team/resources/cpuCores',
|
|
431
|
+
value: cpuCores,
|
|
432
|
+
code: 'INVALID_TYPE'
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Validate maxAgents (supports maxAgents and max_agents)
|
|
437
|
+
if (maxAgents !== undefined) {
|
|
438
|
+
if (typeof maxAgents !== 'number' || maxAgents < 1 || !Number.isInteger(maxAgents)) {
|
|
439
|
+
errors.push({
|
|
440
|
+
field: 'team.resources.maxAgents',
|
|
441
|
+
message: 'maxAgents must be a positive integer',
|
|
442
|
+
path: '/team/resources/maxAgents',
|
|
443
|
+
value: maxAgents,
|
|
444
|
+
code: 'INVALID_TYPE'
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Validate network if present
|
|
450
|
+
if (team.network && typeof team.network === 'object') {
|
|
451
|
+
const network = team.network;
|
|
452
|
+
if (network.coordinatorIp && !this.isValidIPv4(network.coordinatorIp)) {
|
|
453
|
+
errors.push({
|
|
454
|
+
field: 'team.network.coordinatorIp',
|
|
455
|
+
message: 'Invalid IPv4 format',
|
|
456
|
+
path: '/team/network/coordinatorIp',
|
|
457
|
+
value: network.coordinatorIp,
|
|
458
|
+
code: 'INVALID_FORMAT'
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
valid: errors.length === 0,
|
|
464
|
+
errors,
|
|
465
|
+
warnings,
|
|
466
|
+
configType: 'team'
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Detect configuration type
|
|
471
|
+
*/ detectConfigType(config) {
|
|
472
|
+
// Check for agent whitelist (has 'agents' array)
|
|
473
|
+
if ('agents' in config && Array.isArray(config.agents)) {
|
|
474
|
+
return 'agent-whitelist';
|
|
475
|
+
}
|
|
476
|
+
// Check for MCP servers (has 'servers' object)
|
|
477
|
+
if ('servers' in config && typeof config.servers === 'object') {
|
|
478
|
+
return 'mcp-servers';
|
|
479
|
+
}
|
|
480
|
+
// Check for skill requirements (has 'tools' object)
|
|
481
|
+
if ('tools' in config && typeof config.tools === 'object') {
|
|
482
|
+
return 'skill-requirements';
|
|
483
|
+
}
|
|
484
|
+
// Check for runtime contract (has 'variables' object)
|
|
485
|
+
if ('variables' in config && typeof config.variables === 'object') {
|
|
486
|
+
return 'runtime-contract';
|
|
487
|
+
}
|
|
488
|
+
// Check for team config (has 'team' object)
|
|
489
|
+
if ('team' in config && typeof config.team === 'object') {
|
|
490
|
+
return 'team';
|
|
491
|
+
}
|
|
492
|
+
return 'unknown';
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Export configuration as environment variables
|
|
496
|
+
* Handles type preservation and validation
|
|
497
|
+
*/ exportEnvVars(config) {
|
|
498
|
+
if (typeof config !== 'object' || config === null) {
|
|
499
|
+
throw new Error('Configuration must be an object');
|
|
500
|
+
}
|
|
501
|
+
const configObj = config;
|
|
502
|
+
if (!('variables' in configObj)) {
|
|
503
|
+
throw new Error('Configuration is not a valid runtime contract');
|
|
504
|
+
}
|
|
505
|
+
const envMap = {};
|
|
506
|
+
const variables = configObj.variables;
|
|
507
|
+
if (typeof variables !== 'object' || variables === null) {
|
|
508
|
+
throw new Error('Variables must be an object');
|
|
509
|
+
}
|
|
510
|
+
const varsObj = variables;
|
|
511
|
+
for(const key in varsObj){
|
|
512
|
+
if (Object.prototype.hasOwnProperty.call(varsObj, key)) {
|
|
513
|
+
const variable = varsObj[key];
|
|
514
|
+
const varObj = variable;
|
|
515
|
+
if (varObj.value !== null && varObj.value !== undefined) {
|
|
516
|
+
const varType = varObj.type;
|
|
517
|
+
envMap[key] = this.coerceToCorrectType(varObj.value, varType);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return envMap;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Coerce value to correct type without loss
|
|
525
|
+
*/ coerceToCorrectType(value, type) {
|
|
526
|
+
if (type === 'integer' && typeof value === 'string') {
|
|
527
|
+
return parseInt(value, 10);
|
|
528
|
+
}
|
|
529
|
+
if (type === 'number' && typeof value === 'string') {
|
|
530
|
+
return parseFloat(value);
|
|
531
|
+
}
|
|
532
|
+
if (type === 'boolean' && typeof value === 'string') {
|
|
533
|
+
return value.toLowerCase() === 'true';
|
|
534
|
+
}
|
|
535
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
return String(value);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Format validation errors for display
|
|
542
|
+
*/ formatErrors(result) {
|
|
543
|
+
if (result.valid) {
|
|
544
|
+
return 'Configuration is valid.';
|
|
545
|
+
}
|
|
546
|
+
let output = `Validation failed with ${result.errors.length} error(s):\n\n`;
|
|
547
|
+
for (const error of result.errors){
|
|
548
|
+
output += `[${error.code}] ${error.field || 'root'}\n`;
|
|
549
|
+
output += ` ${error.message}\n`;
|
|
550
|
+
if (error.value !== undefined) {
|
|
551
|
+
output += ` Current value: ${JSON.stringify(error.value)}\n`;
|
|
552
|
+
}
|
|
553
|
+
output += '\n';
|
|
554
|
+
}
|
|
555
|
+
if (result.warnings.length > 0) {
|
|
556
|
+
output += `\nWarnings (${result.warnings.length}):\n`;
|
|
557
|
+
for (const warning of result.warnings){
|
|
558
|
+
output += ` - ${warning}\n`;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return output;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Helper: Validate URL format
|
|
565
|
+
*/ isValidURL(url) {
|
|
566
|
+
if (typeof url !== 'string') return false;
|
|
567
|
+
try {
|
|
568
|
+
new URL(url);
|
|
569
|
+
return true;
|
|
570
|
+
} catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Helper: Validate disk quota format
|
|
576
|
+
*/ isValidDiskQuota(quota) {
|
|
577
|
+
if (typeof quota !== 'string') return false;
|
|
578
|
+
return /^\d+[KMGTPE]B$/.test(quota);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Helper: Validate IPv4 format
|
|
582
|
+
*/ isValidIPv4(ip) {
|
|
583
|
+
if (typeof ip !== 'string') return false;
|
|
584
|
+
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
585
|
+
if (!ipv4Pattern.test(ip)) return false;
|
|
586
|
+
const parts = ip.split('.');
|
|
587
|
+
return parts.every((part)=>{
|
|
588
|
+
const num = parseInt(part, 10);
|
|
589
|
+
return num >= 0 && num <= 255;
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Helper: Normalize field name (support both snake_case and camelCase)
|
|
594
|
+
* Examples: disk_quota → diskQuota, cpu_cores → cpuCores, maxAgents → maxAgents
|
|
595
|
+
*/ normalizeFieldName(name) {
|
|
596
|
+
if (!name.includes('_')) return name;
|
|
597
|
+
return name.replace(/_([a-z])/g, (_, letter)=>letter.toUpperCase());
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Helper: Get property value supporting both naming conventions
|
|
601
|
+
* @param obj Object to search
|
|
602
|
+
* @param field Field name in any convention (camelCase or snake_case)
|
|
603
|
+
* @returns Value if found, undefined otherwise
|
|
604
|
+
*/ getPropertyValue(obj, field) {
|
|
605
|
+
// Try camelCase version first
|
|
606
|
+
if (field in obj) return obj[field];
|
|
607
|
+
// Try snake_case version
|
|
608
|
+
const snakeCase = field.replace(/[A-Z]/g, (letter)=>`_${letter.toLowerCase()}`);
|
|
609
|
+
if (snakeCase in obj) return obj[snakeCase];
|
|
610
|
+
// Try normalizing if it's snake_case input
|
|
611
|
+
const camelCase = this.normalizeFieldName(field);
|
|
612
|
+
if (camelCase in obj) return obj[camelCase];
|
|
613
|
+
return undefined;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Singleton instance for global usage
|
|
618
|
+
*/ let validatorInstance = null;
|
|
619
|
+
/**
|
|
620
|
+
* Get or create validator instance
|
|
621
|
+
*/ export function getValidator(schema) {
|
|
622
|
+
if (!validatorInstance) {
|
|
623
|
+
validatorInstance = new ConfigValidator(schema);
|
|
624
|
+
}
|
|
625
|
+
return validatorInstance;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Validate configuration object
|
|
629
|
+
*/ export function validateConfig(config) {
|
|
630
|
+
return getValidator().validate(config);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Validate JSON string
|
|
634
|
+
*/ export function validateJSON(jsonString) {
|
|
635
|
+
return getValidator().validateJSON(jsonString);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Export environment variables from runtime contract
|
|
639
|
+
*/ export function exportEnvVars(config) {
|
|
640
|
+
return getValidator().exportEnvVars(config);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Check if configuration is valid (boolean shortcut)
|
|
644
|
+
*/ export function isValidConfig(config) {
|
|
645
|
+
return getValidator().validate(config).valid;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Reset validator instance (useful for testing)
|
|
649
|
+
*/ export function resetValidator() {
|
|
650
|
+
validatorInstance = null;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Validate multiple configuration files efficiently
|
|
654
|
+
* @param filePaths Array of file paths to validate
|
|
655
|
+
* @returns Map of file path to validation result
|
|
656
|
+
* @throws Error if file cannot be read
|
|
657
|
+
*/ export function validateConfigFiles(filePaths) {
|
|
658
|
+
const results = {};
|
|
659
|
+
const validator = getValidator();
|
|
660
|
+
for (const filePath of filePaths){
|
|
661
|
+
try {
|
|
662
|
+
// Dynamically import fs module to read file
|
|
663
|
+
const fs = require('fs');
|
|
664
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
665
|
+
const config = JSON.parse(content);
|
|
666
|
+
results[filePath] = validator.validate(config);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
results[filePath] = {
|
|
669
|
+
valid: false,
|
|
670
|
+
errors: [
|
|
671
|
+
{
|
|
672
|
+
field: 'file',
|
|
673
|
+
message: `Failed to read or parse file: ${error instanceof Error ? error.message : String(error)}`,
|
|
674
|
+
path: filePath,
|
|
675
|
+
code: 'FILE_READ_ERROR'
|
|
676
|
+
}
|
|
677
|
+
],
|
|
678
|
+
warnings: [],
|
|
679
|
+
configType: 'unknown'
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return results;
|
|
684
|
+
}
|
|
685
|
+
export default ConfigValidator;
|
|
686
|
+
|
|
687
|
+
//# sourceMappingURL=config-validator.js.map
|