midnight-mcp 0.1.28 → 0.1.30

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.
@@ -1,16 +1,11 @@
1
1
  /**
2
2
  * Contract validation handlers
3
- * Pre-compilation validation for Compact contracts using the Compact CLI
3
+ * Static analysis for Compact contracts
4
4
  */
5
- import { exec, execFile } from "child_process";
6
- import { promisify } from "util";
7
- import { writeFile, mkdir, readFile, rm } from "fs/promises";
8
- import { join, basename, resolve, isAbsolute } from "path";
9
- import { tmpdir } from "os";
5
+ import { readFile } from "fs/promises";
6
+ import { basename, isAbsolute, resolve } from "path";
10
7
  import { platform } from "process";
11
8
  import { logger } from "../../utils/index.js";
12
- const execAsync = promisify(exec);
13
- const execFileAsync = promisify(execFile);
14
9
  // ============================================================================
15
10
  // SECURITY & VALIDATION HELPERS
16
11
  // ============================================================================
@@ -76,739 +71,6 @@ function isValidUtf8Text(content) {
76
71
  }
77
72
  return true;
78
73
  }
79
- /**
80
- * Detect local includes that won't work in temp directory
81
- */
82
- function detectLocalIncludes(code) {
83
- const localIncludes = [];
84
- // Pattern: include "something.compact" or include "./path"
85
- const includePattern = /include\s+"([^"]+)"/g;
86
- let match;
87
- while ((match = includePattern.exec(code)) !== null) {
88
- const includePath = match[1];
89
- // Skip standard library includes
90
- if (includePath === "std" ||
91
- includePath.startsWith("CompactStandardLibrary")) {
92
- continue;
93
- }
94
- // Local file reference
95
- if (includePath.endsWith(".compact") ||
96
- includePath.startsWith("./") ||
97
- includePath.startsWith("../")) {
98
- localIncludes.push(includePath);
99
- }
100
- }
101
- return localIncludes;
102
- }
103
- // ============================================================================
104
- // VALIDATION HANDLERS
105
- // ============================================================================
106
- /**
107
- * Validate a Compact contract by running the compiler
108
- * This provides pre-compilation validation with detailed error diagnostics
109
- */
110
- export async function validateContract(input) {
111
- logger.debug("Validating contract", {
112
- filename: input.filename,
113
- hasCode: !!input.code,
114
- filePath: input.filePath,
115
- });
116
- // ============================================================================
117
- // RESOLVE CODE SOURCE - Either from code string or file path
118
- // ============================================================================
119
- let code;
120
- let filename;
121
- let sourceDir = null; // Track source directory for local includes
122
- let originalFilePath = null; // Track original file path for compilation
123
- if (input.filePath) {
124
- // SECURITY: Validate file path first
125
- const pathValidation = validateFilePath(input.filePath);
126
- if (!pathValidation.valid) {
127
- return {
128
- success: false,
129
- errorType: "security_error",
130
- error: "Invalid file path",
131
- message: `❌ ${pathValidation.error}`,
132
- userAction: {
133
- problem: pathValidation.error,
134
- solution: "Provide an absolute path to a .compact file in your project directory",
135
- example: { filePath: "/Users/you/projects/myapp/contract.compact" },
136
- isUserFault: true,
137
- },
138
- };
139
- }
140
- const safePath = pathValidation.normalizedPath;
141
- sourceDir = join(safePath, "..");
142
- originalFilePath = safePath; // Store for use in compilation
143
- // SECURITY: Validate sourceDir against blocked paths
144
- // This prevents malicious includes from accessing system directories
145
- const sourceDirValidation = validateFilePath(join(sourceDir, "dummy.compact"));
146
- if (!sourceDirValidation.valid &&
147
- sourceDirValidation.error?.includes("system directories")) {
148
- return {
149
- success: false,
150
- errorType: "security_error",
151
- error: "Invalid source directory",
152
- message: "❌ Cannot access files in system directories",
153
- userAction: {
154
- problem: "The contract's parent directory is a restricted system location",
155
- solution: "Move your contract files to a user project directory",
156
- isUserFault: true,
157
- },
158
- };
159
- }
160
- // Read code from file
161
- try {
162
- code = await readFile(safePath, "utf-8");
163
- filename = basename(safePath);
164
- // SECURITY: Check for binary/non-UTF8 content
165
- if (!isValidUtf8Text(code)) {
166
- return {
167
- success: false,
168
- errorType: "user_error",
169
- error: "Invalid file content",
170
- message: "❌ File appears to be binary or contains invalid characters",
171
- userAction: {
172
- problem: "The file is not a valid UTF-8 text file",
173
- solution: "Ensure you're pointing to a Compact source file (.compact), not a compiled binary",
174
- isUserFault: true,
175
- },
176
- };
177
- }
178
- }
179
- catch (fsError) {
180
- const err = fsError;
181
- return {
182
- success: false,
183
- errorType: "user_error",
184
- error: "Failed to read file",
185
- message: `❌ Cannot read file: ${input.filePath}`,
186
- userAction: {
187
- problem: err.code === "ENOENT"
188
- ? "File does not exist"
189
- : err.code === "EACCES"
190
- ? "Permission denied"
191
- : "Cannot read file",
192
- solution: err.code === "ENOENT"
193
- ? "Check that the file path is correct"
194
- : "Check file permissions",
195
- details: err.message,
196
- isUserFault: true,
197
- },
198
- };
199
- }
200
- }
201
- else if (input.code) {
202
- code = input.code;
203
- // Sanitize filename to prevent command injection
204
- const rawFilename = input.filename || "contract.compact";
205
- filename = rawFilename.replace(/[^a-zA-Z0-9._-]/g, "_");
206
- if (!filename.endsWith(".compact")) {
207
- filename = "contract.compact";
208
- }
209
- // Check for binary content in provided code
210
- if (!isValidUtf8Text(code)) {
211
- return {
212
- success: false,
213
- errorType: "user_error",
214
- error: "Invalid code content",
215
- message: "❌ Code contains invalid characters",
216
- userAction: {
217
- problem: "The provided code contains binary or non-printable characters",
218
- solution: "Provide valid UTF-8 Compact source code",
219
- isUserFault: true,
220
- },
221
- };
222
- }
223
- }
224
- else {
225
- // Neither code nor filePath provided
226
- return {
227
- success: false,
228
- errorType: "user_error",
229
- error: "No contract provided",
230
- message: "❌ Must provide either 'code' or 'filePath'",
231
- userAction: {
232
- problem: "Neither code string nor file path was provided",
233
- solution: "Provide the contract source code OR a path to a .compact file",
234
- example: {
235
- withCode: { code: "pragma language_version >= 0.16; ..." },
236
- withFile: { filePath: "/path/to/contract.compact" },
237
- },
238
- isUserFault: true,
239
- },
240
- };
241
- }
242
- // ============================================================================
243
- // INPUT VALIDATION - Check for user errors before attempting compilation
244
- // ============================================================================
245
- // Check for local includes that won't work in temp directory
246
- const localIncludes = detectLocalIncludes(code);
247
- if (localIncludes.length > 0 && !sourceDir) {
248
- // Code was provided directly (not from file) and has local includes
249
- return {
250
- success: false,
251
- errorType: "user_error",
252
- error: "Local includes detected",
253
- message: "❌ Contract has local file includes that cannot be resolved",
254
- userAction: {
255
- problem: `Contract includes local files: ${localIncludes.join(", ")}`,
256
- solution: "Use filePath instead of code when your contract has local includes, so we can resolve relative paths",
257
- detectedIncludes: localIncludes,
258
- example: {
259
- instead: '{ code: "include \\"utils.compact\\"; ..." }',
260
- use: '{ filePath: "/path/to/your/contract.compact" }',
261
- },
262
- isUserFault: true,
263
- },
264
- };
265
- }
266
- // Warn about local includes (they may fail during compilation)
267
- const localIncludeWarning = localIncludes.length > 0
268
- ? {
269
- warning: "Contract has local includes",
270
- includes: localIncludes,
271
- note: "Local includes may fail if files are not in the expected location relative to the contract",
272
- }
273
- : null;
274
- // Check for empty input
275
- if (!code || code.trim().length === 0) {
276
- return {
277
- success: false,
278
- errorType: "user_error",
279
- error: "Empty contract code provided",
280
- message: "❌ No contract code to validate",
281
- userAction: {
282
- problem: "The contract code is empty or contains only whitespace",
283
- solution: "Provide valid Compact contract source code",
284
- example: `pragma language_version >= 0.16;
285
-
286
- import CompactStandardLibrary;
287
-
288
- export ledger counter: Counter;
289
-
290
- export circuit increment(): [] {
291
- counter.increment(1);
292
- }`,
293
- },
294
- };
295
- }
296
- // Check for excessively large input (potential abuse or mistake)
297
- const MAX_CODE_SIZE = 1024 * 1024; // 1MB
298
- if (code.length > MAX_CODE_SIZE) {
299
- return {
300
- success: false,
301
- errorType: "user_error",
302
- error: "Contract code too large",
303
- message: "❌ Contract code exceeds maximum size",
304
- userAction: {
305
- problem: `Contract is ${(code.length / 1024).toFixed(1)}KB, maximum is ${MAX_CODE_SIZE / 1024}KB`,
306
- solution: "Reduce contract size or split into multiple files",
307
- },
308
- };
309
- }
310
- // Check for missing pragma (common user mistake)
311
- if (!code.includes("pragma language_version")) {
312
- return {
313
- success: false,
314
- errorType: "user_error",
315
- error: "Missing pragma directive",
316
- message: "❌ Contract is missing required pragma directive",
317
- userAction: {
318
- problem: "All Compact contracts must start with a pragma language_version directive",
319
- solution: "Add pragma directive at the beginning of your contract",
320
- fix: "Add: pragma language_version >= 0.16;",
321
- example: `pragma language_version >= 0.16;
322
-
323
- import CompactStandardLibrary;
324
-
325
- // ... rest of your contract`,
326
- },
327
- detectedIssues: ["Missing pragma language_version directive"],
328
- };
329
- }
330
- // Check for missing import (common for Counter, Map, etc.)
331
- // Use word boundaries to avoid false positives in comments/strings
332
- const usesStdLib = /\bCounter\b/.test(code) ||
333
- /\bMap\s*</.test(code) ||
334
- /\bSet\s*</.test(code) ||
335
- /\bOpaque\s*</.test(code);
336
- const hasImport = /\bimport\s+CompactStandardLibrary\b/.test(code) ||
337
- /\binclude\s+"std"/.test(code);
338
- if (usesStdLib && !hasImport) {
339
- return {
340
- success: false,
341
- errorType: "user_error",
342
- error: "Missing standard library import",
343
- message: "❌ Contract uses standard library types without importing them",
344
- userAction: {
345
- problem: "You're using types like Counter, Map, Set, or Opaque without importing the standard library",
346
- solution: "Add the import statement after your pragma directive",
347
- fix: "Add: import CompactStandardLibrary;",
348
- example: `pragma language_version >= 0.16;
349
-
350
- import CompactStandardLibrary;
351
-
352
- export ledger counter: Counter;
353
- // ...`,
354
- },
355
- detectedIssues: [
356
- "Uses standard library types (Counter, Map, Set, Opaque)",
357
- "Missing: import CompactStandardLibrary;",
358
- ],
359
- };
360
- }
361
- // ============================================================================
362
- // COMPILER CHECK - Verify compiler is available
363
- // ============================================================================
364
- let compactPath = "";
365
- let compilerVersion = "";
366
- try {
367
- if (platform === "win32") {
368
- // On Windows, avoid the built-in NTFS 'compact.exe' from System32
369
- // by iterating through all candidates and verifying each one
370
- const { stdout: whereOutput } = await execAsync("where compact.exe");
371
- const candidates = whereOutput
372
- .trim()
373
- .split(/\r?\n/)
374
- .filter((line) => line.trim().length > 0);
375
- let found = false;
376
- for (const candidate of candidates) {
377
- try {
378
- const candidatePath = candidate.trim();
379
- // Skip Windows System32 compact.exe (NTFS compression utility)
380
- if (candidatePath.toLowerCase().includes("system32")) {
381
- continue;
382
- }
383
- const { stdout: versionOutput } = await execFileAsync(candidatePath, [
384
- "compile",
385
- "--version",
386
- ]);
387
- compactPath = candidatePath;
388
- compilerVersion = versionOutput.trim();
389
- found = true;
390
- break;
391
- }
392
- catch {
393
- // Try next candidate
394
- }
395
- }
396
- if (!found) {
397
- throw new Error("Compact compiler not found in PATH");
398
- }
399
- }
400
- else {
401
- // Unix: use which to find compact
402
- const { stdout: whichOutput } = await execAsync("which compact");
403
- compactPath = whichOutput.trim().split(/\r?\n/)[0];
404
- const { stdout: versionOutput } = await execFileAsync(compactPath, [
405
- "compile",
406
- "--version",
407
- ]);
408
- compilerVersion = versionOutput.trim();
409
- }
410
- }
411
- catch {
412
- return {
413
- success: false,
414
- errorType: "environment_error",
415
- compilerInstalled: false,
416
- error: "Compact compiler not found",
417
- message: "❌ Compact compiler is not installed",
418
- installation: {
419
- message: "The Compact compiler is required for contract validation. Install it with:",
420
- command: `curl --proto '=https' --tlsv1.2 -LsSf https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh`,
421
- postInstall: [
422
- "After installation, run: compact update",
423
- "Then verify with: compact compile --version",
424
- ],
425
- docs: "https://docs.midnight.network/develop/tutorial/building",
426
- },
427
- userAction: {
428
- problem: "The Compact compiler is not installed on this system",
429
- solution: "Install the compiler using the command above, then retry validation",
430
- isUserFault: false,
431
- },
432
- };
433
- }
434
- // Check compiler version compatibility
435
- const versionMatch = compilerVersion.match(/(\d+)\.(\d+)/);
436
- if (versionMatch) {
437
- const major = parseInt(versionMatch[1], 10);
438
- const minor = parseInt(versionMatch[2], 10);
439
- if (major === 0 && minor < 16) {
440
- return {
441
- success: false,
442
- errorType: "environment_error",
443
- compilerInstalled: true,
444
- compilerVersion,
445
- error: "Compiler version too old",
446
- message: `❌ Compact compiler ${compilerVersion} is outdated`,
447
- userAction: {
448
- problem: `Your compiler version (${compilerVersion}) may not support current syntax`,
449
- solution: "Update to the latest compiler version",
450
- command: "compact update",
451
- isUserFault: false,
452
- },
453
- };
454
- }
455
- }
456
- // ============================================================================
457
- // COMPILATION - Create temp files and run compiler
458
- // ============================================================================
459
- const tempDir = join(tmpdir(), `midnight-validate-${Date.now()}`);
460
- const contractPath = join(tempDir, filename);
461
- const outputDir = join(tempDir, "output");
462
- try {
463
- // Create temp directory
464
- try {
465
- await mkdir(tempDir, { recursive: true });
466
- await mkdir(outputDir, { recursive: true });
467
- }
468
- catch (fsError) {
469
- const err = fsError;
470
- return {
471
- success: false,
472
- errorType: "system_error",
473
- error: "Failed to create temporary directory",
474
- message: "❌ System error: Cannot create temp files",
475
- systemError: {
476
- code: err.code,
477
- details: err.message,
478
- problem: err.code === "ENOSPC"
479
- ? "Disk is full"
480
- : err.code === "EACCES"
481
- ? "Permission denied"
482
- : "File system error",
483
- solution: err.code === "ENOSPC"
484
- ? "Free up disk space and retry"
485
- : err.code === "EACCES"
486
- ? "Check file system permissions"
487
- : "Check system resources",
488
- isUserFault: false,
489
- },
490
- };
491
- }
492
- // Write contract file
493
- try {
494
- await writeFile(contractPath, code, "utf-8");
495
- }
496
- catch (writeError) {
497
- const err = writeError;
498
- return {
499
- success: false,
500
- errorType: "system_error",
501
- error: "Failed to write contract file",
502
- message: "❌ System error: Cannot write temp file",
503
- systemError: {
504
- code: err.code,
505
- details: err.message,
506
- isUserFault: false,
507
- },
508
- };
509
- }
510
- // Run compilation
511
- // When originalFilePath is available (file path provided), compile the original file
512
- // from its source directory to resolve local includes correctly.
513
- // Otherwise, compile the temp file from the temp directory.
514
- const fileToCompile = originalFilePath || contractPath;
515
- const compileCwd = originalFilePath ? sourceDir : tempDir;
516
- try {
517
- const execOptions = {
518
- timeout: 60000, // 60 second timeout
519
- maxBuffer: 10 * 1024 * 1024, // 10MB buffer
520
- cwd: compileCwd, // Use appropriate directory for include resolution
521
- };
522
- // Use execFile with array arguments to avoid shell injection vulnerabilities
523
- // This is safer than string interpolation as paths are passed directly
524
- const { stdout, stderr } = await execFileAsync("compact", ["compile", fileToCompile, outputDir], execOptions);
525
- // Compilation succeeded!
526
- const allWarnings = stderr ? parseWarnings(stderr) : [];
527
- // Add local include warning if applicable
528
- if (localIncludeWarning) {
529
- allWarnings.push(`Note: Contract has local includes (${localIncludes.join(", ")}) - ensure these files exist relative to your contract`);
530
- }
531
- return {
532
- success: true,
533
- errorType: null,
534
- compilerInstalled: true,
535
- compilerVersion,
536
- compilerPath: compactPath,
537
- message: "✅ Contract compiled successfully!",
538
- output: stdout || "Compilation completed without errors",
539
- warnings: allWarnings,
540
- localIncludes: localIncludes.length > 0 ? localIncludes : undefined,
541
- contractInfo: {
542
- filename,
543
- codeLength: code.length,
544
- lineCount: code.split("\n").length,
545
- },
546
- nextSteps: [
547
- "The contract syntax is valid and compiles",
548
- "Generated files would be in the output directory",
549
- "You can proceed with deployment or further development",
550
- ],
551
- };
552
- }
553
- catch (compileError) {
554
- // Compilation failed - parse and categorize the error
555
- const error = compileError;
556
- // Check for timeout
557
- if (error.killed || error.signal === "SIGTERM") {
558
- return {
559
- success: false,
560
- errorType: "timeout_error",
561
- compilerInstalled: true,
562
- compilerVersion,
563
- error: "Compilation timed out",
564
- message: "❌ Compilation timed out after 60 seconds",
565
- userAction: {
566
- problem: "The contract took too long to compile",
567
- solution: "Simplify the contract or check for infinite loops in circuit logic",
568
- possibleCauses: [
569
- "Very complex contract with many circuits",
570
- "Recursive or deeply nested structures",
571
- "Large number of constraints",
572
- ],
573
- isUserFault: true,
574
- },
575
- };
576
- }
577
- const errorOutput = error.stderr || error.stdout || error.message || "";
578
- const diagnostics = parseCompilerErrors(errorOutput, code);
579
- // Categorize the error for better user feedback
580
- const errorCategory = categorizeCompilerError(errorOutput);
581
- return {
582
- success: false,
583
- errorType: "compilation_error",
584
- errorCategory,
585
- compilerInstalled: true,
586
- compilerVersion,
587
- compilerPath: compactPath,
588
- message: `❌ ${errorCategory.title}`,
589
- errors: diagnostics.errors,
590
- errorCount: diagnostics.errors.length,
591
- rawOutput: errorOutput.slice(0, 2000),
592
- contractInfo: {
593
- filename,
594
- codeLength: code.length,
595
- lineCount: code.split("\n").length,
596
- },
597
- userAction: {
598
- problem: errorCategory.explanation,
599
- solution: errorCategory.solution,
600
- isUserFault: true,
601
- },
602
- suggestions: diagnostics.suggestions,
603
- commonFixes: getCommonFixes(diagnostics.errors),
604
- };
605
- }
606
- }
607
- finally {
608
- // Cleanup temp files (cross-platform)
609
- try {
610
- await rm(tempDir, { recursive: true, force: true });
611
- }
612
- catch {
613
- // Ignore cleanup errors
614
- }
615
- }
616
- }
617
- // ============================================================================
618
- // ERROR PARSING HELPERS
619
- // ============================================================================
620
- /**
621
- * Categorize compiler errors for better user feedback
622
- */
623
- function categorizeCompilerError(output) {
624
- const lowerOutput = output.toLowerCase();
625
- if (lowerOutput.includes("parse error") ||
626
- lowerOutput.includes("looking for")) {
627
- return {
628
- category: "syntax_error",
629
- title: "Syntax Error",
630
- explanation: "The contract has invalid syntax that the parser cannot understand",
631
- solution: "Check for missing semicolons, brackets, or typos near the indicated line",
632
- };
633
- }
634
- if (lowerOutput.includes("type") &&
635
- (lowerOutput.includes("mismatch") || lowerOutput.includes("expected"))) {
636
- return {
637
- category: "type_error",
638
- title: "Type Error",
639
- explanation: "There is a type mismatch in your contract",
640
- solution: "Ensure variable types match expected types in operations",
641
- };
642
- }
643
- if (lowerOutput.includes("undefined") ||
644
- lowerOutput.includes("not found") ||
645
- lowerOutput.includes("unknown")) {
646
- return {
647
- category: "reference_error",
648
- title: "Reference Error",
649
- explanation: "The contract references something that doesn't exist",
650
- solution: "Check that all variables, types, and functions are properly defined or imported",
651
- };
652
- }
653
- if (lowerOutput.includes("import") ||
654
- lowerOutput.includes("include") ||
655
- lowerOutput.includes("module")) {
656
- return {
657
- category: "import_error",
658
- title: "Import Error",
659
- explanation: "There is a problem with an import or include statement",
660
- solution: "Verify import paths and ensure required libraries are available",
661
- };
662
- }
663
- if (lowerOutput.includes("circuit") ||
664
- lowerOutput.includes("witness") ||
665
- lowerOutput.includes("ledger")) {
666
- return {
667
- category: "structure_error",
668
- title: "Contract Structure Error",
669
- explanation: "There is an issue with the contract structure (circuits, witnesses, or ledger)",
670
- solution: "Review the contract structure against Compact documentation",
671
- };
672
- }
673
- return {
674
- category: "unknown_error",
675
- title: "Compilation Failed",
676
- explanation: "The compiler encountered an error",
677
- solution: "Review the error message and check Compact documentation",
678
- };
679
- }
680
- /**
681
- * Parse compiler error output into structured diagnostics
682
- */
683
- function parseCompilerErrors(output, sourceCode) {
684
- const errors = [];
685
- const suggestions = [];
686
- const lines = sourceCode.split("\n");
687
- // Common patterns in Compact compiler output
688
- // Pattern: "error: <message>" or "Error: <message>"
689
- const errorLinePattern = /(?:error|Error):\s*(.+)/gi;
690
- // Pattern: "line <n>:" or "at line <n>" or "<filename>:<line>:<col>"
691
- const lineNumberPattern = /(?:line\s*(\d+)|at\s+line\s+(\d+)|:(\d+):(\d+))/i;
692
- // Pattern: "expected <x>, found <y>"
693
- const expectedPattern = /expected\s+['"`]?([^'"`]+)['"`]?,?\s*(?:found|got)\s+['"`]?([^'"`]+)['"`]?/i;
694
- // Split output into logical segments
695
- const segments = output.split(/(?=error:|Error:)/i);
696
- for (const segment of segments) {
697
- if (!segment.trim())
698
- continue;
699
- const errorMatch = segment.match(errorLinePattern);
700
- if (errorMatch) {
701
- const message = errorMatch[0].replace(/^(?:error|Error):\s*/i, "").trim();
702
- // Try to extract line number
703
- const lineMatch = segment.match(lineNumberPattern);
704
- const line = lineMatch
705
- ? parseInt(lineMatch[1] || lineMatch[2] || lineMatch[3], 10)
706
- : undefined;
707
- const column = lineMatch && lineMatch[4] ? parseInt(lineMatch[4], 10) : undefined;
708
- // Get source context if we have a line number
709
- let context;
710
- if (line && line > 0 && line <= lines.length) {
711
- const start = Math.max(0, line - 2);
712
- const end = Math.min(lines.length, line + 1);
713
- context = lines
714
- .slice(start, end)
715
- .map((l, i) => `${start + i + 1}: ${l}`)
716
- .join("\n");
717
- }
718
- errors.push({
719
- line,
720
- column,
721
- message,
722
- severity: "error",
723
- context,
724
- });
725
- // Generate suggestions based on error type
726
- const expectedMatch = message.match(expectedPattern);
727
- if (expectedMatch) {
728
- suggestions.push(`Expected "${expectedMatch[1]}" but found "${expectedMatch[2]}". Check your syntax.`);
729
- }
730
- }
731
- }
732
- // If no structured errors found, add the raw output as an error
733
- if (errors.length === 0 && output.trim()) {
734
- errors.push({
735
- message: output.trim().slice(0, 500),
736
- severity: "error",
737
- });
738
- }
739
- // Add general suggestions based on common issues
740
- if (output.includes("Cell")) {
741
- suggestions.push("Remember to use .value to access Cell<T> contents (e.g., state.value)");
742
- }
743
- if (output.includes("Opaque")) {
744
- suggestions.push('Opaque<"string"> is a type, not a type alias. Use it directly in signatures.');
745
- }
746
- if (output.includes("disclose")) {
747
- suggestions.push("In conditionals, use: const x = disclose(expr); if (x) { ... } instead of if (disclose(expr))");
748
- }
749
- if (output.includes("Counter")) {
750
- suggestions.push("Counter type requires initialization: counter = Counter.increment(counter, 1)");
751
- }
752
- return { errors, suggestions };
753
- }
754
- /**
755
- * Parse warnings from compiler output
756
- */
757
- function parseWarnings(output) {
758
- const warnings = [];
759
- const warningPattern = /(?:warning|Warning):\s*(.+)/gi;
760
- let match;
761
- while ((match = warningPattern.exec(output)) !== null) {
762
- warnings.push(match[1].trim());
763
- }
764
- return warnings;
765
- }
766
- /**
767
- * Get common fixes based on error patterns
768
- */
769
- function getCommonFixes(errors) {
770
- const fixes = [];
771
- const messages = errors.map((e) => e.message.toLowerCase()).join(" ");
772
- if (messages.includes("cell") || messages.includes("value")) {
773
- fixes.push({
774
- pattern: "Cell<T> access error",
775
- fix: "Use `state.value` instead of just `state` when accessing Cell contents",
776
- });
777
- }
778
- if (messages.includes("opaque") || messages.includes("string type")) {
779
- fixes.push({
780
- pattern: "Opaque string type error",
781
- fix: 'Use `Opaque<"your_type_name">` directly - it cannot be aliased with type keyword',
782
- });
783
- }
784
- if (messages.includes("boolean") || messages.includes("witness")) {
785
- fixes.push({
786
- pattern: "Boolean witness error",
787
- fix: "Witnesses return `Uint<1>` not `Boolean` - use `x != 0` to convert to Boolean",
788
- });
789
- }
790
- if (messages.includes("disclose") || messages.includes("conditional")) {
791
- fixes.push({
792
- pattern: "Disclosure in conditional error",
793
- fix: "Store disclose() result in const before using in if: `const revealed = disclose(x); if (revealed) { ... }`",
794
- });
795
- }
796
- if (messages.includes("counter") || messages.includes("increment")) {
797
- fixes.push({
798
- pattern: "Counter initialization error",
799
- fix: "Initialize counters with: `counter = Counter.increment(counter, 1)`",
800
- });
801
- }
802
- if (messages.includes("map") ||
803
- messages.includes("key") ||
804
- messages.includes("insert")) {
805
- fixes.push({
806
- pattern: "Map operation error",
807
- fix: "Maps require aligned access: insert at key before reading, or use default values",
808
- });
809
- }
810
- return fixes;
811
- }
812
74
  // ============================================================================
813
75
  // CONTRACT STRUCTURE EXTRACTION
814
76
  // ============================================================================