minovative-mind-cli 2.6.4 → 2.8.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.
@@ -299,3 +299,238 @@ export async function verifyChangedFiles(workspaceRoot, filePaths, abortSignal)
299
299
  warnings: terminalWarnings || null,
300
300
  };
301
301
  }
302
+ /**
303
+ * Common mutation operators for AST/line-level diff mutation testing.
304
+ */
305
+ const MUTATION_OPERATORS = [
306
+ {
307
+ name: 'EqualityReplacement',
308
+ pattern: /(===|!==|==|!=)/g,
309
+ replace: (m) => {
310
+ if (m === '===')
311
+ return '!==';
312
+ if (m === '!==')
313
+ return '===';
314
+ if (m === '==')
315
+ return '!=';
316
+ if (m === '!=')
317
+ return '==';
318
+ return m;
319
+ },
320
+ },
321
+ {
322
+ name: 'RelationalReplacement',
323
+ pattern: /(>=|<=|>|<)/g,
324
+ replace: (m) => {
325
+ if (m === '>=')
326
+ return '<';
327
+ if (m === '<=')
328
+ return '>';
329
+ if (m === '>')
330
+ return '<=';
331
+ if (m === '<')
332
+ return '>=';
333
+ return m;
334
+ },
335
+ },
336
+ {
337
+ name: 'ArithmeticReplacement',
338
+ pattern: /(\+|\-|\*|\/)/g,
339
+ replace: (m) => {
340
+ if (m === '+')
341
+ return '-';
342
+ if (m === '-')
343
+ return '+';
344
+ if (m === '*')
345
+ return '/';
346
+ if (m === '/')
347
+ return '*';
348
+ return m;
349
+ },
350
+ },
351
+ {
352
+ name: 'LogicalReplacement',
353
+ pattern: /(\&\&|\|\|)/g,
354
+ replace: (m) => (m === '&&' ? '||' : '&&'),
355
+ },
356
+ {
357
+ name: 'BooleanLiteralInversion',
358
+ pattern: /\b(true|false)\b/g,
359
+ replace: (m) => (m === 'true' ? 'false' : 'true'),
360
+ },
361
+ ];
362
+ /**
363
+ * Generates mutants scoped specifically to modified lines of a source file.
364
+ */
365
+ export function generateDiffScopedMutants(filePath, content, modifiedLines) {
366
+ const mutants = [];
367
+ const lines = content.split('\n');
368
+ const lineNumbers = modifiedLines && modifiedLines.length > 0
369
+ ? modifiedLines
370
+ : lines.map((_, i) => i + 1);
371
+ let idCounter = 1;
372
+ for (const lineNum of lineNumbers) {
373
+ if (lineNum < 1 || lineNum > lines.length)
374
+ continue;
375
+ const origLine = lines[lineNum - 1];
376
+ // Skip empty lines, comments, or imports/exports
377
+ const trimmed = origLine.trim();
378
+ if (!trimmed ||
379
+ trimmed.startsWith('//') ||
380
+ trimmed.startsWith('/*') ||
381
+ trimmed.startsWith('*') ||
382
+ trimmed.startsWith('import ') ||
383
+ trimmed.startsWith('export ') ||
384
+ trimmed.startsWith('#')) {
385
+ continue;
386
+ }
387
+ for (const op of MUTATION_OPERATORS) {
388
+ op.pattern.lastIndex = 0;
389
+ let match;
390
+ while ((match = op.pattern.exec(origLine)) !== null) {
391
+ const start = match.index;
392
+ const matchedText = match[0];
393
+ const replacement = op.replace(matchedText);
394
+ if (replacement === matchedText)
395
+ continue;
396
+ const mutatedLine = origLine.substring(0, start) + replacement + origLine.substring(start + matchedText.length);
397
+ mutants.push({
398
+ id: `mutant-${idCounter++}`,
399
+ filePath,
400
+ line: lineNum,
401
+ originalCode: origLine,
402
+ mutatedCode: mutatedLine,
403
+ operator: op.name,
404
+ });
405
+ }
406
+ }
407
+ }
408
+ return mutants;
409
+ }
410
+ /**
411
+ * Runs diff-scoped mutation testing on modified files within the workspace.
412
+ * Mutates target lines in-memory/temp copy, executes workspace tests, and ensures mutants are killed.
413
+ */
414
+ export async function runDiffScopedMutationTesting(workspaceRoot, filePaths, modifiedLineMap, abortSignal, options) {
415
+ const maxPerFile = options?.maxMutantsPerFile ?? 10;
416
+ const allMutants = [];
417
+ let totalKilled = 0;
418
+ for (const relPath of filePaths) {
419
+ if (abortSignal?.aborted)
420
+ break;
421
+ const fullPath = path.resolve(workspaceRoot, relPath);
422
+ let originalContent;
423
+ try {
424
+ originalContent = await fs.readFile(fullPath, 'utf-8');
425
+ }
426
+ catch {
427
+ continue;
428
+ }
429
+ const modifiedLines = modifiedLineMap ? modifiedLineMap[relPath] : undefined;
430
+ const mutants = generateDiffScopedMutants(relPath, originalContent, modifiedLines).slice(0, maxPerFile);
431
+ const lines = originalContent.split('\n');
432
+ for (const mutant of mutants) {
433
+ if (abortSignal?.aborted)
434
+ break;
435
+ // Create mutated file content
436
+ const mutatedLines = [...lines];
437
+ mutatedLines[mutant.line - 1] = mutant.mutatedCode;
438
+ const mutatedContent = mutatedLines.join('\n');
439
+ let killed = false;
440
+ let output = '';
441
+ try {
442
+ // Safely write mutated content with try...finally cleanup
443
+ await fs.writeFile(fullPath, mutatedContent, 'utf-8');
444
+ // Execute verification/test command
445
+ const testResult = await runVerification(workspaceRoot, abortSignal);
446
+ if (testResult && !testResult.success) {
447
+ killed = true;
448
+ output = testResult.errors.join('\n').substring(0, 500);
449
+ }
450
+ else if (!testResult) {
451
+ // No test runner detected; default to non-kill unless error occurs
452
+ killed = false;
453
+ }
454
+ }
455
+ catch (err) {
456
+ killed = true;
457
+ output = err.message || String(err);
458
+ }
459
+ finally {
460
+ // Restore original file unconditionally
461
+ try {
462
+ await fs.writeFile(fullPath, originalContent, 'utf-8');
463
+ }
464
+ catch (restoreErr) {
465
+ debugLog(`Failed to restore ${fullPath} after mutation testing: ${restoreErr}`);
466
+ }
467
+ }
468
+ if (killed)
469
+ totalKilled++;
470
+ allMutants.push({ ...mutant, killed, output });
471
+ }
472
+ }
473
+ const total = allMutants.length;
474
+ const score = total > 0 ? Math.round((totalKilled / total) * 100) : 100;
475
+ return {
476
+ totalMutants: total,
477
+ killedMutants: totalKilled,
478
+ survivedMutants: total - totalKilled,
479
+ mutationScore: score,
480
+ mutants: allMutants,
481
+ };
482
+ }
483
+ /**
484
+ * Detects whether property-based testing framework configuration or dependencies exist in workspace.
485
+ */
486
+ export async function detectPropertyBasedTests(workspaceRoot) {
487
+ // Check package.json for fast-check or jsverify
488
+ try {
489
+ const pkgStr = await fs.readFile(path.join(workspaceRoot, 'package.json'), 'utf-8');
490
+ if (pkgStr.includes('fast-check'))
491
+ return { hasPbt: true, framework: 'fast-check' };
492
+ if (pkgStr.includes('jsverify'))
493
+ return { hasPbt: true, framework: 'jsverify' };
494
+ }
495
+ catch { }
496
+ // Check Python environment for hypothesis
497
+ try {
498
+ const reqStr = await fs.readFile(path.join(workspaceRoot, 'requirements.txt'), 'utf-8');
499
+ if (reqStr.includes('hypothesis'))
500
+ return { hasPbt: true, framework: 'hypothesis' };
501
+ }
502
+ catch { }
503
+ try {
504
+ const pyprojStr = await fs.readFile(path.join(workspaceRoot, 'pyproject.toml'), 'utf-8');
505
+ if (pyprojStr.includes('hypothesis'))
506
+ return { hasPbt: true, framework: 'hypothesis' };
507
+ }
508
+ catch { }
509
+ // Check Cargo.toml for proptest / quickcheck
510
+ try {
511
+ const cargoStr = await fs.readFile(path.join(workspaceRoot, 'Cargo.toml'), 'utf-8');
512
+ if (cargoStr.includes('proptest'))
513
+ return { hasPbt: true, framework: 'proptest' };
514
+ if (cargoStr.includes('quickcheck'))
515
+ return { hasPbt: true, framework: 'quickcheck' };
516
+ }
517
+ catch { }
518
+ // Check go.mod for rapid
519
+ try {
520
+ const goModStr = await fs.readFile(path.join(workspaceRoot, 'go.mod'), 'utf-8');
521
+ if (goModStr.includes('pgregory.net/rapid'))
522
+ return { hasPbt: true, framework: 'rapid' };
523
+ }
524
+ catch { }
525
+ return { hasPbt: false };
526
+ }
527
+ /**
528
+ * Executes workspace property-based tests if detected.
529
+ */
530
+ export async function runPropertyBasedVerification(workspaceRoot, abortSignal) {
531
+ const pbtInfo = await detectPropertyBasedTests(workspaceRoot);
532
+ if (!pbtInfo.hasPbt)
533
+ return null;
534
+ debugLog(`Detected property-based testing framework: ${pbtInfo.framework}`);
535
+ return runVerification(workspaceRoot, abortSignal);
536
+ }
@@ -11,6 +11,18 @@ export interface EphemeralScriptOptions {
11
11
  /** AbortSignal to cancel execution. */
12
12
  abortSignal?: AbortSignal;
13
13
  }
14
+ export interface PropertyTestConfig {
15
+ numRuns?: number;
16
+ seed?: number;
17
+ shrinkTimeoutMs?: number;
18
+ }
19
+ export interface PropertyTestResult extends EphemeralScriptResult {
20
+ passed: boolean;
21
+ counterexample?: string;
22
+ shrunkInput?: string;
23
+ seed?: number;
24
+ numRunsCompleted?: number;
25
+ }
14
26
  /**
15
27
  * Normalizes user/AI provided language string to a standard runtime identifier.
16
28
  */
@@ -50,3 +62,76 @@ export declare function runEphemeralScript(workspaceRoot: string, language: stri
50
62
  * @param options - Execution options (timeoutMs, maxOutputChars, abortSignal).
51
63
  */
52
64
  export declare function runDebugScript(workspaceRoot: string, code: string, language?: string | EphemeralScriptOptions, options?: EphemeralScriptOptions): Promise<EphemeralScriptResult>;
65
+ /**
66
+ * Generates an ephemeral Property-Based Testing script template tailored to the target language and properties.
67
+ */
68
+ export declare function generatePBTScriptTemplate(language: string, targetFunctionOrFeature: string, properties: string[]): string;
69
+ /**
70
+ * Runs an ephemeral Property-Based Test script and extracts failure counterexamples and shrink results.
71
+ */
72
+ export declare function runPropertyBasedTest(workspaceRoot: string, code: string, language?: string | EphemeralScriptOptions, config?: PropertyTestConfig & EphemeralScriptOptions): Promise<PropertyTestResult>;
73
+ export interface FuzzProbeConfig {
74
+ iterations?: number;
75
+ maxInputLength?: number;
76
+ seed?: number;
77
+ timeoutMs?: number;
78
+ }
79
+ export interface FuzzProbeResult extends EphemeralScriptResult {
80
+ passed: boolean;
81
+ totalFuzzRuns: number;
82
+ failingInputs?: string[];
83
+ errorSummary?: string;
84
+ seed?: number;
85
+ }
86
+ export interface HeapDeltaConfig {
87
+ warmupIterations?: number;
88
+ testIterations?: number;
89
+ samplingIntervalMs?: number;
90
+ }
91
+ export interface HeapDeltaResult extends EphemeralScriptResult {
92
+ initialHeapBytes: number;
93
+ finalHeapBytes: number;
94
+ deltaBytes: number;
95
+ leakDetected: boolean;
96
+ growthRateBytesPerIter?: number;
97
+ samples?: Array<{
98
+ iteration: number;
99
+ heapBytes: number;
100
+ }>;
101
+ }
102
+ export interface BehavioralDriftConfig {
103
+ tolerance?: number;
104
+ compareKeys?: string[];
105
+ }
106
+ export interface BehavioralDriftResult extends EphemeralScriptResult {
107
+ hasDrift: boolean;
108
+ baselineOutput?: string;
109
+ candidateOutput?: string;
110
+ diffSummary?: string;
111
+ driftDetails?: Array<{
112
+ field?: string;
113
+ baseline: any;
114
+ candidate: any;
115
+ diff?: string;
116
+ }>;
117
+ }
118
+ /**
119
+ * Generates an ephemeral Fuzz Probe script template tailored to the target language.
120
+ */
121
+ export declare function generateFuzzScriptTemplate(language: string, targetFeature: string, config?: FuzzProbeConfig): string;
122
+ /**
123
+ * Runs a fuzz probing analysis script and parses failure counterexamples and crash statistics.
124
+ */
125
+ export declare function runFuzzProbe(workspaceRoot: string, code: string, language?: string | EphemeralScriptOptions, config?: FuzzProbeConfig & EphemeralScriptOptions): Promise<FuzzProbeResult>;
126
+ /**
127
+ * Generates an ephemeral memory heap analysis script template tailored to the target language.
128
+ */
129
+ export declare function generateHeapCheckScriptTemplate(language: string, targetCode: string, config?: HeapDeltaConfig): string;
130
+ /**
131
+ * Runs a memory heap inspection script and extracts heap growth, leak indicators, and memory delta statistics.
132
+ */
133
+ export declare function checkHeapDelta(workspaceRoot: string, code: string, language?: string | EphemeralScriptOptions, config?: HeapDeltaConfig & EphemeralScriptOptions): Promise<HeapDeltaResult>;
134
+ /**
135
+ * Executes baseline and candidate scripts, comparing outputs and return values to detect behavioral drift.
136
+ */
137
+ export declare function checkBehavioralDrift(workspaceRoot: string, baselineCode: string, candidateCode: string, language?: string | EphemeralScriptOptions, config?: BehavioralDriftConfig & EphemeralScriptOptions): Promise<BehavioralDriftResult>;