aios-core 4.2.13 → 4.2.15
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/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/data/entity-registry.yaml +27 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +65 -61
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +3 -3
- package/pro/license/degradation.js +220 -220
- package/pro/license/errors.js +450 -450
- package/pro/license/feature-gate.js +354 -354
- package/pro/license/index.js +181 -181
- package/pro/license/license-cache.js +523 -523
- package/pro/license/license-crypto.js +303 -303
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -191
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
|
@@ -1,555 +1,555 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const yaml = require('js-yaml');
|
|
4
|
-
const { validateYAML } = require('./yaml-validator');
|
|
5
|
-
const DependencyAnalyzer = require('./dependency-analyzer');
|
|
6
|
-
const SecurityChecker = require('./security-checker');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Validates component modifications before applying them
|
|
10
|
-
*/
|
|
11
|
-
class ModificationValidator {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.dependencyAnalyzer = new DependencyAnalyzer();
|
|
14
|
-
this.securityChecker = new SecurityChecker();
|
|
15
|
-
this.validationRules = {
|
|
16
|
-
agent: this.validateAgentModification.bind(this),
|
|
17
|
-
task: this.validateTaskModification.bind(this),
|
|
18
|
-
workflow: this.validateWorkflowModification.bind(this),
|
|
19
|
-
template: this.validateTemplateModification.bind(this)
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Validate a component modification
|
|
25
|
-
* @param {string} componentType - Type of component (agent, task, workflow, etc.)
|
|
26
|
-
* @param {string} originalContent - Original component content
|
|
27
|
-
* @param {string} modifiedContent - Modified component content
|
|
28
|
-
* @param {Object} options - Validation options
|
|
29
|
-
* @returns {Object} Validation result with errors and warnings
|
|
30
|
-
*/
|
|
31
|
-
async validateModification(_componentType, originalContent, modifiedContent, options = {}) {
|
|
32
|
-
const result = {
|
|
33
|
-
valid: true,
|
|
34
|
-
errors: [],
|
|
35
|
-
warnings: [],
|
|
36
|
-
suggestions: [],
|
|
37
|
-
breakingChanges: []
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Basic validation
|
|
41
|
-
if (!originalContent || !modifiedContent) {
|
|
42
|
-
result.valid = false;
|
|
43
|
-
result.errors.push('Original or modified content is empty');
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Run type-specific validation
|
|
48
|
-
if (this.validationRules[componentType]) {
|
|
49
|
-
const typeResult = await this.validationRules[componentType](
|
|
50
|
-
originalContent,
|
|
51
|
-
modifiedContent,
|
|
52
|
-
options
|
|
53
|
-
);
|
|
54
|
-
this.mergeResults(result, typeResult);
|
|
55
|
-
} else {
|
|
56
|
-
result.warnings.push(`No specific validation rules for component type: ${componentType}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Run security validation
|
|
60
|
-
const securityResult = await this.validateSecurity(modifiedContent, componentType);
|
|
61
|
-
this.mergeResults(result, securityResult);
|
|
62
|
-
|
|
63
|
-
// Check for breaking changes
|
|
64
|
-
const breakingChanges = await this.detectBreakingChanges(
|
|
65
|
-
_componentType,
|
|
66
|
-
originalContent,
|
|
67
|
-
modifiedContent
|
|
68
|
-
);
|
|
69
|
-
result.breakingChanges = breakingChanges;
|
|
70
|
-
|
|
71
|
-
result.valid = result.errors.length === 0;
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validate agent modifications
|
|
77
|
-
* @private
|
|
78
|
-
*/
|
|
79
|
-
async validateAgentModification(originalContent, modifiedContent, options) {
|
|
80
|
-
const result = {
|
|
81
|
-
valid: true,
|
|
82
|
-
errors: [],
|
|
83
|
-
warnings: [],
|
|
84
|
-
suggestions: []
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
// Parse agent content
|
|
89
|
-
const originalParts = this.parseAgentContent(originalContent);
|
|
90
|
-
const modifiedParts = this.parseAgentContent(modifiedContent);
|
|
91
|
-
|
|
92
|
-
// Validate YAML structure
|
|
93
|
-
const yamlValidation = validateYAML(modifiedParts.yaml);
|
|
94
|
-
if (!yamlValidation.valid) {
|
|
95
|
-
result.valid = false;
|
|
96
|
-
result.errors.push(`YAML validation failed: ${yamlValidation.error}`);
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const originalMeta = yaml.load(originalParts.yaml);
|
|
101
|
-
const modifiedMeta = yaml.load(modifiedParts.yaml);
|
|
102
|
-
|
|
103
|
-
// Check required fields
|
|
104
|
-
const requiredFields = ['name', 'id', 'title', 'icon', 'whenToUse'];
|
|
105
|
-
for (const field of requiredFields) {
|
|
106
|
-
if (!modifiedMeta[field]) {
|
|
107
|
-
result.errors.push(`Required field missing: ${field}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Validate dependencies
|
|
112
|
-
if (modifiedMeta.dependencies) {
|
|
113
|
-
const depValidation = await this.validateDependencies(modifiedMeta.dependencies);
|
|
114
|
-
this.mergeResults(result, depValidation);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Check command structure
|
|
118
|
-
if (modifiedMeta.commands) {
|
|
119
|
-
for (const [cmd, desc] of Object.entries(modifiedMeta.commands)) {
|
|
120
|
-
if (!desc || typeof desc !== 'string') {
|
|
121
|
-
result.errors.push(`Invalid command description for: ${cmd}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check for removed commands (breaking change)
|
|
127
|
-
if (originalMeta.commands && modifiedMeta.commands) {
|
|
128
|
-
const removedCommands = Object.keys(originalMeta.commands)
|
|
129
|
-
.filter(cmd => !modifiedMeta.commands[cmd]);
|
|
130
|
-
|
|
131
|
-
if (removedCommands.length > 0) {
|
|
132
|
-
result.warnings.push(`Commands removed: ${removedCommands.join(', ')}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Validate markdown content
|
|
137
|
-
const markdownValidation = this.validateMarkdown(modifiedParts.markdown);
|
|
138
|
-
this.mergeResults(result, markdownValidation);
|
|
139
|
-
|
|
140
|
-
} catch (_error) {
|
|
141
|
-
result.valid = false;
|
|
142
|
-
result.errors.push(`Failed to parse agent content: ${error.message}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return result;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Validate task modifications
|
|
150
|
-
* @private
|
|
151
|
-
*/
|
|
152
|
-
async validateTaskModification(originalContent, modifiedContent, options) {
|
|
153
|
-
const result = {
|
|
154
|
-
valid: true,
|
|
155
|
-
errors: [],
|
|
156
|
-
warnings: [],
|
|
157
|
-
suggestions: []
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Check for required sections
|
|
161
|
-
const requiredSections = ['## Purpose', '## Task Execution'];
|
|
162
|
-
for (const section of requiredSections) {
|
|
163
|
-
if (!modifiedContent.includes(section)) {
|
|
164
|
-
result.errors.push(`Required section missing: ${section}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Validate elicitation blocks
|
|
169
|
-
const elicitationBlocks = modifiedContent.match(/\[\[LLM:([\s\S]*?)\]\]/g) || [];
|
|
170
|
-
for (const block of elicitationBlocks) {
|
|
171
|
-
if (!block.includes(']]')) {
|
|
172
|
-
result.errors.push('Unclosed elicitation block found');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Check for task flow consistency
|
|
177
|
-
const taskSteps = modifiedContent.match(/###\s+\d+\.\s+/g) || [];
|
|
178
|
-
const expectedSteps = taskSteps.length;
|
|
179
|
-
for (let i = 1; i <= expectedSteps; i++) {
|
|
180
|
-
if (!modifiedContent.includes(`### ${i}.`)) {
|
|
181
|
-
result.warnings.push(`Task step ${i} appears to be missing or misnumbered`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Validate output format if specified
|
|
186
|
-
const outputMatch = modifiedContent.match(/## Output Format[\s\S]*?```([\s\S]*?)```/);
|
|
187
|
-
if (outputMatch) {
|
|
188
|
-
const outputFormat = outputMatch[1].trim();
|
|
189
|
-
if (outputFormat.startsWith('json')) {
|
|
190
|
-
try {
|
|
191
|
-
// Extract JSON and validate
|
|
192
|
-
const jsonContent = outputFormat.replace(/^json\s*/, '');
|
|
193
|
-
JSON.parse(jsonContent);
|
|
194
|
-
} catch (_error) {
|
|
195
|
-
result.warnings.push('Output format contains invalid JSON example');
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Validate workflow modifications
|
|
205
|
-
* @private
|
|
206
|
-
*/
|
|
207
|
-
async validateWorkflowModification(originalContent, modifiedContent, options) {
|
|
208
|
-
const result = {
|
|
209
|
-
valid: true,
|
|
210
|
-
errors: [],
|
|
211
|
-
warnings: [],
|
|
212
|
-
suggestions: []
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const _originalWorkflow = yaml.load(originalContent);
|
|
217
|
-
const modifiedWorkflow = yaml.load(modifiedContent);
|
|
218
|
-
|
|
219
|
-
// Validate YAML structure
|
|
220
|
-
const yamlValidation = validateYAML(modifiedContent);
|
|
221
|
-
if (!yamlValidation.valid) {
|
|
222
|
-
result.valid = false;
|
|
223
|
-
result.errors.push(`YAML validation failed: ${yamlValidation.error}`);
|
|
224
|
-
return result;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Check required fields
|
|
228
|
-
if (!modifiedWorkflow.name) {
|
|
229
|
-
result.errors.push('Workflow name is required');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (!modifiedWorkflow.phases || Object.keys(modifiedWorkflow.phases).length === 0) {
|
|
233
|
-
result.errors.push('Workflow must have at least one phase');
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Validate phase structure
|
|
237
|
-
const phaseSequences = [];
|
|
238
|
-
for (const [phaseName, phase] of Object.entries(modifiedWorkflow.phases || {})) {
|
|
239
|
-
if (!phase.sequence) {
|
|
240
|
-
result.errors.push(`Phase '${phaseName}' missing sequence number`);
|
|
241
|
-
} else {
|
|
242
|
-
phaseSequences.push(phase.sequence);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (!phase.agents || phase.agents.length === 0) {
|
|
246
|
-
result.errors.push(`Phase '${phaseName}' must have at least one agent`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Validate agent references
|
|
250
|
-
for (const agent of phase.agents || []) {
|
|
251
|
-
const agentExists = await this.checkAgentExists(agent);
|
|
252
|
-
if (!agentExists) {
|
|
253
|
-
result.warnings.push(`Agent '${agent}' referenced in phase '${phaseName}' not found`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Check for sequence gaps
|
|
259
|
-
phaseSequences.sort((a, b) => a - b);
|
|
260
|
-
for (let i = 1; i < phaseSequences.length; i++) {
|
|
261
|
-
if (phaseSequences[i] - phaseSequences[i-1] > 2) {
|
|
262
|
-
result.warnings.push('Large gap in phase sequences detected');
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Validate entry/exit criteria references
|
|
267
|
-
for (const [phaseName, phase] of Object.entries(modifiedWorkflow.phases || {})) {
|
|
268
|
-
if (phase.entry_criteria) {
|
|
269
|
-
for (const criteria of phase.entry_criteria) {
|
|
270
|
-
// Check if criteria references valid artifacts or phases
|
|
271
|
-
const referencesValid = this.validateCriteriaReferences(
|
|
272
|
-
criteria,
|
|
273
|
-
modifiedWorkflow
|
|
274
|
-
);
|
|
275
|
-
if (!referencesValid) {
|
|
276
|
-
result.warnings.push(
|
|
277
|
-
`Entry criteria '${criteria}' in phase '${phaseName}' may reference non-existent artifact`
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
} catch (_error) {
|
|
285
|
-
result.valid = false;
|
|
286
|
-
result.errors.push(`Failed to parse workflow: ${error.message}`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return result;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Validate template modifications
|
|
294
|
-
* @private
|
|
295
|
-
*/
|
|
296
|
-
async validateTemplateModification(originalContent, modifiedContent, options) {
|
|
297
|
-
const result = {
|
|
298
|
-
valid: true,
|
|
299
|
-
errors: [],
|
|
300
|
-
warnings: [],
|
|
301
|
-
suggestions: []
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
// Check for placeholder consistency
|
|
305
|
-
const placeholders = modifiedContent.match(/\{\{[^}]+\}\}/g) || [];
|
|
306
|
-
const uniquePlaceholders = [...new Set(placeholders)];
|
|
307
|
-
|
|
308
|
-
// Warn about unreplaced placeholders
|
|
309
|
-
if (uniquePlaceholders.length > 0) {
|
|
310
|
-
result.suggestions.push(
|
|
311
|
-
`Template contains ${uniquePlaceholders.length} placeholders: ${uniquePlaceholders.join(', ')}`
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Validate LLM instruction blocks
|
|
316
|
-
const llmBlocks = modifiedContent.match(/\[\[LLM:([\s\S]*?)\]\]/g) || [];
|
|
317
|
-
for (const block of llmBlocks) {
|
|
318
|
-
if (block.length > 1000) {
|
|
319
|
-
result.warnings.push('LLM instruction block exceeds recommended length (1000 chars)');
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return result;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Validate dependencies exist
|
|
328
|
-
* @private
|
|
329
|
-
*/
|
|
330
|
-
async validateDependencies(dependencies) {
|
|
331
|
-
const result = {
|
|
332
|
-
valid: true,
|
|
333
|
-
errors: [],
|
|
334
|
-
warnings: []
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const baseDir = path.join(process.cwd(), 'aios-core');
|
|
338
|
-
|
|
339
|
-
for (const [type, files] of Object.entries(dependencies)) {
|
|
340
|
-
if (!Array.isArray(files)) continue;
|
|
341
|
-
|
|
342
|
-
const typeDir = path.join(baseDir, type);
|
|
343
|
-
|
|
344
|
-
for (const file of files) {
|
|
345
|
-
const filePath = path.join(typeDir, file);
|
|
346
|
-
try {
|
|
347
|
-
await fs.access(filePath);
|
|
348
|
-
} catch (_error) {
|
|
349
|
-
result.warnings.push(`Dependency not found: ${type}/${file}`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return result;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Validate markdown content
|
|
359
|
-
* @private
|
|
360
|
-
*/
|
|
361
|
-
validateMarkdown(content) {
|
|
362
|
-
const result = {
|
|
363
|
-
valid: true,
|
|
364
|
-
errors: [],
|
|
365
|
-
warnings: []
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
// Check for broken internal links
|
|
369
|
-
const internalLinks = content.match(/\[([^\]]+)\]\(#[^)]+\)/g) || [];
|
|
370
|
-
for (const link of internalLinks) {
|
|
371
|
-
const anchor = link.match(/#([^)]+)/)[1];
|
|
372
|
-
const headingRegex = new RegExp(`^#+.*${anchor}`, 'mi');
|
|
373
|
-
if (!headingRegex.test(content)) {
|
|
374
|
-
result.warnings.push(`Broken internal link: ${link}`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Check for code block closure
|
|
379
|
-
const codeBlocks = content.split('```');
|
|
380
|
-
if (codeBlocks.length % 2 === 0) {
|
|
381
|
-
result.errors.push('Unclosed code block detected');
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return result;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Validate security concerns
|
|
389
|
-
* @private
|
|
390
|
-
*/
|
|
391
|
-
async validateSecurity(content, componentType) {
|
|
392
|
-
const result = {
|
|
393
|
-
valid: true,
|
|
394
|
-
errors: [],
|
|
395
|
-
warnings: []
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const securityIssues = await this.securityChecker.checkContent(content);
|
|
399
|
-
|
|
400
|
-
if (securityIssues.length > 0) {
|
|
401
|
-
for (const issue of securityIssues) {
|
|
402
|
-
if (issue.severity === 'high') {
|
|
403
|
-
result.errors.push(`Security issue: ${issue.message}`);
|
|
404
|
-
} else {
|
|
405
|
-
result.warnings.push(`Security concern: ${issue.message}`);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return result;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Detect breaking changes
|
|
415
|
-
* @private
|
|
416
|
-
*/
|
|
417
|
-
async detectBreakingChanges(_componentType, originalContent, modifiedContent) {
|
|
418
|
-
const breakingChanges = [];
|
|
419
|
-
|
|
420
|
-
switch (_componentType) {
|
|
421
|
-
case 'agent':
|
|
422
|
-
// Check for removed commands
|
|
423
|
-
try {
|
|
424
|
-
const originalParts = this.parseAgentContent(originalContent);
|
|
425
|
-
const modifiedParts = this.parseAgentContent(modifiedContent);
|
|
426
|
-
const originalMeta = yaml.load(originalParts.yaml);
|
|
427
|
-
const modifiedMeta = yaml.load(modifiedParts.yaml);
|
|
428
|
-
|
|
429
|
-
if (originalMeta.commands && modifiedMeta.commands) {
|
|
430
|
-
const removedCommands = Object.keys(originalMeta.commands)
|
|
431
|
-
.filter(cmd => !modifiedMeta.commands[cmd]);
|
|
432
|
-
|
|
433
|
-
if (removedCommands.length > 0) {
|
|
434
|
-
breakingChanges.push({
|
|
435
|
-
type: 'removed_commands',
|
|
436
|
-
items: removedCommands,
|
|
437
|
-
impact: 'Users relying on these commands will need to update their workflows'
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
} catch (_error) {
|
|
442
|
-
// Ignore parsing errors here
|
|
443
|
-
}
|
|
444
|
-
break;
|
|
445
|
-
|
|
446
|
-
case 'task':
|
|
447
|
-
// Check for changed output format
|
|
448
|
-
const originalOutput = originalContent.match(/## Output Format[\s\S]*?```[\s\S]*?```/);
|
|
449
|
-
const modifiedOutput = modifiedContent.match(/## Output Format[\s\S]*?```[\s\S]*?```/);
|
|
450
|
-
|
|
451
|
-
if (originalOutput && modifiedOutput && originalOutput[0] !== modifiedOutput[0]) {
|
|
452
|
-
breakingChanges.push({
|
|
453
|
-
type: 'output_format_changed',
|
|
454
|
-
impact: 'Components consuming this task output may need updates'
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
break;
|
|
458
|
-
|
|
459
|
-
case 'workflow':
|
|
460
|
-
// Check for removed phases
|
|
461
|
-
try {
|
|
462
|
-
const _originalWorkflow = yaml.load(originalContent);
|
|
463
|
-
const modifiedWorkflow = yaml.load(modifiedContent);
|
|
464
|
-
|
|
465
|
-
const originalPhases = Object.keys(originalWorkflow.phases || {});
|
|
466
|
-
const modifiedPhases = Object.keys(modifiedWorkflow.phases || {});
|
|
467
|
-
|
|
468
|
-
const removedPhases = originalPhases.filter(p => !modifiedPhases.includes(p));
|
|
469
|
-
|
|
470
|
-
if (removedPhases.length > 0) {
|
|
471
|
-
breakingChanges.push({
|
|
472
|
-
type: 'removed_phases',
|
|
473
|
-
items: removedPhases,
|
|
474
|
-
impact: 'Projects using this workflow may fail at removed phases'
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
} catch (_error) {
|
|
478
|
-
// Ignore parsing errors here
|
|
479
|
-
}
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return breakingChanges;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Parse agent content into YAML and markdown sections
|
|
488
|
-
* @private
|
|
489
|
-
*/
|
|
490
|
-
parseAgentContent(content) {
|
|
491
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
492
|
-
if (!match) {
|
|
493
|
-
throw new Error('Invalid agent content format');
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
return {
|
|
497
|
-
yaml: match[1],
|
|
498
|
-
markdown: match[2]
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Check if an agent exists
|
|
504
|
-
* @private
|
|
505
|
-
*/
|
|
506
|
-
async checkAgentExists(agentName) {
|
|
507
|
-
const agentPath = path.join(process.cwd(), 'aios-core', 'agents', `${agentName}.md`);
|
|
508
|
-
try {
|
|
509
|
-
await fs.access(agentPath);
|
|
510
|
-
return true;
|
|
511
|
-
} catch {
|
|
512
|
-
return false;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Validate criteria references
|
|
518
|
-
* @private
|
|
519
|
-
*/
|
|
520
|
-
validateCriteriaReferences(criteria, workflow) {
|
|
521
|
-
// Simple check - could be enhanced
|
|
522
|
-
const artifacts = new Set();
|
|
523
|
-
|
|
524
|
-
for (const phase of Object.values(workflow.phases || {})) {
|
|
525
|
-
if (phase.artifacts) {
|
|
526
|
-
phase.artifacts.forEach(a => artifacts.add(a));
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Check if criteria mentions any known artifact
|
|
531
|
-
for (const artifact of artifacts) {
|
|
532
|
-
if (criteria.toLowerCase().includes(artifact.toLowerCase())) {
|
|
533
|
-
return true;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return false;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Merge validation results
|
|
542
|
-
* @private
|
|
543
|
-
*/
|
|
544
|
-
mergeResults(target, source) {
|
|
545
|
-
target.errors.push(...(source.errors || []));
|
|
546
|
-
target.warnings.push(...(source.warnings || []));
|
|
547
|
-
target.suggestions.push(...(source.suggestions || []));
|
|
548
|
-
|
|
549
|
-
if (source.valid === false) {
|
|
550
|
-
target.valid = false;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const yaml = require('js-yaml');
|
|
4
|
+
const { validateYAML } = require('./yaml-validator');
|
|
5
|
+
const DependencyAnalyzer = require('./dependency-analyzer');
|
|
6
|
+
const SecurityChecker = require('./security-checker');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validates component modifications before applying them
|
|
10
|
+
*/
|
|
11
|
+
class ModificationValidator {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.dependencyAnalyzer = new DependencyAnalyzer();
|
|
14
|
+
this.securityChecker = new SecurityChecker();
|
|
15
|
+
this.validationRules = {
|
|
16
|
+
agent: this.validateAgentModification.bind(this),
|
|
17
|
+
task: this.validateTaskModification.bind(this),
|
|
18
|
+
workflow: this.validateWorkflowModification.bind(this),
|
|
19
|
+
template: this.validateTemplateModification.bind(this)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate a component modification
|
|
25
|
+
* @param {string} componentType - Type of component (agent, task, workflow, etc.)
|
|
26
|
+
* @param {string} originalContent - Original component content
|
|
27
|
+
* @param {string} modifiedContent - Modified component content
|
|
28
|
+
* @param {Object} options - Validation options
|
|
29
|
+
* @returns {Object} Validation result with errors and warnings
|
|
30
|
+
*/
|
|
31
|
+
async validateModification(_componentType, originalContent, modifiedContent, options = {}) {
|
|
32
|
+
const result = {
|
|
33
|
+
valid: true,
|
|
34
|
+
errors: [],
|
|
35
|
+
warnings: [],
|
|
36
|
+
suggestions: [],
|
|
37
|
+
breakingChanges: []
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Basic validation
|
|
41
|
+
if (!originalContent || !modifiedContent) {
|
|
42
|
+
result.valid = false;
|
|
43
|
+
result.errors.push('Original or modified content is empty');
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Run type-specific validation
|
|
48
|
+
if (this.validationRules[componentType]) {
|
|
49
|
+
const typeResult = await this.validationRules[componentType](
|
|
50
|
+
originalContent,
|
|
51
|
+
modifiedContent,
|
|
52
|
+
options
|
|
53
|
+
);
|
|
54
|
+
this.mergeResults(result, typeResult);
|
|
55
|
+
} else {
|
|
56
|
+
result.warnings.push(`No specific validation rules for component type: ${componentType}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Run security validation
|
|
60
|
+
const securityResult = await this.validateSecurity(modifiedContent, componentType);
|
|
61
|
+
this.mergeResults(result, securityResult);
|
|
62
|
+
|
|
63
|
+
// Check for breaking changes
|
|
64
|
+
const breakingChanges = await this.detectBreakingChanges(
|
|
65
|
+
_componentType,
|
|
66
|
+
originalContent,
|
|
67
|
+
modifiedContent
|
|
68
|
+
);
|
|
69
|
+
result.breakingChanges = breakingChanges;
|
|
70
|
+
|
|
71
|
+
result.valid = result.errors.length === 0;
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate agent modifications
|
|
77
|
+
* @private
|
|
78
|
+
*/
|
|
79
|
+
async validateAgentModification(originalContent, modifiedContent, options) {
|
|
80
|
+
const result = {
|
|
81
|
+
valid: true,
|
|
82
|
+
errors: [],
|
|
83
|
+
warnings: [],
|
|
84
|
+
suggestions: []
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Parse agent content
|
|
89
|
+
const originalParts = this.parseAgentContent(originalContent);
|
|
90
|
+
const modifiedParts = this.parseAgentContent(modifiedContent);
|
|
91
|
+
|
|
92
|
+
// Validate YAML structure
|
|
93
|
+
const yamlValidation = validateYAML(modifiedParts.yaml);
|
|
94
|
+
if (!yamlValidation.valid) {
|
|
95
|
+
result.valid = false;
|
|
96
|
+
result.errors.push(`YAML validation failed: ${yamlValidation.error}`);
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const originalMeta = yaml.load(originalParts.yaml);
|
|
101
|
+
const modifiedMeta = yaml.load(modifiedParts.yaml);
|
|
102
|
+
|
|
103
|
+
// Check required fields
|
|
104
|
+
const requiredFields = ['name', 'id', 'title', 'icon', 'whenToUse'];
|
|
105
|
+
for (const field of requiredFields) {
|
|
106
|
+
if (!modifiedMeta[field]) {
|
|
107
|
+
result.errors.push(`Required field missing: ${field}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Validate dependencies
|
|
112
|
+
if (modifiedMeta.dependencies) {
|
|
113
|
+
const depValidation = await this.validateDependencies(modifiedMeta.dependencies);
|
|
114
|
+
this.mergeResults(result, depValidation);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check command structure
|
|
118
|
+
if (modifiedMeta.commands) {
|
|
119
|
+
for (const [cmd, desc] of Object.entries(modifiedMeta.commands)) {
|
|
120
|
+
if (!desc || typeof desc !== 'string') {
|
|
121
|
+
result.errors.push(`Invalid command description for: ${cmd}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check for removed commands (breaking change)
|
|
127
|
+
if (originalMeta.commands && modifiedMeta.commands) {
|
|
128
|
+
const removedCommands = Object.keys(originalMeta.commands)
|
|
129
|
+
.filter(cmd => !modifiedMeta.commands[cmd]);
|
|
130
|
+
|
|
131
|
+
if (removedCommands.length > 0) {
|
|
132
|
+
result.warnings.push(`Commands removed: ${removedCommands.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Validate markdown content
|
|
137
|
+
const markdownValidation = this.validateMarkdown(modifiedParts.markdown);
|
|
138
|
+
this.mergeResults(result, markdownValidation);
|
|
139
|
+
|
|
140
|
+
} catch (_error) {
|
|
141
|
+
result.valid = false;
|
|
142
|
+
result.errors.push(`Failed to parse agent content: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate task modifications
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
async validateTaskModification(originalContent, modifiedContent, options) {
|
|
153
|
+
const result = {
|
|
154
|
+
valid: true,
|
|
155
|
+
errors: [],
|
|
156
|
+
warnings: [],
|
|
157
|
+
suggestions: []
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Check for required sections
|
|
161
|
+
const requiredSections = ['## Purpose', '## Task Execution'];
|
|
162
|
+
for (const section of requiredSections) {
|
|
163
|
+
if (!modifiedContent.includes(section)) {
|
|
164
|
+
result.errors.push(`Required section missing: ${section}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate elicitation blocks
|
|
169
|
+
const elicitationBlocks = modifiedContent.match(/\[\[LLM:([\s\S]*?)\]\]/g) || [];
|
|
170
|
+
for (const block of elicitationBlocks) {
|
|
171
|
+
if (!block.includes(']]')) {
|
|
172
|
+
result.errors.push('Unclosed elicitation block found');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check for task flow consistency
|
|
177
|
+
const taskSteps = modifiedContent.match(/###\s+\d+\.\s+/g) || [];
|
|
178
|
+
const expectedSteps = taskSteps.length;
|
|
179
|
+
for (let i = 1; i <= expectedSteps; i++) {
|
|
180
|
+
if (!modifiedContent.includes(`### ${i}.`)) {
|
|
181
|
+
result.warnings.push(`Task step ${i} appears to be missing or misnumbered`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Validate output format if specified
|
|
186
|
+
const outputMatch = modifiedContent.match(/## Output Format[\s\S]*?```([\s\S]*?)```/);
|
|
187
|
+
if (outputMatch) {
|
|
188
|
+
const outputFormat = outputMatch[1].trim();
|
|
189
|
+
if (outputFormat.startsWith('json')) {
|
|
190
|
+
try {
|
|
191
|
+
// Extract JSON and validate
|
|
192
|
+
const jsonContent = outputFormat.replace(/^json\s*/, '');
|
|
193
|
+
JSON.parse(jsonContent);
|
|
194
|
+
} catch (_error) {
|
|
195
|
+
result.warnings.push('Output format contains invalid JSON example');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Validate workflow modifications
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
async validateWorkflowModification(originalContent, modifiedContent, options) {
|
|
208
|
+
const result = {
|
|
209
|
+
valid: true,
|
|
210
|
+
errors: [],
|
|
211
|
+
warnings: [],
|
|
212
|
+
suggestions: []
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const _originalWorkflow = yaml.load(originalContent);
|
|
217
|
+
const modifiedWorkflow = yaml.load(modifiedContent);
|
|
218
|
+
|
|
219
|
+
// Validate YAML structure
|
|
220
|
+
const yamlValidation = validateYAML(modifiedContent);
|
|
221
|
+
if (!yamlValidation.valid) {
|
|
222
|
+
result.valid = false;
|
|
223
|
+
result.errors.push(`YAML validation failed: ${yamlValidation.error}`);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check required fields
|
|
228
|
+
if (!modifiedWorkflow.name) {
|
|
229
|
+
result.errors.push('Workflow name is required');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!modifiedWorkflow.phases || Object.keys(modifiedWorkflow.phases).length === 0) {
|
|
233
|
+
result.errors.push('Workflow must have at least one phase');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Validate phase structure
|
|
237
|
+
const phaseSequences = [];
|
|
238
|
+
for (const [phaseName, phase] of Object.entries(modifiedWorkflow.phases || {})) {
|
|
239
|
+
if (!phase.sequence) {
|
|
240
|
+
result.errors.push(`Phase '${phaseName}' missing sequence number`);
|
|
241
|
+
} else {
|
|
242
|
+
phaseSequences.push(phase.sequence);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!phase.agents || phase.agents.length === 0) {
|
|
246
|
+
result.errors.push(`Phase '${phaseName}' must have at least one agent`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Validate agent references
|
|
250
|
+
for (const agent of phase.agents || []) {
|
|
251
|
+
const agentExists = await this.checkAgentExists(agent);
|
|
252
|
+
if (!agentExists) {
|
|
253
|
+
result.warnings.push(`Agent '${agent}' referenced in phase '${phaseName}' not found`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check for sequence gaps
|
|
259
|
+
phaseSequences.sort((a, b) => a - b);
|
|
260
|
+
for (let i = 1; i < phaseSequences.length; i++) {
|
|
261
|
+
if (phaseSequences[i] - phaseSequences[i-1] > 2) {
|
|
262
|
+
result.warnings.push('Large gap in phase sequences detected');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Validate entry/exit criteria references
|
|
267
|
+
for (const [phaseName, phase] of Object.entries(modifiedWorkflow.phases || {})) {
|
|
268
|
+
if (phase.entry_criteria) {
|
|
269
|
+
for (const criteria of phase.entry_criteria) {
|
|
270
|
+
// Check if criteria references valid artifacts or phases
|
|
271
|
+
const referencesValid = this.validateCriteriaReferences(
|
|
272
|
+
criteria,
|
|
273
|
+
modifiedWorkflow
|
|
274
|
+
);
|
|
275
|
+
if (!referencesValid) {
|
|
276
|
+
result.warnings.push(
|
|
277
|
+
`Entry criteria '${criteria}' in phase '${phaseName}' may reference non-existent artifact`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
} catch (_error) {
|
|
285
|
+
result.valid = false;
|
|
286
|
+
result.errors.push(`Failed to parse workflow: ${error.message}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Validate template modifications
|
|
294
|
+
* @private
|
|
295
|
+
*/
|
|
296
|
+
async validateTemplateModification(originalContent, modifiedContent, options) {
|
|
297
|
+
const result = {
|
|
298
|
+
valid: true,
|
|
299
|
+
errors: [],
|
|
300
|
+
warnings: [],
|
|
301
|
+
suggestions: []
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Check for placeholder consistency
|
|
305
|
+
const placeholders = modifiedContent.match(/\{\{[^}]+\}\}/g) || [];
|
|
306
|
+
const uniquePlaceholders = [...new Set(placeholders)];
|
|
307
|
+
|
|
308
|
+
// Warn about unreplaced placeholders
|
|
309
|
+
if (uniquePlaceholders.length > 0) {
|
|
310
|
+
result.suggestions.push(
|
|
311
|
+
`Template contains ${uniquePlaceholders.length} placeholders: ${uniquePlaceholders.join(', ')}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Validate LLM instruction blocks
|
|
316
|
+
const llmBlocks = modifiedContent.match(/\[\[LLM:([\s\S]*?)\]\]/g) || [];
|
|
317
|
+
for (const block of llmBlocks) {
|
|
318
|
+
if (block.length > 1000) {
|
|
319
|
+
result.warnings.push('LLM instruction block exceeds recommended length (1000 chars)');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Validate dependencies exist
|
|
328
|
+
* @private
|
|
329
|
+
*/
|
|
330
|
+
async validateDependencies(dependencies) {
|
|
331
|
+
const result = {
|
|
332
|
+
valid: true,
|
|
333
|
+
errors: [],
|
|
334
|
+
warnings: []
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const baseDir = path.join(process.cwd(), 'aios-core');
|
|
338
|
+
|
|
339
|
+
for (const [type, files] of Object.entries(dependencies)) {
|
|
340
|
+
if (!Array.isArray(files)) continue;
|
|
341
|
+
|
|
342
|
+
const typeDir = path.join(baseDir, type);
|
|
343
|
+
|
|
344
|
+
for (const file of files) {
|
|
345
|
+
const filePath = path.join(typeDir, file);
|
|
346
|
+
try {
|
|
347
|
+
await fs.access(filePath);
|
|
348
|
+
} catch (_error) {
|
|
349
|
+
result.warnings.push(`Dependency not found: ${type}/${file}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Validate markdown content
|
|
359
|
+
* @private
|
|
360
|
+
*/
|
|
361
|
+
validateMarkdown(content) {
|
|
362
|
+
const result = {
|
|
363
|
+
valid: true,
|
|
364
|
+
errors: [],
|
|
365
|
+
warnings: []
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// Check for broken internal links
|
|
369
|
+
const internalLinks = content.match(/\[([^\]]+)\]\(#[^)]+\)/g) || [];
|
|
370
|
+
for (const link of internalLinks) {
|
|
371
|
+
const anchor = link.match(/#([^)]+)/)[1];
|
|
372
|
+
const headingRegex = new RegExp(`^#+.*${anchor}`, 'mi');
|
|
373
|
+
if (!headingRegex.test(content)) {
|
|
374
|
+
result.warnings.push(`Broken internal link: ${link}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Check for code block closure
|
|
379
|
+
const codeBlocks = content.split('```');
|
|
380
|
+
if (codeBlocks.length % 2 === 0) {
|
|
381
|
+
result.errors.push('Unclosed code block detected');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Validate security concerns
|
|
389
|
+
* @private
|
|
390
|
+
*/
|
|
391
|
+
async validateSecurity(content, componentType) {
|
|
392
|
+
const result = {
|
|
393
|
+
valid: true,
|
|
394
|
+
errors: [],
|
|
395
|
+
warnings: []
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const securityIssues = await this.securityChecker.checkContent(content);
|
|
399
|
+
|
|
400
|
+
if (securityIssues.length > 0) {
|
|
401
|
+
for (const issue of securityIssues) {
|
|
402
|
+
if (issue.severity === 'high') {
|
|
403
|
+
result.errors.push(`Security issue: ${issue.message}`);
|
|
404
|
+
} else {
|
|
405
|
+
result.warnings.push(`Security concern: ${issue.message}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Detect breaking changes
|
|
415
|
+
* @private
|
|
416
|
+
*/
|
|
417
|
+
async detectBreakingChanges(_componentType, originalContent, modifiedContent) {
|
|
418
|
+
const breakingChanges = [];
|
|
419
|
+
|
|
420
|
+
switch (_componentType) {
|
|
421
|
+
case 'agent':
|
|
422
|
+
// Check for removed commands
|
|
423
|
+
try {
|
|
424
|
+
const originalParts = this.parseAgentContent(originalContent);
|
|
425
|
+
const modifiedParts = this.parseAgentContent(modifiedContent);
|
|
426
|
+
const originalMeta = yaml.load(originalParts.yaml);
|
|
427
|
+
const modifiedMeta = yaml.load(modifiedParts.yaml);
|
|
428
|
+
|
|
429
|
+
if (originalMeta.commands && modifiedMeta.commands) {
|
|
430
|
+
const removedCommands = Object.keys(originalMeta.commands)
|
|
431
|
+
.filter(cmd => !modifiedMeta.commands[cmd]);
|
|
432
|
+
|
|
433
|
+
if (removedCommands.length > 0) {
|
|
434
|
+
breakingChanges.push({
|
|
435
|
+
type: 'removed_commands',
|
|
436
|
+
items: removedCommands,
|
|
437
|
+
impact: 'Users relying on these commands will need to update their workflows'
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} catch (_error) {
|
|
442
|
+
// Ignore parsing errors here
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
|
|
446
|
+
case 'task':
|
|
447
|
+
// Check for changed output format
|
|
448
|
+
const originalOutput = originalContent.match(/## Output Format[\s\S]*?```[\s\S]*?```/);
|
|
449
|
+
const modifiedOutput = modifiedContent.match(/## Output Format[\s\S]*?```[\s\S]*?```/);
|
|
450
|
+
|
|
451
|
+
if (originalOutput && modifiedOutput && originalOutput[0] !== modifiedOutput[0]) {
|
|
452
|
+
breakingChanges.push({
|
|
453
|
+
type: 'output_format_changed',
|
|
454
|
+
impact: 'Components consuming this task output may need updates'
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
break;
|
|
458
|
+
|
|
459
|
+
case 'workflow':
|
|
460
|
+
// Check for removed phases
|
|
461
|
+
try {
|
|
462
|
+
const _originalWorkflow = yaml.load(originalContent);
|
|
463
|
+
const modifiedWorkflow = yaml.load(modifiedContent);
|
|
464
|
+
|
|
465
|
+
const originalPhases = Object.keys(originalWorkflow.phases || {});
|
|
466
|
+
const modifiedPhases = Object.keys(modifiedWorkflow.phases || {});
|
|
467
|
+
|
|
468
|
+
const removedPhases = originalPhases.filter(p => !modifiedPhases.includes(p));
|
|
469
|
+
|
|
470
|
+
if (removedPhases.length > 0) {
|
|
471
|
+
breakingChanges.push({
|
|
472
|
+
type: 'removed_phases',
|
|
473
|
+
items: removedPhases,
|
|
474
|
+
impact: 'Projects using this workflow may fail at removed phases'
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
} catch (_error) {
|
|
478
|
+
// Ignore parsing errors here
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return breakingChanges;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Parse agent content into YAML and markdown sections
|
|
488
|
+
* @private
|
|
489
|
+
*/
|
|
490
|
+
parseAgentContent(content) {
|
|
491
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
492
|
+
if (!match) {
|
|
493
|
+
throw new Error('Invalid agent content format');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
yaml: match[1],
|
|
498
|
+
markdown: match[2]
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Check if an agent exists
|
|
504
|
+
* @private
|
|
505
|
+
*/
|
|
506
|
+
async checkAgentExists(agentName) {
|
|
507
|
+
const agentPath = path.join(process.cwd(), 'aios-core', 'agents', `${agentName}.md`);
|
|
508
|
+
try {
|
|
509
|
+
await fs.access(agentPath);
|
|
510
|
+
return true;
|
|
511
|
+
} catch {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Validate criteria references
|
|
518
|
+
* @private
|
|
519
|
+
*/
|
|
520
|
+
validateCriteriaReferences(criteria, workflow) {
|
|
521
|
+
// Simple check - could be enhanced
|
|
522
|
+
const artifacts = new Set();
|
|
523
|
+
|
|
524
|
+
for (const phase of Object.values(workflow.phases || {})) {
|
|
525
|
+
if (phase.artifacts) {
|
|
526
|
+
phase.artifacts.forEach(a => artifacts.add(a));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Check if criteria mentions any known artifact
|
|
531
|
+
for (const artifact of artifacts) {
|
|
532
|
+
if (criteria.toLowerCase().includes(artifact.toLowerCase())) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Merge validation results
|
|
542
|
+
* @private
|
|
543
|
+
*/
|
|
544
|
+
mergeResults(target, source) {
|
|
545
|
+
target.errors.push(...(source.errors || []));
|
|
546
|
+
target.warnings.push(...(source.warnings || []));
|
|
547
|
+
target.suggestions.push(...(source.suggestions || []));
|
|
548
|
+
|
|
549
|
+
if (source.valid === false) {
|
|
550
|
+
target.valid = false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
555
|
module.exports = ModificationValidator;
|