wogiflow 1.0.11 → 1.0.13

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 (46) hide show
  1. package/.workflow/specs/architecture.md.template +24 -0
  2. package/.workflow/specs/stack.md.template +33 -0
  3. package/.workflow/specs/testing.md.template +36 -0
  4. package/README.md +90 -1
  5. package/lib/unified-wizard.js +569 -30
  6. package/package.json +1 -1
  7. package/scripts/MEMORY-ARCHITECTURE.md +150 -0
  8. package/scripts/flow +20 -19
  9. package/scripts/flow-auto-context.js +97 -3
  10. package/scripts/flow-conflict-resolver.js +735 -0
  11. package/scripts/flow-context-gatherer.js +520 -0
  12. package/scripts/flow-context-monitor.js +148 -19
  13. package/scripts/flow-damage-control.js +5 -1
  14. package/scripts/flow-export-profile +168 -1
  15. package/scripts/flow-import-profile +257 -6
  16. package/scripts/flow-instruction-richness.js +182 -18
  17. package/scripts/flow-knowledge-router.js +2 -0
  18. package/scripts/flow-knowledge-sync.js +2 -0
  19. package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
  20. package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
  21. package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
  22. package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
  23. package/scripts/flow-memory-db.js +386 -1
  24. package/scripts/flow-memory-sync.js +2 -0
  25. package/scripts/flow-model-adapter.js +53 -29
  26. package/scripts/flow-model-router.js +246 -1
  27. package/scripts/flow-morning.js +94 -0
  28. package/scripts/flow-onboard +223 -10
  29. package/scripts/flow-orchestrate-validation.js +539 -0
  30. package/scripts/flow-orchestrate.js +16 -507
  31. package/scripts/flow-pattern-extractor.js +1265 -0
  32. package/scripts/flow-prompt-composer.js +222 -2
  33. package/scripts/flow-quality-guard.js +594 -0
  34. package/scripts/flow-section-index.js +713 -0
  35. package/scripts/flow-section-resolver.js +484 -0
  36. package/scripts/flow-session-end.js +188 -2
  37. package/scripts/flow-skill-create.js +19 -3
  38. package/scripts/flow-skill-matcher.js +122 -7
  39. package/scripts/flow-statusline-setup.js +218 -0
  40. package/scripts/flow-step-review.js +19 -0
  41. package/scripts/flow-tech-debt.js +734 -0
  42. package/scripts/flow-utils.js +2 -0
  43. package/scripts/hooks/core/long-input-gate.js +293 -0
  44. package/scripts/flow-parallel-detector.js +0 -399
  45. package/scripts/flow-parallel-dispatch.js +0 -987
  46. /package/scripts/{flow-transcript-language.js → flow-long-input-language.js} +0 -0
@@ -6,9 +6,13 @@
6
6
  * Executes plans created by Claude using a local LLM.
7
7
  * Updates all Wogi Flow state files after each step.
8
8
  *
9
+ * LIMITATION: The --resume flag is documented but not yet implemented.
10
+ * Checkpoint data is saved but recovery logic needs completion.
11
+ * TODO: Implement resume from checkpoint functionality.
12
+ *
9
13
  * Usage:
10
14
  * flow-orchestrate <plan.json> # Execute a plan
11
- * flow-orchestrate --resume # Resume from checkpoint
15
+ * flow-orchestrate --resume # Resume from checkpoint (NOT YET IMPLEMENTED)
12
16
  * flow-orchestrate --rollback # Rollback last execution
13
17
  */
14
18
 
@@ -65,6 +69,14 @@ const {
65
69
  // Import response parser for error recovery
66
70
  const { parseOnRetry, cleanCodeBlock } = require('./flow-response-parser');
67
71
 
72
+ // Import validation module (extracted for modularity)
73
+ const {
74
+ extractCodeFromResponse,
75
+ isValidCode,
76
+ validateOutputMatchesTask,
77
+ validateImports
78
+ } = require('./flow-orchestrate-validation');
79
+
68
80
  // Import adaptive learning for smart retries and model improvement
69
81
  const {
70
82
  analyzeFailure,
@@ -122,7 +134,7 @@ function saveStructuredFailure(step, errorHistory, attempts, config) {
122
134
  errors: errorHistory.slice(-5).map(e => ({
123
135
  category: e.category,
124
136
  signature: e.signature,
125
- message: err.message?.slice(0, 500) || ''
137
+ message: e.message?.slice(0, 500) || ''
126
138
  })),
127
139
  suggestion: generateFixSuggestion(errorHistory),
128
140
  lastErrorCategory: errorHistory[errorHistory.length - 1]?.category || 'unknown'
@@ -211,511 +223,8 @@ function loadHybridConfig() {
211
223
  };
212
224
  }
213
225
 
214
- // ============================================================
215
- // Code Extraction
216
- // ============================================================
217
-
218
- /**
219
- * Extracts clean code from LLM response.
220
- * Handles:
221
- * - Thinking/reasoning preamble
222
- * - </think> tags (from models that use thinking tokens)
223
- * - Markdown code blocks
224
- * - Trailing explanations
225
- * - Model-specific artifacts (Llama, Qwen, DeepSeek, etc.)
226
- * - JSON wrapper responses
227
- * - Multiple code blocks (selects largest/most relevant)
228
- */
229
- function extractCodeFromResponse(response, modelName = '') {
230
- if (!response || typeof response !== 'string') {
231
- return response;
232
- }
233
-
234
- const rawResponse = response;
235
- let code = response;
236
-
237
- // 0. Handle JSON wrapper responses (some models wrap code in JSON)
238
- try {
239
- const jsonMatch = code.match(/^\s*\{[\s\S]*"code"\s*:\s*"([\s\S]*)"[\s\S]*\}\s*$/);
240
- if (jsonMatch) {
241
- code = JSON.parse(`"${jsonMatch[1]}"`); // Unescape JSON string
242
- }
243
- } catch { /* not JSON wrapped */ }
244
-
245
- // 1. Remove model-specific thinking tags and artifacts
246
- const thinkingPatterns = [
247
- // Standard thinking tags
248
- /<think>[\s\S]*?<\/think>/gi,
249
- /<thinking>[\s\S]*?<\/thinking>/gi,
250
- /<reasoning>[\s\S]*?<\/reasoning>/gi,
251
- /<analysis>[\s\S]*?<\/analysis>/gi,
252
-
253
- // Qwen-specific
254
- /<\|im_start\|>[\s\S]*?<\|im_end\|>/gi,
255
-
256
- // DeepSeek-specific artifacts
257
- /^<\|begin_of_sentence\|>/gm,
258
- /<\|end_of_sentence\|>$/gm,
259
-
260
- // Llama-specific
261
- /\[INST\][\s\S]*?\[\/INST\]/gi,
262
- /<<SYS>>[\s\S]*?<<\/SYS>>/gi,
263
-
264
- // Generic assistant markers
265
- /^Assistant:\s*/gim,
266
- /^AI:\s*/gim,
267
- /^Response:\s*/gim,
268
- /^Output:\s*/gim,
269
- /^Answer:\s*/gim,
270
- /^Code:\s*/gim,
271
-
272
- // Model-specific trailing signatures
273
- /---\s*End of (response|code|file)[\s\S]*$/gi,
274
- /\n\nPlease let me know[\s\S]*$/gi,
275
- /\n\nIs there anything[\s\S]*$/gi,
276
- /\n\nFeel free to[\s\S]*$/gi,
277
- /\n\nLet me know if[\s\S]*$/gi,
278
- ];
279
-
280
- for (const pattern of thinkingPatterns) {
281
- code = code.replace(pattern, '');
282
- }
283
-
284
- // 2. Handle </think> tag (if partial tag remains)
285
- const thinkEndMatch = code.match(/<\/think>\s*/i);
286
- if (thinkEndMatch) {
287
- code = code.slice(thinkEndMatch.index + thinkEndMatch[0].length);
288
- }
289
-
290
- // 3. Extract from markdown code blocks
291
- // Find all code blocks and pick the best one
292
- const codeBlocks = [...code.matchAll(/```(?:typescript|tsx|ts|javascript|jsx|js|plaintext)?\s*\n([\s\S]*?)```/g)];
293
-
294
- if (codeBlocks.length > 0) {
295
- // Score each block and pick the best one
296
- let bestBlock = codeBlocks[0][1];
297
- let bestScore = scoreCodeBlock(bestBlock);
298
-
299
- for (let i = 1; i < codeBlocks.length; i++) {
300
- const blockContent = codeBlocks[i][1];
301
- const score = scoreCodeBlock(blockContent);
302
- if (score > bestScore) {
303
- bestScore = score;
304
- bestBlock = blockContent;
305
- }
306
- }
307
- code = bestBlock;
308
- } else {
309
- // Also try to remove any remaining markdown code block markers
310
- code = code.replace(/^```(?:typescript|tsx|javascript|jsx|ts|js|plaintext)?\n/gm, '');
311
- code = code.replace(/\n```$/gm, '');
312
- code = code.replace(/^```$/gm, '');
313
- }
314
-
315
- // 4. Find first valid TypeScript/JavaScript line
316
- const validStartPatterns = [
317
- /^import\s/m,
318
- /^export\s/m,
319
- /^const\s/m,
320
- /^let\s/m,
321
- /^var\s/m,
322
- /^function\s/m,
323
- /^async\s+function\s/m,
324
- /^class\s/m,
325
- /^interface\s/m,
326
- /^type\s/m,
327
- /^enum\s/m,
328
- /^declare\s/m,
329
- /^module\s/m,
330
- /^namespace\s/m,
331
- /^\/\*\*/m, // JSDoc comment
332
- /^\/\*[^*]/m, // Block comment
333
- /^\/\//m, // Single line comment at start
334
- /^'use /m, // 'use strict' or 'use client'
335
- /^"use /m,
336
- /^@/m, // Decorators
337
- ];
338
-
339
- let earliestMatch = -1;
340
- for (const pattern of validStartPatterns) {
341
- const match = code.search(pattern);
342
- if (match !== -1 && (earliestMatch === -1 || match < earliestMatch)) {
343
- earliestMatch = match;
344
- }
345
- }
346
-
347
- if (earliestMatch > 0) {
348
- code = code.slice(earliestMatch);
349
- }
350
-
351
- // 5. Remove trailing explanations and prose
352
- const trailingPatterns = [
353
- // Standard prose after code
354
- /(\}|\;)\s*\n\s*\n+[A-Z][a-z]/,
355
- // Numbered explanations
356
- /(\}|\;)\s*\n\s*\n+\d+\.\s+/,
357
- // Bullet points
358
- /(\}|\;)\s*\n\s*\n+[-*•]\s+/,
359
- // Notes/explanations
360
- /(\}|\;)\s*\n\s*\n+(?:Note:|Explanation:|Summary:|Key |Important:)/i,
361
- ];
362
-
363
- for (const pattern of trailingPatterns) {
364
- const match = code.match(pattern);
365
- if (match) {
366
- code = code.slice(0, match.index + 1);
367
- break;
368
- }
369
- }
370
-
371
- // 6. Clean up common artifacts
372
- code = code
373
- // Remove zero-width characters
374
- .replace(/[\u200B-\u200D\uFEFF]/g, '')
375
- // Normalize line endings
376
- .replace(/\r\n/g, '\n')
377
- .replace(/\r/g, '\n')
378
- // Remove trailing whitespace on each line
379
- .replace(/[ \t]+$/gm, '')
380
- // Collapse multiple blank lines to max 2
381
- .replace(/\n{3,}/g, '\n\n')
382
- .trim();
383
-
384
- // Debug logging
385
- if (process.env.DEBUG_HYBRID) {
386
- console.log('\n--- RAW LLM RESPONSE (first 500 chars) ---');
387
- console.log(rawResponse.slice(0, 500));
388
- console.log('\n--- EXTRACTED CODE (first 500 chars) ---');
389
- console.log(code.slice(0, 500));
390
- console.log('---\n');
391
- }
392
-
393
- return code;
394
- }
395
-
396
- /**
397
- * Score a code block to determine which is most likely the actual code
398
- * Higher score = more likely to be the real code
399
- */
400
- function scoreCodeBlock(block) {
401
- if (!block) return 0;
402
-
403
- let score = 0;
404
-
405
- // Length bonus (longer is usually better, but cap it)
406
- score += Math.min(block.length / 100, 50);
407
-
408
- // Valid code patterns
409
- if (/^import\s/m.test(block)) score += 20;
410
- if (/^export\s/m.test(block)) score += 20;
411
- if (/^const\s/m.test(block)) score += 10;
412
- if (/^function\s/m.test(block)) score += 10;
413
- if (/^class\s/m.test(block)) score += 10;
414
- if (/^interface\s/m.test(block)) score += 15;
415
- if (/^type\s/m.test(block)) score += 10;
416
-
417
- // Code structure indicators
418
- score += (block.match(/\{/g) || []).length * 2;
419
- score += (block.match(/\}/g) || []).length * 2;
420
- score += (block.match(/=>/g) || []).length * 3;
421
- score += (block.match(/return\s/g) || []).length * 3;
422
-
423
- // Penalties for prose/non-code
424
- if (/^[A-Z][a-z]+\s+[a-z]+/m.test(block)) score -= 10; // Starts with prose
425
- if (/\.$/.test(block.trim())) score -= 5; // Ends with period (prose)
426
-
427
- return score;
428
- }
429
-
430
- /**
431
- * Validates if the extracted code looks like valid TypeScript/JavaScript.
432
- * Returns { valid: boolean, reason?: string }
433
- */
434
- function isValidCode(code) {
435
- if (!code) {
436
- return { valid: false, reason: 'Empty output' };
437
- }
438
-
439
- if (code.length < 10) {
440
- return { valid: false, reason: 'Output too short' };
441
- }
442
-
443
- const trimmed = code.trim();
444
-
445
- // Check for common LLM prose patterns that indicate thinking/explanation
446
- const prosePatterns = [
447
- /^(We need|Let's|The |I |You |This |Maybe|Probably|Actually|But |So |Thus |Given |Here|Now |First|To |In order)/i,
448
- /^(Looking at|Based on|According to|As you can|Note that|Remember|Consider|Thinking|Output:)/i,
449
- /^(```|~~~)/, // Markdown code fence at start means extraction failed
450
- /<think>|<\/think>/i, // Thinking tags leaked through
451
- ];
452
-
453
- for (const pattern of prosePatterns) {
454
- if (pattern.test(trimmed)) {
455
- return { valid: false, reason: `Starts with prose/thinking: "${trimmed.slice(0, 50)}..."` };
456
- }
457
- }
458
-
459
- // Must start with valid TS/JS syntax
460
- const validStartPatterns = /^(import|export|const|let|var|function|async|class|interface|type|enum|declare|module|namespace|\/\*\*|\/\*|\/\/|'use |"use |@)/;
461
-
462
- if (!validStartPatterns.test(trimmed)) {
463
- return { valid: false, reason: `Invalid start: "${trimmed.slice(0, 50)}..."` };
464
- }
465
-
466
- // Additional sanity checks
467
- // Should have some code-like structure (braces, semicolons, etc.)
468
- const hasCodeStructure = /[{};=()]/.test(code);
469
- if (!hasCodeStructure && code.length > 100) {
470
- return { valid: false, reason: 'No code structure detected (missing braces/semicolons)' };
471
- }
472
-
473
- return { valid: true };
474
- }
475
-
476
- // ============================================================
477
- // Semantic Output Validation
478
- // ============================================================
479
-
480
- /**
481
- * Validates that the output semantically matches what was requested.
482
- * This catches cases where the code is syntactically valid but implements
483
- * the wrong thing (e.g., creating ApprovalChain instead of Button).
484
- *
485
- * @param {string} code - The generated code
486
- * @param {Object} step - The step definition containing type and params
487
- * @returns {{ valid: boolean, reason?: string, confidence: number }}
488
- */
489
- function validateOutputMatchesTask(code, step) {
490
- if (!code || !step) {
491
- return { valid: true, confidence: 0 }; // Can't validate without info
492
- }
493
-
494
- const stepType = step.type;
495
- const expectedName = step.params?.name || step.params?.componentName || '';
496
- const targetPath = step.params?.path || '';
497
- const codeLower = code.toLowerCase();
498
- const issues = [];
499
- let confidence = 100;
500
-
501
- // Extract the expected filename/component name from path
502
- const fileBaseName = targetPath
503
- ? path.basename(targetPath, path.extname(targetPath))
504
- : expectedName;
505
-
506
- // 1. Check if expected name appears in the code
507
- if (fileBaseName && fileBaseName.length > 2) {
508
- const namePattern = new RegExp(`\\b${escapeRegex(fileBaseName)}\\b`, 'i');
509
- if (!namePattern.test(code)) {
510
- issues.push(`Expected "${fileBaseName}" not found in output`);
511
- confidence -= 40;
512
- }
513
- }
514
-
515
- // 2. Check step-type specific patterns
516
- switch (stepType) {
517
- case 'create-component':
518
- // Should have a function/const that exports a component
519
- if (!/export\s+(default\s+)?function|export\s+(default\s+)?const/.test(code)) {
520
- issues.push('No exported function/const found for component');
521
- confidence -= 30;
522
- }
523
- // Should have JSX (tsx file)
524
- if (targetPath.endsWith('.tsx') && !/<[A-Z]|<[a-z]+\s|<\//.test(code)) {
525
- issues.push('No JSX found in .tsx component');
526
- confidence -= 20;
527
- }
528
- break;
529
-
530
- case 'create-hook':
531
- // Should have a use* function
532
- if (!/function\s+use[A-Z]|const\s+use[A-Z]/.test(code)) {
533
- issues.push('No use* hook function found');
534
- confidence -= 50;
535
- }
536
- break;
537
-
538
- case 'create-service':
539
- // Should have exports (functions or class)
540
- if (!/export\s+(const|function|class|async)/.test(code)) {
541
- issues.push('No exports found in service');
542
- confidence -= 30;
543
- }
544
- break;
545
-
546
- case 'modify-file':
547
- // For modifications, the expected changes should be present
548
- // This is harder to validate without more context
549
- break;
550
- }
551
-
552
- // 3. Check for common "wrong thing" patterns
553
- // If the code exports something completely different from expected name
554
- const exportMatches = code.match(/export\s+(?:default\s+)?(?:function|const|class)\s+(\w+)/g) || [];
555
- if (exportMatches.length > 0 && fileBaseName) {
556
- const exportNames = exportMatches.map(m => {
557
- const parts = m.split(/\s+/);
558
- return parts[parts.length - 1];
559
- });
560
-
561
- // Check if any export is similar to expected name
562
- const hasMatchingExport = exportNames.some(name =>
563
- name.toLowerCase().includes(fileBaseName.toLowerCase()) ||
564
- fileBaseName.toLowerCase().includes(name.toLowerCase())
565
- );
566
-
567
- if (!hasMatchingExport && exportNames.length > 0) {
568
- issues.push(`Exports [${exportNames.join(', ')}] but expected "${fileBaseName}"`);
569
- confidence -= 30;
570
- }
571
- }
572
-
573
- // Validation result
574
- const valid = confidence >= 50;
575
- return {
576
- valid,
577
- reason: issues.length > 0 ? issues.join('; ') : undefined,
578
- confidence,
579
- issues
580
- };
581
- }
582
-
583
- /**
584
- * Escapes special regex characters in a string
585
- */
586
- function escapeRegex(string) {
587
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
588
- }
589
-
590
- // ============================================================
591
- // Import Validation (Config-Driven)
592
- // ============================================================
593
-
594
- /**
595
- * Validates imports in generated code against the export map.
596
- * Uses the cached export map for accurate import validation.
597
- *
598
- * @param {string} code - The generated code
599
- * @param {Object} exportMap - The export map (or null to load from cache)
600
- * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
601
- */
602
- function validateImports(code, exportMap = null) {
603
- const errors = [];
604
- const warnings = [];
605
-
606
- // Load export map if not provided
607
- if (!exportMap) {
608
- exportMap = loadCachedExportMap();
609
- if (!exportMap) {
610
- // No export map available, can't validate
611
- return { valid: true, errors: [], warnings: ['No export map available for validation'] };
612
- }
613
- }
614
-
615
- // Load doNotImport from config
616
- let doNotImport = ['React']; // Default
617
- try {
618
- const config = getConfig();
619
- doNotImport = config.hybrid?.projectContext?.doNotImport || ['React'];
620
- } catch {}
621
-
622
- // Build a lookup map for all exports by import path
623
- const exportsByPath = new Map();
624
-
625
- // Add all exports from the map
626
- for (const [category, items] of Object.entries(exportMap)) {
627
- if (category === '_meta') continue;
628
-
629
- for (const [name, info] of Object.entries(items)) {
630
- if (!info.importPath) continue;
631
-
632
- const exports = [];
633
- if (info.exports?.length > 0) exports.push(...info.exports);
634
- if (info.types?.length > 0) exports.push(...info.types);
635
- if (info.defaultExport) exports.push(info.defaultExport);
636
-
637
- exportsByPath.set(info.importPath, {
638
- name,
639
- exports,
640
- defaultExport: info.defaultExport,
641
- category
642
- });
643
- }
644
- }
645
-
646
- // Extract imports from code
647
- const importMatches = code.match(/import\s+(?:type\s+)?(?:{[^}]*}|[\w*]+)?\s*(?:,\s*{[^}]*})?\s*from\s+['"]([^'"]+)['"]/g) || [];
648
-
649
- for (const importLine of importMatches) {
650
- // Extract the import path
651
- const pathMatch = importLine.match(/from\s+['"]([^'"]+)['"]/);
652
- if (!pathMatch) continue;
653
-
654
- const importPath = pathMatch[1];
655
-
656
- // Skip external packages
657
- if (!importPath.startsWith('@/') && !importPath.startsWith('./') && !importPath.startsWith('../')) {
658
- // Check doNotImport for external packages
659
- for (const forbidden of doNotImport) {
660
- if (importLine.includes(`import ${forbidden} `) ||
661
- importLine.includes(`import ${forbidden},`) ||
662
- importLine.includes(`import * as ${forbidden}`)) {
663
- errors.push(`Forbidden import detected: "import ${forbidden}" - use named imports instead`);
664
- }
665
- }
666
- continue;
667
- }
668
-
669
- // Check if import path exists in our export map
670
- const knownExports = exportsByPath.get(importPath);
671
-
672
- if (!knownExports) {
673
- // Path not in export map - might be a relative import or unknown path
674
- if (importPath.startsWith('@/')) {
675
- warnings.push(`Import path "${importPath}" not found in export map - verify it exists`);
676
- }
677
- continue;
678
- }
679
-
680
- // Extract what's being imported
681
- const namedImportsMatch = importLine.match(/{([^}]+)}/);
682
- if (namedImportsMatch) {
683
- const importedNames = namedImportsMatch[1]
684
- .split(',')
685
- .map(n => n.trim().split(/\s+as\s+/)[0].trim()) // Handle "X as Y"
686
- .filter(n => n && n !== 'type'); // Filter out 'type' keyword
687
-
688
- const availableExports = knownExports.exports || [];
689
-
690
- for (const importedName of importedNames) {
691
- if (importedName && !availableExports.includes(importedName)) {
692
- const suggestions = availableExports.slice(0, 5).join(', ');
693
- errors.push(`"${importedName}" is not exported by "${importPath}" - available: ${suggestions}`);
694
- }
695
- }
696
- }
697
-
698
- // Check default import
699
- const defaultImportMatch = importLine.match(/import\s+(\w+)\s*(?:,|from)/);
700
- if (defaultImportMatch) {
701
- const defaultImportName = defaultImportMatch[1];
702
- if (defaultImportName !== 'type' && !knownExports.defaultExport) {
703
- // Check if they might want a named export
704
- if (knownExports.exports.includes(defaultImportName)) {
705
- warnings.push(`"${defaultImportName}" is a named export, not default - use: import { ${defaultImportName} } from '${importPath}'`);
706
- } else {
707
- errors.push(`"${importPath}" has no default export - use named imports instead`);
708
- }
709
- }
710
- }
711
- }
712
-
713
- return {
714
- valid: errors.length === 0,
715
- errors,
716
- warnings
717
- };
718
- }
226
+ // NOTE: Code extraction and validation functions moved to flow-orchestrate-validation.js
227
+ // Functions: extractCodeFromResponse, scoreCodeBlock, isValidCode, validateOutputMatchesTask, validateImports, escapeRegex
719
228
 
720
229
  // ============================================================
721
230
  // Auto-Correction for Common LLM Mistakes