@paths.design/caws-cli 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -150
- package/dist/budget-derivation.d.ts +35 -0
- package/dist/budget-derivation.d.ts.map +1 -0
- package/dist/budget-derivation.js +204 -0
- package/dist/cicd-optimizer.d.ts +142 -0
- package/dist/cicd-optimizer.d.ts.map +1 -0
- package/dist/cicd-optimizer.js +504 -0
- package/dist/commands/burnup.d.ts +6 -0
- package/dist/commands/burnup.d.ts.map +1 -0
- package/dist/commands/burnup.js +90 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +514 -0
- package/dist/commands/provenance.d.ts +22 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +594 -0
- package/dist/commands/tool.d.ts +13 -0
- package/dist/commands/tool.d.ts.map +1 -0
- package/dist/commands/tool.js +138 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +80 -0
- package/dist/config/index.d.ts +29 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +132 -0
- package/dist/error-handler.d.ts +50 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +253 -0
- package/dist/generators/working-spec.d.ts +13 -0
- package/dist/generators/working-spec.d.ts.map +1 -0
- package/dist/generators/working-spec.js +204 -0
- package/dist/index-new.d.ts +5 -0
- package/dist/index-new.d.ts.map +1 -0
- package/dist/index-new.js +317 -0
- package/dist/index.d.ts +3 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -1659
- package/dist/index.js.backup +4711 -0
- package/dist/scaffold/cursor-hooks.d.ts +7 -0
- package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
- package/dist/scaffold/cursor-hooks.js +152 -0
- package/dist/scaffold/index.d.ts +20 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +486 -0
- package/dist/test-analysis.d.ts +182 -0
- package/dist/test-analysis.d.ts.map +1 -0
- package/dist/test-analysis.js +580 -0
- package/dist/tool-interface.d.ts +236 -0
- package/dist/tool-interface.d.ts.map +1 -0
- package/dist/tool-interface.js +314 -0
- package/dist/tool-loader.d.ts +77 -0
- package/dist/tool-loader.d.ts.map +1 -0
- package/dist/tool-loader.js +298 -0
- package/dist/tool-validator.d.ts +72 -0
- package/dist/tool-validator.d.ts.map +1 -0
- package/dist/tool-validator.js +387 -0
- package/dist/utils/detection.d.ts +7 -0
- package/dist/utils/detection.d.ts.map +1 -0
- package/dist/utils/detection.js +174 -0
- package/dist/utils/finalization.d.ts +17 -0
- package/dist/utils/finalization.d.ts.map +1 -0
- package/dist/utils/finalization.js +229 -0
- package/dist/utils/project-analysis.d.ts +14 -0
- package/dist/utils/project-analysis.d.ts.map +1 -0
- package/dist/utils/project-analysis.js +105 -0
- package/dist/validation/spec-validation.d.ts +29 -0
- package/dist/validation/spec-validation.d.ts.map +1 -0
- package/dist/validation/spec-validation.js +376 -0
- package/dist/waivers-manager.d.ts +167 -0
- package/dist/waivers-manager.d.ts.map +1 -0
- package/dist/waivers-manager.js +549 -0
- package/package.json +10 -12
- package/templates/.cursor/README.md +311 -0
- package/templates/.cursor/hooks/audit.sh +55 -0
- package/templates/.cursor/hooks/block-dangerous.sh +77 -0
- package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
- package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/templates/.cursor/hooks/format.sh +38 -0
- package/templates/.cursor/hooks/naming-check.sh +64 -0
- package/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/templates/.cursor/hooks/validate-spec.sh +38 -0
- package/templates/.cursor/hooks.json +59 -0
- package/templates/.github/copilot/instructions.md +311 -0
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/templates/.vscode/launch.json +56 -0
- package/templates/.vscode/settings.json +93 -0
- package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/templates/apps/tools/caws/README.md +1 -1
- package/templates/apps/tools/caws/prompt-lint.js.backup +274 -0
- package/templates/apps/tools/caws/provenance.js.backup +73 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
- package/templates/codemod/test.js +93 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview CAWS Tool Loader - Dynamic tool discovery and loading system
|
|
5
|
+
* Provides secure, sandboxed loading of tools from apps/tools/caws/ directory
|
|
6
|
+
* @author @darianrosebrook
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { EventEmitter } = require('events');
|
|
12
|
+
const { setTimeout, clearTimeout } = require('timers');
|
|
13
|
+
const { safeAsync } = require('./error-handler');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tool Loader - Discovers, validates, and loads CAWS tools dynamically
|
|
17
|
+
* @extends EventEmitter
|
|
18
|
+
*/
|
|
19
|
+
class ToolLoader extends EventEmitter {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
this.options = {
|
|
23
|
+
toolsDir: options.toolsDir || path.join(process.cwd(), 'apps/tools/caws'),
|
|
24
|
+
cacheEnabled: options.cacheEnabled !== false,
|
|
25
|
+
timeout: options.timeout || 10000,
|
|
26
|
+
maxTools: options.maxTools || 50,
|
|
27
|
+
...options,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
this.loadedTools = new Map();
|
|
31
|
+
this.discoveredTools = new Set();
|
|
32
|
+
this.loadingState = 'idle'; // idle, discovering, loading, ready, error
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Discover available tools in the tools directory
|
|
37
|
+
* @returns {Promise<Array<string>>} Array of tool file paths
|
|
38
|
+
*/
|
|
39
|
+
async discoverTools() {
|
|
40
|
+
return safeAsync(async () => {
|
|
41
|
+
this.loadingState = 'discovering';
|
|
42
|
+
this.emit('discovery:start');
|
|
43
|
+
|
|
44
|
+
// Check if tools directory exists
|
|
45
|
+
if (!fs.existsSync(this.options.toolsDir)) {
|
|
46
|
+
this.emit('discovery:complete', { tools: [], reason: 'directory_not_found' });
|
|
47
|
+
this.loadingState = 'ready';
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Read directory contents
|
|
52
|
+
const files = await fs.promises.readdir(this.options.toolsDir);
|
|
53
|
+
|
|
54
|
+
// Filter for valid tool files
|
|
55
|
+
const toolFiles = files
|
|
56
|
+
.filter((file) => {
|
|
57
|
+
// Must be .js file
|
|
58
|
+
if (!file.endsWith('.js')) return false;
|
|
59
|
+
|
|
60
|
+
// Must not be hidden or backup file
|
|
61
|
+
if (file.startsWith('.') || file.includes('.backup')) return false;
|
|
62
|
+
|
|
63
|
+
// Must not be test file (unless explicitly allowed)
|
|
64
|
+
if (file.includes('.test.') && !this.options.includeTests) return false;
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
})
|
|
68
|
+
.map((file) => path.join(this.options.toolsDir, file))
|
|
69
|
+
.filter((filePath) => {
|
|
70
|
+
// Validate file exists and is readable
|
|
71
|
+
try {
|
|
72
|
+
const stats = fs.statSync(filePath);
|
|
73
|
+
return stats.isFile() && stats.size > 0 && stats.size < 1024 * 1024; // < 1MB
|
|
74
|
+
} catch (error) {
|
|
75
|
+
this.emit('discovery:warning', { file: filePath, error: error.message });
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.slice(0, this.options.maxTools); // Limit number of tools
|
|
80
|
+
|
|
81
|
+
this.discoveredTools = new Set(toolFiles);
|
|
82
|
+
this.emit('discovery:complete', { tools: toolFiles, count: toolFiles.length });
|
|
83
|
+
this.loadingState = 'idle';
|
|
84
|
+
|
|
85
|
+
return toolFiles;
|
|
86
|
+
}, 'Tool discovery failed');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Load a specific tool module
|
|
91
|
+
* @param {string} toolPath - Path to tool file
|
|
92
|
+
* @returns {Promise<Object>} Loaded tool module
|
|
93
|
+
*/
|
|
94
|
+
async loadTool(toolPath) {
|
|
95
|
+
return safeAsync(async () => {
|
|
96
|
+
const toolId = path.basename(toolPath, '.js');
|
|
97
|
+
|
|
98
|
+
// Check cache first
|
|
99
|
+
if (this.loadedTools.has(toolId) && this.options.cacheEnabled) {
|
|
100
|
+
return this.loadedTools.get(toolId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.emit('tool:loading', { id: toolId, path: toolPath });
|
|
104
|
+
|
|
105
|
+
// Validate tool file before loading
|
|
106
|
+
await this.validateToolFile(toolPath);
|
|
107
|
+
|
|
108
|
+
// Load the module with timeout
|
|
109
|
+
const toolModule = await this.loadModuleWithTimeout(toolPath);
|
|
110
|
+
|
|
111
|
+
// Validate tool interface
|
|
112
|
+
await this.validateToolInterface(toolModule, toolId);
|
|
113
|
+
|
|
114
|
+
// Cache the loaded tool
|
|
115
|
+
const tool = {
|
|
116
|
+
module: toolModule,
|
|
117
|
+
path: toolPath,
|
|
118
|
+
loadedAt: new Date(),
|
|
119
|
+
metadata: toolModule.getMetadata ? toolModule.getMetadata() : {},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
this.loadedTools.set(toolId, tool);
|
|
123
|
+
this.emit('tool:loaded', { id: toolId, metadata: tool.metadata });
|
|
124
|
+
|
|
125
|
+
return tool;
|
|
126
|
+
}, `Tool loading failed: ${toolId}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Load all discovered tools
|
|
131
|
+
* @returns {Promise<Map<string, Object>>} Map of loaded tools
|
|
132
|
+
*/
|
|
133
|
+
async loadAllTools() {
|
|
134
|
+
this.loadingState = 'loading';
|
|
135
|
+
this.emit('loading:start');
|
|
136
|
+
|
|
137
|
+
const toolPaths = await this.discoverTools();
|
|
138
|
+
const results = new Map();
|
|
139
|
+
|
|
140
|
+
for (const toolPath of toolPaths) {
|
|
141
|
+
try {
|
|
142
|
+
const tool = await this.loadTool(toolPath);
|
|
143
|
+
results.set(path.basename(toolPath, '.js'), tool);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
// Log error but continue loading other tools
|
|
146
|
+
this.emit('loading:warning', { path: toolPath, error: error.message });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.loadingState = 'ready';
|
|
151
|
+
this.emit('loading:complete', { loaded: results.size, total: toolPaths.length });
|
|
152
|
+
|
|
153
|
+
return results;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get a loaded tool by ID
|
|
158
|
+
* @param {string} toolId - Tool identifier
|
|
159
|
+
* @returns {Object|null} Tool object or null if not found
|
|
160
|
+
*/
|
|
161
|
+
getTool(toolId) {
|
|
162
|
+
return this.loadedTools.get(toolId) || null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get all loaded tools
|
|
167
|
+
* @returns {Map<string, Object>} Map of loaded tools
|
|
168
|
+
*/
|
|
169
|
+
getAllTools() {
|
|
170
|
+
return new Map(this.loadedTools);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Unload a tool (remove from cache)
|
|
175
|
+
* @param {string} toolId - Tool identifier
|
|
176
|
+
* @returns {boolean} True if tool was unloaded
|
|
177
|
+
*/
|
|
178
|
+
unloadTool(toolId) {
|
|
179
|
+
const unloaded = this.loadedTools.delete(toolId);
|
|
180
|
+
if (unloaded) {
|
|
181
|
+
this.emit('tool:unloaded', { id: toolId });
|
|
182
|
+
}
|
|
183
|
+
return unloaded;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate tool file before loading
|
|
188
|
+
* @private
|
|
189
|
+
* @param {string} toolPath - Path to tool file
|
|
190
|
+
*/
|
|
191
|
+
async validateToolFile(toolPath) {
|
|
192
|
+
// Basic file validation
|
|
193
|
+
const stats = await fs.promises.stat(toolPath);
|
|
194
|
+
if (stats.size === 0) {
|
|
195
|
+
throw new Error('Tool file is empty');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (stats.size > 1024 * 1024) {
|
|
199
|
+
// 1MB limit
|
|
200
|
+
throw new Error('Tool file too large (>1MB)');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Read first few lines to check for shebang and basic structure
|
|
204
|
+
const fd = await fs.promises.open(toolPath, 'r');
|
|
205
|
+
try {
|
|
206
|
+
const buffer = Buffer.alloc(512);
|
|
207
|
+
const { bytesRead } = await fd.read(buffer, 0, 512, 0);
|
|
208
|
+
const content = buffer.toString('utf8', 0, bytesRead);
|
|
209
|
+
|
|
210
|
+
// Check for shebang
|
|
211
|
+
if (!content.startsWith('#!/usr/bin/env node') && !content.startsWith('#!')) {
|
|
212
|
+
throw new Error('Tool file missing shebang');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Basic syntax check - look for module.exports or ES modules
|
|
216
|
+
const hasExports = content.includes('module.exports') || content.includes('export ');
|
|
217
|
+
if (!hasExports) {
|
|
218
|
+
throw new Error('Tool file does not export anything');
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
await fd.close();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Load module with timeout protection
|
|
227
|
+
* @private
|
|
228
|
+
* @param {string} toolPath - Path to tool file
|
|
229
|
+
*/
|
|
230
|
+
async loadModuleWithTimeout(toolPath) {
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const timeout = setTimeout(() => {
|
|
233
|
+
reject(new Error(`Tool loading timeout after ${this.options.timeout}ms`));
|
|
234
|
+
}, this.options.timeout);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// Clear require cache to ensure fresh load
|
|
238
|
+
delete require.cache[require.resolve(toolPath)];
|
|
239
|
+
|
|
240
|
+
const module = require(toolPath);
|
|
241
|
+
clearTimeout(timeout);
|
|
242
|
+
resolve(module);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
clearTimeout(timeout);
|
|
245
|
+
reject(error);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Validate tool interface compliance
|
|
252
|
+
* @private
|
|
253
|
+
* @param {Object} toolModule - Loaded tool module
|
|
254
|
+
* @param {string} toolId - Tool identifier
|
|
255
|
+
*/
|
|
256
|
+
async validateToolInterface(toolModule, toolId) {
|
|
257
|
+
const requiredMethods = ['execute', 'getMetadata'];
|
|
258
|
+
|
|
259
|
+
for (const method of requiredMethods) {
|
|
260
|
+
if (typeof toolModule[method] !== 'function') {
|
|
261
|
+
throw new Error(`Tool ${toolId} missing required method: ${method}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Validate metadata structure
|
|
266
|
+
if (toolModule.getMetadata) {
|
|
267
|
+
const metadata = toolModule.getMetadata();
|
|
268
|
+
const requiredFields = ['id', 'name', 'version'];
|
|
269
|
+
|
|
270
|
+
for (const field of requiredFields) {
|
|
271
|
+
if (!metadata[field]) {
|
|
272
|
+
throw new Error(`Tool ${toolId} metadata missing required field: ${field}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Validate metadata types
|
|
277
|
+
if (typeof metadata.id !== 'string' || typeof metadata.name !== 'string') {
|
|
278
|
+
throw new Error(`Tool ${toolId} metadata has invalid types`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get loader statistics
|
|
285
|
+
* @returns {Object} Statistics object
|
|
286
|
+
*/
|
|
287
|
+
getStats() {
|
|
288
|
+
return {
|
|
289
|
+
discovered: this.discoveredTools.size,
|
|
290
|
+
loaded: this.loadedTools.size,
|
|
291
|
+
state: this.loadingState,
|
|
292
|
+
cacheEnabled: this.options.cacheEnabled,
|
|
293
|
+
toolsDir: this.options.toolsDir,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
module.exports = ToolLoader;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export = ToolValidator;
|
|
3
|
+
/**
|
|
4
|
+
* Tool Validator - Security validation and allowlist enforcement
|
|
5
|
+
*/
|
|
6
|
+
declare class ToolValidator {
|
|
7
|
+
constructor(options?: {});
|
|
8
|
+
options: {
|
|
9
|
+
allowlistPath: any;
|
|
10
|
+
strictMode: boolean;
|
|
11
|
+
maxFileSize: any;
|
|
12
|
+
};
|
|
13
|
+
allowlist: any;
|
|
14
|
+
validationCache: Map<any, any>;
|
|
15
|
+
/**
|
|
16
|
+
* Load and parse the tools allowlist
|
|
17
|
+
* @returns {Promise<Array<string>>} Array of allowed commands/patterns
|
|
18
|
+
*/
|
|
19
|
+
loadAllowlist(): Promise<Array<string>>;
|
|
20
|
+
/**
|
|
21
|
+
* Validate a tool against security requirements
|
|
22
|
+
* @param {Object} tool - Tool object with module and metadata
|
|
23
|
+
* @returns {Promise<Object>} Validation result
|
|
24
|
+
*/
|
|
25
|
+
validateTool(tool: any): Promise<any>;
|
|
26
|
+
/**
|
|
27
|
+
* Check file-level security
|
|
28
|
+
* @private
|
|
29
|
+
* @param {Object} tool - Tool object
|
|
30
|
+
*/
|
|
31
|
+
private checkFileSecurity;
|
|
32
|
+
/**
|
|
33
|
+
* Check code-level security
|
|
34
|
+
* @private
|
|
35
|
+
* @param {Object} tool - Tool object
|
|
36
|
+
*/
|
|
37
|
+
private checkCodeSecurity;
|
|
38
|
+
/**
|
|
39
|
+
* Check interface compliance
|
|
40
|
+
* @private
|
|
41
|
+
* @param {Object} tool - Tool object
|
|
42
|
+
*/
|
|
43
|
+
private checkInterfaceCompliance;
|
|
44
|
+
/**
|
|
45
|
+
* Check metadata validity
|
|
46
|
+
* @private
|
|
47
|
+
* @param {Object} tool - Tool object
|
|
48
|
+
*/
|
|
49
|
+
private checkMetadataValidity;
|
|
50
|
+
/**
|
|
51
|
+
* Check dependency safety
|
|
52
|
+
* @private
|
|
53
|
+
* @param {Object} tool - Tool object
|
|
54
|
+
*/
|
|
55
|
+
private checkDependencySafety;
|
|
56
|
+
/**
|
|
57
|
+
* Validate a command against the allowlist
|
|
58
|
+
* @param {string} command - Command to validate
|
|
59
|
+
* @returns {boolean} True if command is allowed
|
|
60
|
+
*/
|
|
61
|
+
validateCommand(command: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Clear validation cache
|
|
64
|
+
*/
|
|
65
|
+
clearCache(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Get validator statistics
|
|
68
|
+
* @returns {Object} Statistics object
|
|
69
|
+
*/
|
|
70
|
+
getStats(): any;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=tool-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-validator.d.ts","sourceRoot":"","sources":["../src/tool-validator.js"],"names":[],"mappings":";;AAYA;;GAEG;AACH;IACE,0BAWC;IAVC;;;;MAMC;IAED,eAAqB;IACrB,+BAAgC;IAGlC;;;OAGG;IACH,iBAFa,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAqBlC;IAED;;;;OAIG;IACH,yBAFa,OAAO,KAAQ,CAsF3B;IAED;;;;OAIG;IACH,0BAgCC;IAED;;;;OAIG;IACH,0BA8CC;IAED;;;;OAIG;IACH,iCAkBC;IAED;;;;OAIG;IACH,8BAqCC;IAED;;;;OAIG;IACH,8BA4BC;IAED;;;;OAIG;IACH,yBAHW,MAAM,GACJ,OAAO,CAmBnB;IAED;;OAEG;IACH,mBAEC;IAED;;;OAGG;IACH,gBAOC;CACF"}
|