specweave 0.30.16 → 0.30.17

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 (53) hide show
  1. package/CLAUDE.md +43 -0
  2. package/dist/src/cli/workers/living-docs-worker.js +80 -44
  3. package/dist/src/cli/workers/living-docs-worker.js.map +1 -1
  4. package/dist/src/core/living-docs/living-docs-sync.d.ts +26 -14
  5. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  6. package/dist/src/core/living-docs/living-docs-sync.js +137 -71
  7. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  8. package/dist/src/core/llm/index.d.ts +1 -0
  9. package/dist/src/core/llm/index.d.ts.map +1 -1
  10. package/dist/src/core/llm/index.js +2 -0
  11. package/dist/src/core/llm/index.js.map +1 -1
  12. package/dist/src/core/llm/providers/anthropic-provider.d.ts.map +1 -1
  13. package/dist/src/core/llm/providers/anthropic-provider.js +15 -26
  14. package/dist/src/core/llm/providers/anthropic-provider.js.map +1 -1
  15. package/dist/src/core/llm/providers/azure-openai-provider.d.ts.map +1 -1
  16. package/dist/src/core/llm/providers/azure-openai-provider.js +13 -5
  17. package/dist/src/core/llm/providers/azure-openai-provider.js.map +1 -1
  18. package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
  19. package/dist/src/core/llm/providers/bedrock-provider.js +12 -8
  20. package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
  21. package/dist/src/core/llm/providers/claude-code-provider.d.ts.map +1 -1
  22. package/dist/src/core/llm/providers/claude-code-provider.js +15 -25
  23. package/dist/src/core/llm/providers/claude-code-provider.js.map +1 -1
  24. package/dist/src/core/llm/providers/ollama-provider.d.ts.map +1 -1
  25. package/dist/src/core/llm/providers/ollama-provider.js +12 -9
  26. package/dist/src/core/llm/providers/ollama-provider.js.map +1 -1
  27. package/dist/src/core/llm/providers/openai-provider.d.ts.map +1 -1
  28. package/dist/src/core/llm/providers/openai-provider.js +13 -6
  29. package/dist/src/core/llm/providers/openai-provider.js.map +1 -1
  30. package/dist/src/core/llm/providers/vertex-ai-provider.d.ts.map +1 -1
  31. package/dist/src/core/llm/providers/vertex-ai-provider.js +12 -8
  32. package/dist/src/core/llm/providers/vertex-ai-provider.js.map +1 -1
  33. package/dist/src/utils/llm-json-extractor.d.ts +105 -0
  34. package/dist/src/utils/llm-json-extractor.d.ts.map +1 -0
  35. package/dist/src/utils/llm-json-extractor.js +336 -0
  36. package/dist/src/utils/llm-json-extractor.js.map +1 -0
  37. package/dist/src/utils/structure-level-detector.d.ts +105 -0
  38. package/dist/src/utils/structure-level-detector.d.ts.map +1 -0
  39. package/dist/src/utils/structure-level-detector.js +388 -0
  40. package/dist/src/utils/structure-level-detector.js.map +1 -0
  41. package/package.json +1 -1
  42. package/plugins/specweave/commands/specweave-increment.md +57 -9
  43. package/plugins/specweave/commands/specweave-sync-specs.md +37 -6
  44. package/plugins/specweave/hooks/hooks.json +10 -0
  45. package/plugins/specweave/hooks/spec-project-validator.sh +111 -0
  46. package/plugins/specweave/skills/increment-planner/SKILL.md +109 -10
  47. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +2 -0
  48. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +1 -0
  49. package/plugins/specweave/skills/multi-project-spec-mapper/SKILL.md +24 -1
  50. package/plugins/specweave/skills/spec-generator/SKILL.md +18 -0
  51. package/plugins/specweave/skills/specweave-framework/SKILL.md +25 -0
  52. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +14 -0
  53. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +21 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * LLM JSON Extractor - Robust JSON parsing from LLM responses
3
+ *
4
+ * Handles common LLM output patterns:
5
+ * - "Based on the analysis, here is the JSON: {...}"
6
+ * - "```json\n{...}\n```"
7
+ * - "Here's what I found:\n{...}"
8
+ * - Pure JSON responses
9
+ *
10
+ * Features:
11
+ * - 4-level extraction strategy (pure -> code blocks -> regex -> array)
12
+ * - Input sanitization (BOM, trailing commas)
13
+ * - Schema-aware correction prompts
14
+ * - Required field validation
15
+ * - Retry-with-correction prompting
16
+ */
17
+ import { consoleLogger } from './logger.js';
18
+ /**
19
+ * Extract JSON from an LLM response that may contain prose
20
+ *
21
+ * Tries multiple extraction strategies in order:
22
+ * 1. Direct parse (pure JSON)
23
+ * 2. Remove markdown code blocks
24
+ * 3. Find JSON object via regex (handles prose around JSON)
25
+ * 4. Find JSON array via regex
26
+ */
27
+ export function extractJson(response, options = {}) {
28
+ const logger = options.logger ?? consoleLogger;
29
+ const result = {
30
+ success: false,
31
+ data: null,
32
+ rawResponse: response,
33
+ extractedJson: null,
34
+ error: null,
35
+ extractionMethod: 'failed'
36
+ };
37
+ if (!response || typeof response !== 'string') {
38
+ result.error = 'Empty or invalid response';
39
+ return result;
40
+ }
41
+ // Sanitize input: strip BOM and normalize
42
+ const sanitized = sanitizeInput(response);
43
+ const trimmed = sanitized.trim();
44
+ // Strategy 1: Direct parse (pure JSON) - with trailing comma fallback
45
+ const directParsed = tryParseJson(trimmed);
46
+ if (directParsed !== null && validateParsedJson(directParsed, options.requiredFields)) {
47
+ result.success = true;
48
+ result.data = directParsed;
49
+ result.extractedJson = trimmed;
50
+ result.extractionMethod = 'pure';
51
+ return result;
52
+ }
53
+ // Strategy 2: Remove markdown code blocks
54
+ const codeBlockPatterns = [
55
+ /^```(?:json)?\s*\n?([\s\S]*?)\n?```$/m, // Full code block
56
+ /```(?:json)?\s*\n?([\s\S]*?)\n?```/, // Code block anywhere
57
+ ];
58
+ for (const pattern of codeBlockPatterns) {
59
+ const match = trimmed.match(pattern);
60
+ if (match?.[1]) {
61
+ const codeBlockContent = match[1].trim();
62
+ const parsed = tryParseJson(codeBlockContent);
63
+ if (parsed !== null && validateParsedJson(parsed, options.requiredFields)) {
64
+ result.success = true;
65
+ result.data = parsed;
66
+ result.extractedJson = codeBlockContent;
67
+ result.extractionMethod = 'code-block';
68
+ return result;
69
+ }
70
+ }
71
+ }
72
+ // Strategy 3: Find JSON object via regex (handles "Based on... here is: {...}")
73
+ // Find the outermost balanced braces
74
+ const jsonObject = findBalancedJson(trimmed, '{', '}');
75
+ if (jsonObject) {
76
+ const parsed = tryParseJson(jsonObject);
77
+ if (parsed !== null && validateParsedJson(parsed, options.requiredFields)) {
78
+ result.success = true;
79
+ result.data = parsed;
80
+ result.extractedJson = jsonObject;
81
+ result.extractionMethod = 'regex';
82
+ return result;
83
+ }
84
+ }
85
+ // Strategy 4: Find JSON array
86
+ const jsonArray = findBalancedJson(trimmed, '[', ']');
87
+ if (jsonArray) {
88
+ const parsed = tryParseJson(jsonArray);
89
+ if (parsed !== null && validateParsedJson(parsed, options.requiredFields)) {
90
+ result.success = true;
91
+ result.data = parsed;
92
+ result.extractedJson = jsonArray;
93
+ result.extractionMethod = 'regex';
94
+ return result;
95
+ }
96
+ }
97
+ // All strategies failed
98
+ result.error = generateErrorMessage(trimmed);
99
+ logger.debug?.(`JSON extraction failed: ${result.error}`);
100
+ return result;
101
+ }
102
+ /**
103
+ * Find balanced JSON by matching opening/closing brackets
104
+ * Handles nested structures and strings with escaped characters
105
+ */
106
+ function findBalancedJson(text, openChar, closeChar) {
107
+ const startIdx = text.indexOf(openChar);
108
+ if (startIdx === -1)
109
+ return null;
110
+ let depth = 0;
111
+ let inString = false;
112
+ let escapeNext = false;
113
+ for (let i = startIdx; i < text.length; i++) {
114
+ const char = text[i];
115
+ if (escapeNext) {
116
+ escapeNext = false;
117
+ continue;
118
+ }
119
+ if (char === '\\' && inString) {
120
+ escapeNext = true;
121
+ continue;
122
+ }
123
+ if (char === '"') {
124
+ inString = !inString;
125
+ continue;
126
+ }
127
+ if (!inString) {
128
+ if (char === openChar) {
129
+ depth++;
130
+ }
131
+ else if (char === closeChar) {
132
+ depth--;
133
+ if (depth === 0) {
134
+ return text.slice(startIdx, i + 1);
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ /**
142
+ * Validate parsed JSON has required fields
143
+ */
144
+ function validateParsedJson(parsed, requiredFields) {
145
+ if (!parsed || typeof parsed !== 'object') {
146
+ return false;
147
+ }
148
+ if (!requiredFields || requiredFields.length === 0) {
149
+ return true;
150
+ }
151
+ const obj = parsed;
152
+ return requiredFields.every(field => field in obj);
153
+ }
154
+ /**
155
+ * Sanitize LLM response input before JSON extraction
156
+ *
157
+ * Handles common issues:
158
+ * - UTF-8 BOM character
159
+ * - Trailing commas in arrays/objects (common LLM mistake)
160
+ */
161
+ function sanitizeInput(input) {
162
+ // Strip UTF-8 BOM if present
163
+ let sanitized = input.replace(/^\uFEFF/, '');
164
+ return sanitized;
165
+ }
166
+ /**
167
+ * Clean trailing commas from JSON string
168
+ * Called after extraction but before parsing
169
+ */
170
+ function cleanTrailingCommas(json) {
171
+ // Remove trailing commas before ] or }
172
+ // Be careful not to modify strings - only do this on extracted JSON
173
+ return json.replace(/,\s*([}\]])/g, '$1');
174
+ }
175
+ /**
176
+ * Try to parse JSON with trailing comma cleanup as fallback
177
+ */
178
+ function tryParseJson(json) {
179
+ try {
180
+ return JSON.parse(json);
181
+ }
182
+ catch {
183
+ // Try with trailing comma cleanup
184
+ try {
185
+ const cleaned = cleanTrailingCommas(json);
186
+ return JSON.parse(cleaned);
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Generate helpful error message for failed extraction
195
+ */
196
+ function generateErrorMessage(response) {
197
+ const preview = response.slice(0, 100).replace(/\n/g, ' ');
198
+ if (response.toLowerCase().startsWith('based on')) {
199
+ return `Model returned prose instead of JSON. Response starts with: "${preview}..."`;
200
+ }
201
+ if (response.toLowerCase().includes('i apologize') || response.toLowerCase().includes('sorry')) {
202
+ return `Model returned an apology/error message instead of JSON. Response starts with: "${preview}..."`;
203
+ }
204
+ if (response.includes('{')) {
205
+ return `Found JSON-like content but parsing failed. Response starts with: "${preview}..."`;
206
+ }
207
+ return `No valid JSON found in response. Response starts with: "${preview}..."`;
208
+ }
209
+ /**
210
+ * Generate a correction prompt for retry after failed JSON extraction
211
+ *
212
+ * @param originalPrompt - The original prompt that was sent
213
+ * @param failedResponse - The failed response from the LLM
214
+ * @param schema - Optional schema to show expected structure (recommended)
215
+ */
216
+ export function generateCorrectionPrompt(originalPrompt, failedResponse, schema) {
217
+ // Build schema example - either from provided schema or use default
218
+ let schemaExample;
219
+ if (schema && Object.keys(schema).length > 0) {
220
+ // Use provided schema
221
+ schemaExample = JSON.stringify(schema, null, 2);
222
+ }
223
+ else {
224
+ // Fallback to generic example (backward compatible)
225
+ schemaExample = `{
226
+ "purpose": "...",
227
+ "keyConcepts": [...],
228
+ "dependencies": [...],
229
+ "complexity": "low|medium|high",
230
+ "suggestedDocs": [...],
231
+ "architecturalNotes": "..."
232
+ }`;
233
+ }
234
+ return `Your previous response was not valid JSON. I need ONLY a JSON object, with no explanations or prose.
235
+
236
+ INCORRECT (what you sent):
237
+ ${failedResponse.slice(0, 200)}${failedResponse.length > 200 ? '...' : ''}
238
+
239
+ CORRECT format - respond with ONLY this JSON structure, nothing else before or after:
240
+ ${schemaExample}
241
+
242
+ Now provide ONLY the JSON for: ${originalPrompt.slice(0, 500)}`;
243
+ }
244
+ /**
245
+ * Extract required field names from a JSON schema object
246
+ *
247
+ * Handles common schema patterns:
248
+ * - Simple objects: extracts top-level keys
249
+ * - Objects with "type" descriptors: extracts keys
250
+ *
251
+ * @param schema - The schema object passed to analyzeStructured (any object type)
252
+ * @returns Array of required field names
253
+ */
254
+ export function extractRequiredFieldsFromSchema(schema) {
255
+ if (!schema || typeof schema !== 'object') {
256
+ return [];
257
+ }
258
+ // For simple schemas like { purpose: "...", complexity: "..." }
259
+ // Return all top-level keys
260
+ return Object.keys(schema);
261
+ }
262
+ /**
263
+ * Build a prompt that strongly encourages JSON-only output
264
+ *
265
+ * Uses multiple techniques:
266
+ * 1. Leading instruction (most important position)
267
+ * 2. Schema definition
268
+ * 3. Trailing reminder
269
+ */
270
+ export function buildJsonPrompt(context, schema, instruction) {
271
+ const schemaExample = JSON.stringify(Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, v])), null, 2);
272
+ return `IMPORTANT: Respond with ONLY valid JSON. No explanations, no markdown, no text before or after.
273
+
274
+ ${instruction}
275
+
276
+ ${context}
277
+
278
+ Required JSON schema:
279
+ ${schemaExample}
280
+
281
+ Remember: Output ONLY the JSON object. Any text outside the JSON will cause errors.`;
282
+ }
283
+ /**
284
+ * Retry wrapper that attempts extraction with correction prompts
285
+ *
286
+ * Uses schema-aware correction prompts for better retry success.
287
+ */
288
+ export async function extractJsonWithRetry(sendPrompt, originalPrompt, options = {}) {
289
+ const maxRetries = options.maxRetries ?? 2;
290
+ const logger = options.logger ?? consoleLogger;
291
+ let lastResult;
292
+ let currentPrompt = originalPrompt;
293
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
294
+ if (attempt > 0) {
295
+ logger.debug?.(`JSON extraction retry ${attempt}/${maxRetries}`);
296
+ }
297
+ const response = await sendPrompt(currentPrompt);
298
+ lastResult = extractJson(response, options);
299
+ if (lastResult.success) {
300
+ if (attempt > 0) {
301
+ logger.debug?.(`JSON extraction succeeded on retry ${attempt}`);
302
+ }
303
+ return lastResult;
304
+ }
305
+ // Generate schema-aware correction prompt for next attempt
306
+ if (attempt < maxRetries) {
307
+ currentPrompt = generateCorrectionPrompt(originalPrompt, response, options.schema);
308
+ }
309
+ }
310
+ return lastResult;
311
+ }
312
+ /**
313
+ * Create a structured output analyzer with built-in retry
314
+ *
315
+ * This is the recommended way to get structured JSON from LLMs.
316
+ * Combines prompt building, extraction, validation, and retry.
317
+ *
318
+ * @param analyze - Function that sends prompt to LLM and returns response
319
+ * @param schema - Expected schema (used for prompt, validation, and correction)
320
+ * @param options - Configuration options
321
+ */
322
+ export async function analyzeStructuredWithRetry(analyze, prompt, schema, options = {}) {
323
+ const requiredFields = extractRequiredFieldsFromSchema(schema);
324
+ const result = await extractJsonWithRetry(analyze, prompt, {
325
+ requiredFields,
326
+ schema,
327
+ maxRetries: options.maxRetries ?? 2,
328
+ logger: options.logger
329
+ });
330
+ return {
331
+ success: result.success,
332
+ data: result.data,
333
+ error: result.error
334
+ };
335
+ }
336
+ //# sourceMappingURL=llm-json-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-json-extractor.js","sourceRoot":"","sources":["../../../src/utils/llm-json-extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Structure Level Detector
3
+ *
4
+ * Detects whether the project uses 1-level or 2-level folder structure
5
+ * for living docs specs:
6
+ *
7
+ * - 1-Level: internal/specs/{project}/FS-XXX/
8
+ * - 2-Level: internal/specs/{project}/{board}/FS-XXX/
9
+ *
10
+ * 2-level is used when:
11
+ * - ADO area path mapping is configured (project → area paths)
12
+ * - JIRA board mapping is configured with multiple boards per project
13
+ * - Umbrella with team-based organization
14
+ *
15
+ * @module utils/structure-level-detector
16
+ */
17
+ /**
18
+ * Structure level configuration
19
+ */
20
+ export interface StructureLevelConfig {
21
+ /** Whether this is 1-level or 2-level structure */
22
+ level: 1 | 2;
23
+ /** Available projects (for 1-level) or top-level containers (for 2-level) */
24
+ projects: ProjectInfo[];
25
+ /** Available boards (only for 2-level) - organized by project */
26
+ boardsByProject?: Record<string, BoardInfo[]>;
27
+ /** Detection reason for debugging */
28
+ detectionReason: string;
29
+ /** Source of configuration */
30
+ source: 'ado-area-path' | 'jira-board' | 'umbrella-teams' | 'multi-project' | 'single-project' | 'existing-folders';
31
+ }
32
+ /**
33
+ * Project information
34
+ */
35
+ export interface ProjectInfo {
36
+ id: string;
37
+ name: string;
38
+ /** For 2-level: this is the ADO project or JIRA project key */
39
+ externalId?: string;
40
+ }
41
+ /**
42
+ * Board information (for 2-level structures)
43
+ */
44
+ export interface BoardInfo {
45
+ id: string;
46
+ name: string;
47
+ /** Parent project ID */
48
+ projectId: string;
49
+ /** Keywords for matching */
50
+ keywords?: string[];
51
+ /** ADO area path */
52
+ areaPath?: string;
53
+ /** JIRA board ID */
54
+ jiraBoardId?: string;
55
+ }
56
+ /**
57
+ * Validation result for spec.md project/board context
58
+ */
59
+ export interface ProjectContextValidation {
60
+ isValid: boolean;
61
+ errors: string[];
62
+ warnings: string[];
63
+ /** Detected structure level */
64
+ structureLevel: 1 | 2;
65
+ /** Parsed project from spec */
66
+ project?: string;
67
+ /** Parsed board from spec (for 2-level) */
68
+ board?: string;
69
+ /** Expected path where this spec will sync to */
70
+ expectedPath?: string;
71
+ }
72
+ /**
73
+ * Detect structure level from configuration
74
+ *
75
+ * @param projectRoot - Path to project root
76
+ * @returns Structure level configuration
77
+ */
78
+ export declare function detectStructureLevel(projectRoot?: string): StructureLevelConfig;
79
+ /**
80
+ * Validate spec.md has required project/board context
81
+ *
82
+ * @param specContent - Content of spec.md file
83
+ * @param projectRoot - Path to project root
84
+ * @returns Validation result
85
+ */
86
+ export declare function validateProjectContext(specContent: string, projectRoot?: string): ProjectContextValidation;
87
+ /**
88
+ * Get required fields for spec.md based on structure level
89
+ *
90
+ * @param projectRoot - Path to project root
91
+ * @returns Object describing required fields
92
+ */
93
+ export declare function getRequiredSpecFields(projectRoot?: string): {
94
+ level: 1 | 2;
95
+ requiredFields: string[];
96
+ optionalFields: string[];
97
+ projects: ProjectInfo[];
98
+ boardsByProject?: Record<string, BoardInfo[]>;
99
+ };
100
+ /**
101
+ * Normalize ID to kebab-case
102
+ */
103
+ declare function normalizeId(name: string): string;
104
+ export { normalizeId };
105
+ //# sourceMappingURL=structure-level-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure-level-detector.d.ts","sourceRoot":"","sources":["../../../src/utils/structure-level-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IAEb,6EAA6E;IAC7E,QAAQ,EAAE,WAAW,EAAE,CAAC;IAExB,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAE9C,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC;IAExB,8BAA8B;IAC9B,MAAM,EAAE,eAAe,GAAG,YAAY,GAAG,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;CACrH;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+BAA+B;IAC/B,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC;IACtB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA+CD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,GAAE,MAAsB,GAAG,oBAAoB,CAoQ9F;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAsB,GAClC,wBAAwB,CAgH1B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,GAAE,MAAsB,GAAG;IAC1E,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;CAC/C,CAmBA;AAED;;GAEG;AACH,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOzC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}