@paths.design/caws-cli 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +295 -150
  2. package/dist/budget-derivation.d.ts +35 -0
  3. package/dist/budget-derivation.d.ts.map +1 -0
  4. package/dist/budget-derivation.js +204 -0
  5. package/dist/cicd-optimizer.d.ts +142 -0
  6. package/dist/cicd-optimizer.d.ts.map +1 -0
  7. package/dist/cicd-optimizer.js +504 -0
  8. package/dist/commands/burnup.d.ts +6 -0
  9. package/dist/commands/burnup.d.ts.map +1 -0
  10. package/dist/commands/burnup.js +90 -0
  11. package/dist/commands/init.d.ts +5 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +514 -0
  14. package/dist/commands/provenance.d.ts +32 -0
  15. package/dist/commands/provenance.d.ts.map +1 -0
  16. package/dist/commands/provenance.js +979 -0
  17. package/dist/commands/tool.d.ts +13 -0
  18. package/dist/commands/tool.d.ts.map +1 -0
  19. package/dist/commands/tool.js +138 -0
  20. package/dist/commands/validate.d.ts +7 -0
  21. package/dist/commands/validate.d.ts.map +1 -0
  22. package/dist/commands/validate.js +80 -0
  23. package/dist/config/index.d.ts +29 -0
  24. package/dist/config/index.d.ts.map +1 -0
  25. package/dist/config/index.js +132 -0
  26. package/dist/error-handler.d.ts +50 -0
  27. package/dist/error-handler.d.ts.map +1 -0
  28. package/dist/error-handler.js +253 -0
  29. package/dist/generators/working-spec.d.ts +13 -0
  30. package/dist/generators/working-spec.d.ts.map +1 -0
  31. package/dist/generators/working-spec.js +204 -0
  32. package/dist/index.d.ts +3 -12
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +193 -2983
  35. package/dist/scaffold/cursor-hooks.d.ts +7 -0
  36. package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
  37. package/dist/scaffold/cursor-hooks.js +152 -0
  38. package/dist/scaffold/git-hooks.d.ts +20 -0
  39. package/dist/scaffold/git-hooks.d.ts.map +1 -0
  40. package/dist/scaffold/git-hooks.js +417 -0
  41. package/dist/scaffold/index.d.ts +20 -0
  42. package/dist/scaffold/index.d.ts.map +1 -0
  43. package/dist/scaffold/index.js +486 -0
  44. package/dist/test-analysis.d.ts +182 -0
  45. package/dist/test-analysis.d.ts.map +1 -0
  46. package/dist/test-analysis.js +580 -0
  47. package/dist/tool-interface.d.ts +236 -0
  48. package/dist/tool-interface.d.ts.map +1 -0
  49. package/dist/tool-interface.js +314 -0
  50. package/dist/tool-loader.d.ts +77 -0
  51. package/dist/tool-loader.d.ts.map +1 -0
  52. package/dist/tool-loader.js +298 -0
  53. package/dist/tool-validator.d.ts +72 -0
  54. package/dist/tool-validator.d.ts.map +1 -0
  55. package/dist/tool-validator.js +387 -0
  56. package/dist/utils/detection.d.ts +7 -0
  57. package/dist/utils/detection.d.ts.map +1 -0
  58. package/dist/utils/detection.js +174 -0
  59. package/dist/utils/finalization.d.ts +17 -0
  60. package/dist/utils/finalization.d.ts.map +1 -0
  61. package/dist/utils/finalization.js +229 -0
  62. package/dist/utils/project-analysis.d.ts +14 -0
  63. package/dist/utils/project-analysis.d.ts.map +1 -0
  64. package/dist/utils/project-analysis.js +105 -0
  65. package/dist/validation/spec-validation.d.ts +29 -0
  66. package/dist/validation/spec-validation.d.ts.map +1 -0
  67. package/dist/validation/spec-validation.js +376 -0
  68. package/dist/waivers-manager.d.ts +167 -0
  69. package/dist/waivers-manager.d.ts.map +1 -0
  70. package/dist/waivers-manager.js +549 -0
  71. package/package.json +10 -12
  72. package/templates/.cursor/README.md +311 -0
  73. package/templates/.cursor/hooks/audit.sh +55 -0
  74. package/templates/.cursor/hooks/block-dangerous.sh +77 -0
  75. package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
  76. package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
  77. package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
  78. package/templates/.cursor/hooks/format.sh +38 -0
  79. package/templates/.cursor/hooks/naming-check.sh +64 -0
  80. package/templates/.cursor/hooks/scan-secrets.sh +46 -0
  81. package/templates/.cursor/hooks/scope-guard.sh +52 -0
  82. package/templates/.cursor/hooks/validate-spec.sh +38 -0
  83. package/templates/.cursor/hooks.json +59 -0
  84. package/templates/.github/copilot/instructions.md +311 -0
  85. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
  86. package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
  87. package/templates/.vscode/launch.json +56 -0
  88. package/templates/.vscode/settings.json +93 -0
  89. package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
  90. package/templates/apps/tools/caws/README.md +1 -1
  91. package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
  92. package/templates/codemod/test.js +93 -1
  93. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  94. package/templates/apps/tools/caws/provenance.js.backup +0 -73
@@ -0,0 +1,376 @@
1
+ /**
2
+ * @fileoverview Working Spec Validation Utilities
3
+ * Functions for validating CAWS working specifications
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const { deriveBudget, checkBudgetCompliance } = require('../budget-derivation');
8
+
9
+ /**
10
+ * Basic validation of working spec
11
+ * @param {Object} spec - Working spec object
12
+ * @param {Object} options - Validation options
13
+ * @returns {Object} Validation result
14
+ */
15
+ const validateWorkingSpec = (spec, _options = {}) => {
16
+ try {
17
+ // Basic structural validation for essential fields
18
+ const requiredFields = [
19
+ 'id',
20
+ 'title',
21
+ 'risk_tier',
22
+ 'mode',
23
+ 'blast_radius',
24
+ 'operational_rollback_slo',
25
+ 'scope',
26
+ 'invariants',
27
+ 'acceptance',
28
+ 'non_functional',
29
+ 'contracts',
30
+ ];
31
+
32
+ // For new policy-based specs, change_budget is not required
33
+ // It's derived from policy.yaml + waivers
34
+
35
+ for (const field of requiredFields) {
36
+ if (!spec[field]) {
37
+ return {
38
+ valid: false,
39
+ errors: [
40
+ {
41
+ instancePath: `/${field}`,
42
+ message: `Missing required field: ${field}`,
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ }
48
+
49
+ // Validate specific field formats
50
+ if (!/^[A-Z]+-\d+$/.test(spec.id)) {
51
+ return {
52
+ valid: false,
53
+ errors: [
54
+ {
55
+ instancePath: '/id',
56
+ message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
57
+ },
58
+ ],
59
+ };
60
+ }
61
+
62
+ // Validate experimental mode
63
+ if (spec.experimental_mode) {
64
+ if (typeof spec.experimental_mode !== 'object') {
65
+ return {
66
+ valid: false,
67
+ errors: [
68
+ {
69
+ instancePath: '/experimental_mode',
70
+ message:
71
+ 'Experimental mode must be an object with enabled, rationale, and expires_at fields',
72
+ },
73
+ ],
74
+ };
75
+ }
76
+
77
+ const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
78
+ for (const field of requiredExpFields) {
79
+ if (!(field in spec.experimental_mode)) {
80
+ return {
81
+ valid: false,
82
+ errors: [
83
+ {
84
+ instancePath: `/experimental_mode/${field}`,
85
+ message: `Missing required experimental mode field: ${field}`,
86
+ },
87
+ ],
88
+ };
89
+ }
90
+ }
91
+
92
+ if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
93
+ return {
94
+ valid: false,
95
+ errors: [
96
+ {
97
+ instancePath: '/experimental_mode',
98
+ message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
99
+ },
100
+ ],
101
+ };
102
+ }
103
+ }
104
+
105
+ if (spec.risk_tier < 1 || spec.risk_tier > 3) {
106
+ return {
107
+ valid: false,
108
+ errors: [
109
+ {
110
+ instancePath: '/risk_tier',
111
+ message: 'Risk tier must be 1, 2, or 3',
112
+ },
113
+ ],
114
+ };
115
+ }
116
+
117
+ if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
118
+ return {
119
+ valid: false,
120
+ errors: [
121
+ {
122
+ instancePath: '/scope/in',
123
+ message: 'Scope IN must not be empty',
124
+ },
125
+ ],
126
+ };
127
+ }
128
+
129
+ return { valid: true };
130
+ } catch (error) {
131
+ return {
132
+ valid: false,
133
+ errors: [
134
+ {
135
+ instancePath: '',
136
+ message: `Validation error: ${error.message}`,
137
+ },
138
+ ],
139
+ };
140
+ }
141
+ };
142
+
143
+ /**
144
+ * Enhanced validation with suggestions and auto-fix
145
+ * @param {Object} spec - Working spec object
146
+ * @param {Object} options - Validation options
147
+ * @returns {Object} Enhanced validation result
148
+ */
149
+ function validateWorkingSpecWithSuggestions(spec, options = {}) {
150
+ const { autoFix = false, checkBudget = false, projectRoot } = options;
151
+
152
+ try {
153
+ // Basic structural validation for essential fields
154
+ const requiredFields = [
155
+ 'id',
156
+ 'title',
157
+ 'risk_tier',
158
+ 'mode',
159
+ 'blast_radius',
160
+ 'operational_rollback_slo',
161
+ 'scope',
162
+ 'invariants',
163
+ 'acceptance',
164
+ 'non_functional',
165
+ 'contracts',
166
+ ];
167
+
168
+ let errors = [];
169
+ let warnings = [];
170
+ let fixes = [];
171
+
172
+ for (const field of requiredFields) {
173
+ if (!spec[field]) {
174
+ errors.push({
175
+ instancePath: `/${field}`,
176
+ message: `Missing required field: ${field}`,
177
+ suggestion: getFieldSuggestion(field, spec),
178
+ canAutoFix: canAutoFixField(field, spec),
179
+ });
180
+ }
181
+ }
182
+
183
+ // Validate specific field formats
184
+ if (spec.id && !/^[A-Z]+-\d+$/.test(spec.id)) {
185
+ errors.push({
186
+ instancePath: '/id',
187
+ message: 'Project ID should be in format: PREFIX-NUMBER (e.g., FEAT-1234)',
188
+ suggestion: 'Use format like: PROJ-001, FEAT-002, FIX-003',
189
+ canAutoFix: false,
190
+ });
191
+ }
192
+
193
+ // Validate risk tier
194
+ if (spec.risk_tier !== undefined && (spec.risk_tier < 1 || spec.risk_tier > 3)) {
195
+ errors.push({
196
+ instancePath: '/risk_tier',
197
+ message: 'Risk tier must be 1, 2, or 3',
198
+ suggestion:
199
+ 'Tier 1: Critical (auth, billing), Tier 2: Standard (features), Tier 3: Low risk (UI)',
200
+ canAutoFix: true,
201
+ });
202
+ fixes.push({ field: 'risk_tier', value: Math.max(1, Math.min(3, spec.risk_tier || 2)) });
203
+ }
204
+
205
+ // Validate scope.in is not empty
206
+ if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
207
+ errors.push({
208
+ instancePath: '/scope/in',
209
+ message: 'Scope IN must not be empty',
210
+ suggestion: 'Specify directories/files that are included in changes',
211
+ canAutoFix: false,
212
+ });
213
+ }
214
+
215
+ // Check for common issues
216
+ if (!spec.invariants || spec.invariants.length === 0) {
217
+ warnings.push({
218
+ instancePath: '/invariants',
219
+ message: 'No system invariants defined',
220
+ suggestion: 'Add 1-3 statements about what must always remain true',
221
+ });
222
+ }
223
+
224
+ if (!spec.acceptance || spec.acceptance.length === 0) {
225
+ warnings.push({
226
+ instancePath: '/acceptance',
227
+ message: 'No acceptance criteria defined',
228
+ suggestion: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
229
+ });
230
+ }
231
+
232
+ // Tier-specific validations
233
+ if (spec.risk_tier === 1 || spec.risk_tier === 2) {
234
+ if (!spec.contracts || spec.contracts.length === 0) {
235
+ errors.push({
236
+ instancePath: '/contracts',
237
+ message: 'Contracts required for Tier 1 and 2 changes',
238
+ suggestion: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
239
+ canAutoFix: false,
240
+ });
241
+ }
242
+ }
243
+
244
+ // Validate waiver_ids format if present
245
+ if (spec.waiver_ids) {
246
+ if (!Array.isArray(spec.waiver_ids)) {
247
+ errors.push({
248
+ instancePath: '/waiver_ids',
249
+ message: 'waiver_ids must be an array of waiver IDs',
250
+ suggestion: 'Use format: ["WV-0001", "WV-0002"]',
251
+ canAutoFix: false,
252
+ });
253
+ } else {
254
+ for (const waiverId of spec.waiver_ids) {
255
+ if (!/^WV-\d{4}$/.test(waiverId)) {
256
+ errors.push({
257
+ instancePath: '/waiver_ids',
258
+ message: `Invalid waiver ID format: ${waiverId}`,
259
+ suggestion: 'Use format: WV-XXXX (e.g., WV-0001)',
260
+ canAutoFix: false,
261
+ });
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ // Derive and check budget if requested
268
+ let budgetCheck = null;
269
+ if (checkBudget && projectRoot) {
270
+ try {
271
+ const derivedBudget = deriveBudget(spec, projectRoot);
272
+
273
+ // Mock current stats for now - in real implementation this would analyze git changes
274
+ const mockStats = {
275
+ files_changed: 50, // This would be calculated from actual changes
276
+ lines_changed: 5000,
277
+ risk_tier: spec.risk_tier,
278
+ };
279
+
280
+ budgetCheck = checkBudgetCompliance(derivedBudget, mockStats);
281
+
282
+ if (!budgetCheck.compliant) {
283
+ for (const violation of budgetCheck.violations) {
284
+ errors.push({
285
+ instancePath: '/budget',
286
+ message: violation.message,
287
+ suggestion: 'Create a waiver or reduce scope to fit within budget',
288
+ canAutoFix: false,
289
+ });
290
+ }
291
+ }
292
+ } catch (error) {
293
+ warnings.push({
294
+ instancePath: '/budget',
295
+ message: `Budget derivation failed: ${error.message}`,
296
+ suggestion: 'Check that .caws/policy.yaml exists and is valid',
297
+ });
298
+ }
299
+ }
300
+
301
+ // Apply auto-fixes if requested
302
+ if (autoFix && fixes.length > 0) {
303
+ console.log('🔧 Applying auto-fixes...');
304
+ for (const fix of fixes) {
305
+ const pathParts = fix.field.split('.');
306
+ let current = spec;
307
+ for (let i = 0; i < pathParts.length - 1; i++) {
308
+ if (!current[pathParts[i]]) current[pathParts[i]] = {};
309
+ current = current[pathParts[i]];
310
+ }
311
+ current[pathParts[pathParts.length - 1]] = fix.value;
312
+ console.log(` Fixed ${fix.field}: ${fix.value}`);
313
+ }
314
+ }
315
+
316
+ return {
317
+ valid: errors.length === 0,
318
+ errors,
319
+ warnings,
320
+ fixes: fixes.length > 0 ? fixes : undefined,
321
+ budget_check: budgetCheck,
322
+ };
323
+ } catch (error) {
324
+ return {
325
+ valid: false,
326
+ errors: [
327
+ {
328
+ instancePath: '',
329
+ message: `Validation error: ${error.message}`,
330
+ },
331
+ ],
332
+ };
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Get suggestion for a missing field
338
+ * @param {string} field - Field name
339
+ * @param {Object} _spec - Spec object (for context)
340
+ * @returns {string} Suggestion text
341
+ */
342
+ function getFieldSuggestion(field, _spec) {
343
+ const suggestions = {
344
+ id: 'Use format like: PROJ-001, FEAT-002, FIX-003',
345
+ title: 'Add a descriptive project title',
346
+ risk_tier: 'Choose: 1 (critical), 2 (standard), or 3 (low risk)',
347
+ mode: 'Choose: feature, refactor, fix, doc, or chore',
348
+ waiver_ids: 'Reference active waivers by ID (e.g., ["WV-0001"]) if budget exceptions needed',
349
+ blast_radius: 'List affected modules and data migration needs',
350
+ operational_rollback_slo: 'Choose: 1m, 5m, 15m, or 1h',
351
+ scope: "Define what's included (in) and excluded (out) from changes",
352
+ invariants: 'Add 1-3 statements about what must always remain true',
353
+ acceptance: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
354
+ non_functional: 'Define accessibility, performance, and security requirements',
355
+ contracts: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
356
+ };
357
+ return suggestions[field] || `Add the ${field} field`;
358
+ }
359
+
360
+ /**
361
+ * Check if a field can be auto-fixed
362
+ * @param {string} field - Field name
363
+ * @param {Object} _spec - Spec object (for context)
364
+ * @returns {boolean} Whether field can be auto-fixed
365
+ */
366
+ function canAutoFixField(field, _spec) {
367
+ const autoFixable = ['risk_tier'];
368
+ return autoFixable.includes(field);
369
+ }
370
+
371
+ module.exports = {
372
+ validateWorkingSpec,
373
+ validateWorkingSpecWithSuggestions,
374
+ getFieldSuggestion,
375
+ canAutoFixField,
376
+ };
@@ -0,0 +1,167 @@
1
+ export = WaiversManager;
2
+ /**
3
+ * Waiver Manager Class
4
+ * Handles waiver creation, validation, expiration, and audit logging
5
+ */
6
+ declare class WaiversManager {
7
+ constructor(options?: {});
8
+ projectRoot: any;
9
+ waiversDir: string;
10
+ waiversFile: string;
11
+ auditLogFile: string;
12
+ /**
13
+ * Waiver Schema Definition
14
+ */
15
+ getWaiverSchema(): {
16
+ type: string;
17
+ required: string[];
18
+ properties: {
19
+ id: {
20
+ type: string;
21
+ pattern: string;
22
+ description: string;
23
+ };
24
+ title: {
25
+ type: string;
26
+ minLength: number;
27
+ maxLength: number;
28
+ description: string;
29
+ };
30
+ reason: {
31
+ type: string;
32
+ enum: string[];
33
+ description: string;
34
+ };
35
+ description: {
36
+ type: string;
37
+ minLength: number;
38
+ maxLength: number;
39
+ description: string;
40
+ };
41
+ gates: {
42
+ type: string;
43
+ items: {
44
+ type: string;
45
+ enum: string[];
46
+ };
47
+ minItems: number;
48
+ description: string;
49
+ };
50
+ risk_assessment: {
51
+ type: string;
52
+ properties: {
53
+ impact_level: {
54
+ type: string;
55
+ enum: string[];
56
+ };
57
+ mitigation_plan: {
58
+ type: string;
59
+ minLength: number;
60
+ };
61
+ review_required: {
62
+ type: string;
63
+ };
64
+ };
65
+ required: string[];
66
+ };
67
+ expires_at: {
68
+ type: string;
69
+ format: string;
70
+ description: string;
71
+ };
72
+ approved_by: {
73
+ type: string;
74
+ description: string;
75
+ };
76
+ created_at: {
77
+ type: string;
78
+ format: string;
79
+ description: string;
80
+ };
81
+ metadata: {
82
+ type: string;
83
+ properties: {
84
+ related_pr: {
85
+ type: string;
86
+ };
87
+ related_issue: {
88
+ type: string;
89
+ };
90
+ environment: {
91
+ type: string;
92
+ enum: string[];
93
+ };
94
+ urgency: {
95
+ type: string;
96
+ enum: string[];
97
+ };
98
+ };
99
+ };
100
+ };
101
+ };
102
+ /**
103
+ * Create a new waiver
104
+ */
105
+ createWaiver(waiverData: any): Promise<{
106
+ id: string;
107
+ title: any;
108
+ reason: any;
109
+ description: any;
110
+ gates: any;
111
+ risk_assessment: any;
112
+ expires_at: any;
113
+ approved_by: any;
114
+ created_at: string;
115
+ metadata: any;
116
+ }>;
117
+ /**
118
+ * Check if waiver applies to specific gates
119
+ */
120
+ checkWaiverCoverage(gatesToCheck: any, context?: {}): Promise<{
121
+ coveredGates: any[];
122
+ waiverDetails: {
123
+ gate: any;
124
+ waiver_id: any;
125
+ reason: any;
126
+ expires_at: any;
127
+ approved_by: any;
128
+ }[];
129
+ allCovered: boolean;
130
+ }>;
131
+ /**
132
+ * Get all active waivers
133
+ */
134
+ getActiveWaivers(): Promise<any>;
135
+ /**
136
+ * Revoke a waiver
137
+ */
138
+ revokeWaiver(waiverId: any, reason?: string): Promise<any>;
139
+ /**
140
+ * Extend waiver expiration
141
+ */
142
+ extendWaiver(waiverId: any, newExpiryDate: any, approvedBy: any): Promise<any>;
143
+ /**
144
+ * Get waiver statistics and health metrics
145
+ */
146
+ getWaiverStats(): Promise<{
147
+ total_active: any;
148
+ by_reason: {};
149
+ by_risk_level: {};
150
+ expiring_soon: any[];
151
+ high_risk: any[];
152
+ total_gates_waived: number;
153
+ average_lifespan_days: number;
154
+ }>;
155
+ generateWaiverId(): Promise<string>;
156
+ validateWaiver(waiver: any): {
157
+ valid: boolean;
158
+ errors: string[];
159
+ };
160
+ checkWaiverConflicts(newWaiver: any): Promise<string[]>;
161
+ waiverAppliesToContext(waiver: any, context: any): boolean;
162
+ loadActiveWaivers(): Promise<unknown>;
163
+ saveActiveWaivers(waivers: any): Promise<void>;
164
+ auditLog(action: any, waiverId: any, details: any): Promise<void>;
165
+ flagForReview(waiver: any): Promise<void>;
166
+ }
167
+ //# sourceMappingURL=waivers-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waivers-manager.d.ts","sourceRoot":"","sources":["../src/waivers-manager.js"],"names":[],"mappings":";AAaA;;;GAGG;AACH;IACE,0BAUC;IATC,iBAAuD;IACvD,mBAAiE;IACjE,oBAAoE;IACpE,qBAAkE;IAQpE;;OAEG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAiGC;IAED;;OAEG;IACH;;;;;;;;;;;OA4DC;IAED;;OAEG;IACH;;;;;;;;;;OA+BC;IAED;;OAEG;IACH,iCAgBC;IAED;;OAEG;IACH,2DAeC;IAED;;OAEG;IACH,+EAuBC;IAED;;OAEG;IACH;;;;;;;;OA2DC;IAID,oCAUC;IAED;;;MAkDC;IAED,wDAkBC;IAED,2DAUC;IAED,sCAYC;IAED,+CAOC;IAED,kEAaC;IAED,0CA2CC;CACF"}