File size: 5,652 Bytes
1dbc34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
 * Validation utilities for SpecOutput objects.
 */

import type { SpecOutput } from '@automaker/types';

/**
 * Validation result containing errors if any.
 */
export interface ValidationResult {
  valid: boolean;
  errors: string[];
}

/**
 * Validate a SpecOutput object for required fields and data integrity.
 *
 * @param spec - The SpecOutput object to validate
 * @returns ValidationResult with errors if validation fails
 */
export function validateSpec(spec: SpecOutput | null | undefined): ValidationResult {
  const errors: string[] = [];

  if (!spec) {
    return { valid: false, errors: ['Spec is null or undefined'] };
  }

  // Required string fields
  if (!spec.project_name || typeof spec.project_name !== 'string') {
    errors.push('project_name is required and must be a string');
  } else if (spec.project_name.trim().length === 0) {
    errors.push('project_name cannot be empty');
  }

  if (!spec.overview || typeof spec.overview !== 'string') {
    errors.push('overview is required and must be a string');
  } else if (spec.overview.trim().length === 0) {
    errors.push('overview cannot be empty');
  }

  // Required array fields
  if (!Array.isArray(spec.technology_stack)) {
    errors.push('technology_stack is required and must be an array');
  } else if (spec.technology_stack.length === 0) {
    errors.push('technology_stack must have at least one item');
  } else if (spec.technology_stack.some((t) => typeof t !== 'string' || t.trim() === '')) {
    errors.push('technology_stack items must be non-empty strings');
  }

  if (!Array.isArray(spec.core_capabilities)) {
    errors.push('core_capabilities is required and must be an array');
  } else if (spec.core_capabilities.length === 0) {
    errors.push('core_capabilities must have at least one item');
  } else if (spec.core_capabilities.some((c) => typeof c !== 'string' || c.trim() === '')) {
    errors.push('core_capabilities items must be non-empty strings');
  }

  // Implemented features
  if (!Array.isArray(spec.implemented_features)) {
    errors.push('implemented_features is required and must be an array');
  } else {
    spec.implemented_features.forEach((f, i) => {
      if (!f.name || typeof f.name !== 'string' || f.name.trim() === '') {
        errors.push(`implemented_features[${i}].name is required and must be a non-empty string`);
      }
      if (!f.description || typeof f.description !== 'string') {
        errors.push(`implemented_features[${i}].description is required and must be a string`);
      }
      if (f.file_locations !== undefined) {
        if (!Array.isArray(f.file_locations)) {
          errors.push(`implemented_features[${i}].file_locations must be an array if provided`);
        } else if (f.file_locations.some((loc) => typeof loc !== 'string' || loc.trim() === '')) {
          errors.push(`implemented_features[${i}].file_locations items must be non-empty strings`);
        }
      }
    });
  }

  // Optional array fields
  if (spec.additional_requirements !== undefined) {
    if (!Array.isArray(spec.additional_requirements)) {
      errors.push('additional_requirements must be an array if provided');
    } else if (spec.additional_requirements.some((r) => typeof r !== 'string' || r.trim() === '')) {
      errors.push('additional_requirements items must be non-empty strings');
    }
  }

  if (spec.development_guidelines !== undefined) {
    if (!Array.isArray(spec.development_guidelines)) {
      errors.push('development_guidelines must be an array if provided');
    } else if (spec.development_guidelines.some((g) => typeof g !== 'string' || g.trim() === '')) {
      errors.push('development_guidelines items must be non-empty strings');
    }
  }

  // Implementation roadmap
  if (spec.implementation_roadmap !== undefined) {
    if (!Array.isArray(spec.implementation_roadmap)) {
      errors.push('implementation_roadmap must be an array if provided');
    } else {
      const validStatuses = ['completed', 'in_progress', 'pending'];
      spec.implementation_roadmap.forEach((r, i) => {
        if (!r.phase || typeof r.phase !== 'string' || r.phase.trim() === '') {
          errors.push(
            `implementation_roadmap[${i}].phase is required and must be a non-empty string`
          );
        }
        if (!r.status || !validStatuses.includes(r.status)) {
          errors.push(
            `implementation_roadmap[${i}].status must be one of: ${validStatuses.join(', ')}`
          );
        }
        if (!r.description || typeof r.description !== 'string') {
          errors.push(`implementation_roadmap[${i}].description is required and must be a string`);
        }
      });
    }
  }

  return { valid: errors.length === 0, errors };
}

/**
 * Check if XML content appears to be a valid spec XML (basic structure check).
 * This is a quick check, not a full validation.
 *
 * @param xmlContent - The XML content to check
 * @returns true if the content appears to be valid spec XML
 */
export function isValidSpecXml(xmlContent: string): boolean {
  if (!xmlContent || typeof xmlContent !== 'string') {
    return false;
  }

  // Check for essential elements
  const hasRoot = xmlContent.includes('<project_specification>');
  const hasProjectName = /<project_name>[\s\S]*?<\/project_name>/.test(xmlContent);
  const hasOverview = /<overview>[\s\S]*?<\/overview>/.test(xmlContent);
  const hasTechStack = /<technology_stack>[\s\S]*?<\/technology_stack>/.test(xmlContent);
  const hasCapabilities = /<core_capabilities>[\s\S]*?<\/core_capabilities>/.test(xmlContent);

  return hasRoot && hasProjectName && hasOverview && hasTechStack && hasCapabilities;
}