@deneb-ui/cli 2.0.88 → 2.0.89

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 (54) hide show
  1. package/bin/index.js +31 -5
  2. package/package.json +2 -2
  3. package/src/arc/__tests__/acceptance-gates.test.cjs +62 -0
  4. package/src/arc/__tests__/arc.test.cjs +922 -0
  5. package/src/arc/__tests__/canonical.test.cjs +57 -0
  6. package/src/arc/__tests__/classification.test.cjs +54 -0
  7. package/src/arc/__tests__/corpus.test.cjs +144 -0
  8. package/src/arc/__tests__/explain-blocked.test.cjs +192 -0
  9. package/src/arc/__tests__/fuzz.test.cjs +106 -0
  10. package/src/arc/__tests__/interaction.test.cjs +85 -0
  11. package/src/arc/__tests__/visual-regression.test.cjs +99 -0
  12. package/src/arc/acceptance-gates.cjs +119 -0
  13. package/src/arc/canonical-paths.cjs +142 -23
  14. package/src/arc/corpus-verifier.cjs +295 -0
  15. package/src/arc/data-classification.cjs +182 -0
  16. package/src/arc/data-flow.cjs +62 -2
  17. package/src/arc/explain-blocked.cjs +388 -0
  18. package/src/arc/fuzz-engine.cjs +604 -0
  19. package/src/arc/index.cjs +148 -2
  20. package/src/arc/interaction-verifier.cjs +185 -0
  21. package/src/arc/ir-builder.cjs +170 -12
  22. package/src/arc/planner.cjs +16 -1
  23. package/src/arc/residual.cjs +22 -0
  24. package/src/arc/rsc-boundary.cjs +105 -4
  25. package/src/arc/runtime-validator.cjs +65 -0
  26. package/src/arc/semantic.cjs +12 -1
  27. package/src/arc/transformer.cjs +14 -2745
  28. package/src/arc/transforms/assets/tailwind-bg.cjs +38 -0
  29. package/src/arc/transforms/collections/list.cjs +474 -0
  30. package/src/arc/transforms/components/child-card.cjs +268 -0
  31. package/src/arc/transforms/components/compound-content.cjs +49 -0
  32. package/src/arc/transforms/components/reusable-component.cjs +173 -0
  33. package/src/arc/transforms/element-transform.cjs +192 -0
  34. package/src/arc/transforms/healing/conditionals.cjs +295 -0
  35. package/src/arc/transforms/healing/sanitizer.cjs +401 -0
  36. package/src/arc/transforms/index.cjs +195 -0
  37. package/src/arc/transforms/primitives/action.cjs +76 -0
  38. package/src/arc/transforms/primitives/text.cjs +65 -0
  39. package/src/arc/transforms/routing/page-key.cjs +84 -0
  40. package/src/arc/transforms/routing/shared-layout.cjs +294 -0
  41. package/src/arc/transforms/runtime/provider.cjs +230 -0
  42. package/src/arc/transforms/transform-context.cjs +118 -0
  43. package/src/arc/types/acceptance.d.ts +50 -0
  44. package/src/arc/types/canonical.d.ts +50 -0
  45. package/src/arc/types/classification.d.ts +42 -0
  46. package/src/arc/types/corpus.d.ts +52 -0
  47. package/src/arc/types/explain-blocked.d.ts +41 -0
  48. package/src/arc/types/fuzz.d.ts +74 -0
  49. package/src/arc/types/interaction.d.ts +43 -0
  50. package/src/arc/types/ir.d.ts +114 -0
  51. package/src/arc/types/rsc.d.ts +39 -0
  52. package/src/arc/types/visual-regression.d.ts +49 -0
  53. package/src/arc/visual-regression.cjs +195 -0
  54. package/src/arc/workspace.cjs +233 -0
@@ -0,0 +1,604 @@
1
+ 'use strict';
2
+
3
+ const recast = require('recast');
4
+ const { parseSource, printSource, t, b, visit } = require('./ast.cjs');
5
+ const { analyzeFile } = require('./semantic.cjs');
6
+ const { planTransformations } = require('./planner.cjs');
7
+ const { applyFilePlan } = require('./transformer.cjs');
8
+
9
+ const MUTATION_TYPES = [
10
+ 'PROP_RENAME',
11
+ 'WRAP_FRAGMENT',
12
+ 'WRAP_DIV',
13
+ 'SPREAD_PROPS',
14
+ 'CONDITIONAL_TERNARY',
15
+ 'CONDITIONAL_LOGICAL',
16
+ 'INJECT_OPTIONAL_CHAIN',
17
+ 'NEST_MEMBER_COLLECTION',
18
+ 'CORRUPT_SYNTAX',
19
+ ];
20
+
21
+ const DEFAULT_SAMPLE_COMPONENT = `
22
+ export function HeroSection({ title, subtitle, items = [] }) {
23
+ return (
24
+ <section className="hero-container flex flex-col p-8 bg-white">
25
+ <h1 className="text-4xl font-bold">{title || "Discover Our Coffee"}</h1>
26
+ <p className="text-lg text-gray-600">{subtitle || "Roasted to perfection every morning"}</p>
27
+ <button className="btn btn-primary" onClick={() => console.log('click')}>
28
+ Order Now
29
+ </button>
30
+ <div className="product-list grid grid-cols-3 gap-4">
31
+ {items.map((item, index) => (
32
+ <div key={item.id || index} className="card p-4">
33
+ <h3 className="font-semibold">{item.name}</h3>
34
+ <span className="price">{item.price}</span>
35
+ </div>
36
+ ))}
37
+ </div>
38
+ </section>
39
+ );
40
+ }
41
+ `;
42
+
43
+ function mutatePropRename(ast, options = {}) {
44
+ let applied = false;
45
+ let desc = 'No editable prop found to rename';
46
+ visit(ast, {
47
+ visitJSXAttribute(pathNode) {
48
+ if (applied) return false;
49
+ const node = pathNode.node;
50
+ const name = node.name && node.name.name;
51
+ const target = options.propName || (['title', 'label', 'heading', 'subtitle', 'description', 'price', 'name'].includes(name) ? name : null);
52
+ if (target && name === target) {
53
+ const newName = options.newPropName || `fuzzed_${name}`;
54
+ node.name.name = newName;
55
+ applied = true;
56
+ desc = `Renamed prop '${name}' to '${newName}'`;
57
+ return false;
58
+ }
59
+ this.traverse(pathNode);
60
+ },
61
+ });
62
+
63
+ if (!applied) {
64
+ // If no attribute matched, find identifier in props parameter
65
+ visit(ast, {
66
+ visitFunction(pathNode) {
67
+ if (applied) return false;
68
+ const params = pathNode.node.params;
69
+ if (params && params[0] && params[0].type === 'ObjectPattern') {
70
+ for (const prop of params[0].properties) {
71
+ if (prop.key && prop.key.name === 'title') {
72
+ prop.key.name = 'fuzzed_title';
73
+ if (prop.value && prop.value.name === 'title') prop.value.name = 'fuzzed_title';
74
+ applied = true;
75
+ desc = "Renamed destructured prop 'title' to 'fuzzed_title'";
76
+ return false;
77
+ }
78
+ }
79
+ }
80
+ this.traverse(pathNode);
81
+ },
82
+ });
83
+ }
84
+
85
+ return { applied, desc };
86
+ }
87
+
88
+ function mutateWrapFragment(ast) {
89
+ let applied = false;
90
+ let desc = 'No JSX return statement found to wrap in Fragment';
91
+ visit(ast, {
92
+ visitReturnStatement(pathNode) {
93
+ if (applied) return false;
94
+ const arg = pathNode.node.argument;
95
+ if (arg && (arg.type === 'JSXElement' || arg.type === 'JSXFragment')) {
96
+ pathNode.node.argument = b.jsxFragment(
97
+ b.jsxOpeningFragment(),
98
+ b.jsxClosingFragment(),
99
+ [arg]
100
+ );
101
+ applied = true;
102
+ desc = 'Wrapped root JSX return argument inside <React.Fragment>';
103
+ return false;
104
+ }
105
+ this.traverse(pathNode);
106
+ },
107
+ });
108
+ return { applied, desc };
109
+ }
110
+
111
+ function mutateWrapDiv(ast) {
112
+ let applied = false;
113
+ let desc = 'No JSX return statement found to wrap in <div>';
114
+ visit(ast, {
115
+ visitReturnStatement(pathNode) {
116
+ if (applied) return false;
117
+ const arg = pathNode.node.argument;
118
+ if (arg && (arg.type === 'JSXElement' || arg.type === 'JSXFragment')) {
119
+ const opening = b.jsxOpeningElement(b.jsxIdentifier('div'), [
120
+ b.jsxAttribute(b.jsxIdentifier('className'), b.stringLiteral('fuzzed-container-wrapper')),
121
+ ]);
122
+ const closing = b.jsxClosingElement(b.jsxIdentifier('div'));
123
+ pathNode.node.argument = b.jsxElement(opening, closing, [arg]);
124
+ applied = true;
125
+ desc = 'Wrapped root JSX element in <div className="fuzzed-container-wrapper">';
126
+ return false;
127
+ }
128
+ this.traverse(pathNode);
129
+ },
130
+ });
131
+ return { applied, desc };
132
+ }
133
+
134
+ function mutateSpreadProps(ast, options = {}) {
135
+ let applied = false;
136
+ let desc = 'No JSX element found to inject spread attribute';
137
+ const spreadIdent = options.spreadName || 'fuzzedProps';
138
+ visit(ast, {
139
+ visitJSXOpeningElement(pathNode) {
140
+ if (applied) return false;
141
+ const node = pathNode.node;
142
+ node.attributes.push(b.jsxSpreadAttribute(b.jsxIdentifier(spreadIdent)));
143
+ applied = true;
144
+ desc = `Injected spread attribute {...${spreadIdent}} on <${node.name.name || 'element'}>`;
145
+ return false;
146
+ },
147
+ });
148
+ return { applied, desc };
149
+ }
150
+
151
+ function mutateConditionalTernary(ast) {
152
+ let applied = false;
153
+ let desc = 'No JSX child element found to wrap in ternary expression';
154
+ visit(ast, {
155
+ visitJSXElement(pathNode) {
156
+ if (applied) return false;
157
+ const node = pathNode.node;
158
+ if (node.children && node.children.length > 0) {
159
+ for (let i = 0; i < node.children.length; i++) {
160
+ const child = node.children[i];
161
+ if (child.type === 'JSXElement') {
162
+ const fallback = b.jsxElement(
163
+ b.jsxOpeningElement(b.jsxIdentifier('div'), [
164
+ b.jsxAttribute(b.jsxIdentifier('className'), b.stringLiteral('fuzzed-fallback')),
165
+ ]),
166
+ b.jsxClosingElement(b.jsxIdentifier('div')),
167
+ [b.jsxText('Fallback State')]
168
+ );
169
+ const ternary = b.jsxExpressionContainer(
170
+ b.conditionalExpression(
171
+ b.identifier('isFuzzedConditionActive'),
172
+ child,
173
+ fallback
174
+ )
175
+ );
176
+ node.children[i] = ternary;
177
+ applied = true;
178
+ desc = `Wrapped <${child.openingElement?.name?.name || 'element'}> in ternary conditional branch`;
179
+ return false;
180
+ }
181
+ }
182
+ }
183
+ this.traverse(pathNode);
184
+ },
185
+ });
186
+ return { applied, desc };
187
+ }
188
+
189
+ function mutateConditionalLogical(ast) {
190
+ let applied = false;
191
+ let desc = 'No JSX child element found to wrap in logical AND expression';
192
+ visit(ast, {
193
+ visitJSXElement(pathNode) {
194
+ if (applied) return false;
195
+ const node = pathNode.node;
196
+ if (node.children && node.children.length > 0) {
197
+ for (let i = 0; i < node.children.length; i++) {
198
+ const child = node.children[i];
199
+ if (child.type === 'JSXElement') {
200
+ const logical = b.jsxExpressionContainer(
201
+ b.logicalExpression(
202
+ '&&',
203
+ b.identifier('showFuzzedContent'),
204
+ child
205
+ )
206
+ );
207
+ node.children[i] = logical;
208
+ applied = true;
209
+ desc = `Wrapped <${child.openingElement?.name?.name || 'element'}> in logical AND (&&) expression`;
210
+ return false;
211
+ }
212
+ }
213
+ }
214
+ this.traverse(pathNode);
215
+ },
216
+ });
217
+ return { applied, desc };
218
+ }
219
+
220
+ function mutateOptionalChain(ast) {
221
+ let applied = false;
222
+ let desc = 'Injected optional chaining expression';
223
+ visit(ast, {
224
+ visitJSXElement(pathNode) {
225
+ if (applied) return false;
226
+ const node = pathNode.node;
227
+ const optionalExpr = b.jsxExpressionContainer(
228
+ b.optionalMemberExpression(
229
+ b.optionalMemberExpression(
230
+ b.identifier('fuzzedConfig'),
231
+ b.identifier('metadata'),
232
+ false,
233
+ true
234
+ ),
235
+ b.identifier('tagline'),
236
+ false,
237
+ true
238
+ )
239
+ );
240
+ node.children.push(optionalExpr);
241
+ applied = true;
242
+ desc = 'Injected optional chaining expression {fuzzedConfig?.metadata?.tagline}';
243
+ return false;
244
+ },
245
+ });
246
+ return { applied, desc };
247
+ }
248
+
249
+ function mutateNestCollection(ast) {
250
+ let applied = false;
251
+ let desc = 'No collection map found to nest';
252
+ visit(ast, {
253
+ visitCallExpression(pathNode) {
254
+ if (applied) return false;
255
+ const node = pathNode.node;
256
+ if (
257
+ node.callee &&
258
+ node.callee.type === 'MemberExpression' &&
259
+ node.callee.property &&
260
+ node.callee.property.name === 'map'
261
+ ) {
262
+ const callback = node.arguments[0];
263
+ if (callback && (callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')) {
264
+ const body = callback.body;
265
+ if (body && body.type === 'JSXElement') {
266
+ const nestedMap = b.jsxExpressionContainer(
267
+ b.callExpression(
268
+ b.memberExpression(
269
+ b.memberExpression(b.identifier('item'), b.identifier('tags')),
270
+ b.identifier('map')
271
+ ),
272
+ [
273
+ b.arrowFunctionExpression(
274
+ [b.identifier('tag'), b.identifier('tagIdx')],
275
+ b.jsxElement(
276
+ b.jsxOpeningElement(b.jsxIdentifier('span'), [
277
+ b.jsxAttribute(b.jsxIdentifier('key'), b.jsxExpressionContainer(b.identifier('tagIdx'))),
278
+ b.jsxAttribute(b.jsxIdentifier('className'), b.stringLiteral('badge')),
279
+ ]),
280
+ b.jsxClosingElement(b.jsxIdentifier('span')),
281
+ [b.jsxExpressionContainer(b.identifier('tag'))]
282
+ )
283
+ ),
284
+ ]
285
+ )
286
+ );
287
+ body.children.push(nestedMap);
288
+ applied = true;
289
+ desc = 'Injected nested collection mapping item.tags.map(tag => ...)';
290
+ return false;
291
+ }
292
+ }
293
+ }
294
+ this.traverse(pathNode);
295
+ },
296
+ });
297
+ return { applied, desc };
298
+ }
299
+
300
+ function mutateCorruptSyntax(code) {
301
+ const mutated = code.replace(/<([A-Za-z0-9_]+)/, '<$1 <<<< MALFORMED_SYNTAX >>>>');
302
+ return {
303
+ applied: true,
304
+ desc: 'Injected malformed JSX syntax to verify clean parser rejection without crash',
305
+ mutatedCode: mutated !== code ? mutated : code + '\n<<<< UNCLOSED_TOKEN',
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Applies a single mutation to React component source code.
311
+ */
312
+ function applyMutation(code, mutationType, options = {}) {
313
+ if (mutationType === 'CORRUPT_SYNTAX') {
314
+ const corrupt = mutateCorruptSyntax(code);
315
+ return {
316
+ mutationType,
317
+ description: corrupt.desc,
318
+ applied: corrupt.applied,
319
+ originalCode: code,
320
+ mutatedCode: corrupt.mutatedCode,
321
+ };
322
+ }
323
+
324
+ let ast;
325
+ try {
326
+ ast = parseSource(code, options.filePath || 'component.tsx');
327
+ } catch (err) {
328
+ return {
329
+ mutationType,
330
+ description: `Failed to parse original code: ${err.message}`,
331
+ applied: false,
332
+ originalCode: code,
333
+ mutatedCode: code,
334
+ };
335
+ }
336
+
337
+ let mutationOutcome;
338
+ switch (mutationType) {
339
+ case 'PROP_RENAME':
340
+ mutationOutcome = mutatePropRename(ast, options);
341
+ break;
342
+ case 'WRAP_FRAGMENT':
343
+ mutationOutcome = mutateWrapFragment(ast);
344
+ break;
345
+ case 'WRAP_DIV':
346
+ mutationOutcome = mutateWrapDiv(ast);
347
+ break;
348
+ case 'SPREAD_PROPS':
349
+ mutationOutcome = mutateSpreadProps(ast, options);
350
+ break;
351
+ case 'CONDITIONAL_TERNARY':
352
+ mutationOutcome = mutateConditionalTernary(ast);
353
+ break;
354
+ case 'CONDITIONAL_LOGICAL':
355
+ mutationOutcome = mutateConditionalLogical(ast);
356
+ break;
357
+ case 'INJECT_OPTIONAL_CHAIN':
358
+ mutationOutcome = mutateOptionalChain(ast);
359
+ break;
360
+ case 'NEST_MEMBER_COLLECTION':
361
+ mutationOutcome = mutateNestCollection(ast);
362
+ break;
363
+ default:
364
+ return {
365
+ mutationType,
366
+ description: `Unknown mutation type: ${mutationType}`,
367
+ applied: false,
368
+ originalCode: code,
369
+ mutatedCode: code,
370
+ };
371
+ }
372
+
373
+ const mutatedCode = mutationOutcome.applied ? printSource(ast, code) : code;
374
+ return {
375
+ mutationType,
376
+ description: mutationOutcome.desc,
377
+ applied: mutationOutcome.applied,
378
+ originalCode: code,
379
+ mutatedCode,
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Generates a diverse suite of fuzzed variations across mutation types.
385
+ */
386
+ function generateFuzzCorpus(code, options = {}) {
387
+ const types = options.mutationTypes || MUTATION_TYPES;
388
+ const corpus = [];
389
+
390
+ for (const type of types) {
391
+ const result = applyMutation(code, type, options);
392
+ if (result.applied) {
393
+ corpus.push(result);
394
+ }
395
+ }
396
+
397
+ return corpus;
398
+ }
399
+
400
+ /**
401
+ * Tests ARC's resilience against a mutated component code string.
402
+ * Verifies syntax validity, absence of unhandled crashes, and clean recovery/rejection.
403
+ */
404
+ function testMutationResilience(mutatedCode, mutationType, options = {}) {
405
+ const filePath = options.filePath || 'src/components/Hero.tsx';
406
+ const profile = {
407
+ root: process.cwd(),
408
+ framework: 'Next.js',
409
+ routerType: 'app-router',
410
+ };
411
+
412
+ // 1. AST Parse Gate
413
+ let ast;
414
+ try {
415
+ ast = parseSource(mutatedCode, filePath);
416
+ } catch (err) {
417
+ if (mutationType === 'CORRUPT_SYNTAX') {
418
+ return {
419
+ mutationType,
420
+ description: 'Corrupt syntax cleanly caught by AST parser at boundary',
421
+ parsed: false,
422
+ analyzed: false,
423
+ transformed: false,
424
+ outputValidSyntax: false,
425
+ crashed: false,
426
+ candidatesFound: 0,
427
+ transformationsApplied: 0,
428
+ status: 'PARSER_REJECTED_CLEANLY',
429
+ resilient: true,
430
+ };
431
+ }
432
+ return {
433
+ mutationType,
434
+ description: `Unexpected parse error on mutated code: ${err.message}`,
435
+ parsed: false,
436
+ analyzed: false,
437
+ transformed: false,
438
+ outputValidSyntax: false,
439
+ crashed: false,
440
+ errorMessage: err.message,
441
+ candidatesFound: 0,
442
+ transformationsApplied: 0,
443
+ status: 'CRASHED',
444
+ resilient: false,
445
+ };
446
+ }
447
+
448
+ // 2. Semantic Analysis Gate
449
+ let analysis;
450
+ try {
451
+ analysis = analyzeFile({
452
+ code: mutatedCode,
453
+ relativeFile: filePath,
454
+ profile,
455
+ });
456
+ analysis.relativeFile = filePath;
457
+ analysis.code = mutatedCode;
458
+ } catch (err) {
459
+ return {
460
+ mutationType,
461
+ description: `Semantic analyzer crashed on AST: ${err.message}`,
462
+ parsed: true,
463
+ analyzed: false,
464
+ transformed: false,
465
+ outputValidSyntax: false,
466
+ crashed: true,
467
+ errorMessage: err.message,
468
+ candidatesFound: 0,
469
+ transformationsApplied: 0,
470
+ status: 'CRASHED',
471
+ resilient: false,
472
+ };
473
+ }
474
+
475
+ const candidatesFound = analysis.candidates ? analysis.candidates.length : 0;
476
+
477
+ // 3. Transformation Planning Gate
478
+ let planResult;
479
+ try {
480
+ planResult = planTransformations({
481
+ profile,
482
+ analyses: [analysis],
483
+ recipe: null,
484
+ });
485
+ } catch (err) {
486
+ return {
487
+ mutationType,
488
+ description: `Planner crashed on candidates: ${err.message}`,
489
+ parsed: true,
490
+ analyzed: true,
491
+ transformed: false,
492
+ outputValidSyntax: false,
493
+ crashed: true,
494
+ errorMessage: err.message,
495
+ candidatesFound,
496
+ transformationsApplied: 0,
497
+ status: 'CRASHED',
498
+ resilient: false,
499
+ };
500
+ }
501
+
502
+ const filePlan = (planResult.files && planResult.files[0]) || (planResult.filePlans && planResult.filePlans[0]) || {
503
+ originalCode: mutatedCode,
504
+ file: filePath,
505
+ transformations: [],
506
+ };
507
+ if (!filePlan.originalCode) filePlan.originalCode = mutatedCode;
508
+ if (!filePlan.file) filePlan.file = filePath;
509
+
510
+ // 4. Transform Execution Gate
511
+ let transformResult;
512
+ try {
513
+ transformResult = applyFilePlan(filePlan, profile);
514
+ } catch (err) {
515
+ return {
516
+ mutationType,
517
+ description: `Transformer crashed during AST rewrites: ${err.message}`,
518
+ parsed: true,
519
+ analyzed: true,
520
+ transformed: false,
521
+ outputValidSyntax: false,
522
+ crashed: true,
523
+ errorMessage: err.message,
524
+ candidatesFound,
525
+ transformationsApplied: 0,
526
+ status: 'CRASHED',
527
+ resilient: false,
528
+ };
529
+ }
530
+
531
+ const transformedCode = transformResult.code || mutatedCode;
532
+ const transformationsApplied = transformResult.applied || 0;
533
+
534
+ // 5. Output AST Validation Gate
535
+ let outputValidSyntax = true;
536
+ try {
537
+ parseSource(transformedCode, filePath);
538
+ } catch {
539
+ outputValidSyntax = false;
540
+ }
541
+
542
+ const status = !outputValidSyntax
543
+ ? 'CRASHED'
544
+ : transformationsApplied > 0
545
+ ? 'ADAPTED_CLEANLY'
546
+ : 'SKIPPED_SAFELY';
547
+
548
+ const resilient = outputValidSyntax && status !== 'CRASHED';
549
+
550
+ return {
551
+ mutationType,
552
+ description: `Fuzz test completed: ${status} (${transformationsApplied} transforms applied)`,
553
+ parsed: true,
554
+ analyzed: true,
555
+ transformed: transformResult.changed || false,
556
+ outputValidSyntax,
557
+ crashed: false,
558
+ candidatesFound,
559
+ transformationsApplied,
560
+ status,
561
+ resilient,
562
+ };
563
+ }
564
+
565
+ /**
566
+ * Executes the complete fuzz harness across all mutation types and computes resilience metrics.
567
+ */
568
+ function runFuzzHarness(componentCode = DEFAULT_SAMPLE_COMPONENT, options = {}) {
569
+ const corpus = generateFuzzCorpus(componentCode, options);
570
+ const results = [];
571
+
572
+ for (const mutation of corpus) {
573
+ const outcome = testMutationResilience(mutation.mutatedCode, mutation.mutationType, options);
574
+ results.push(outcome);
575
+ }
576
+
577
+ const totalMutations = results.length;
578
+ const resilientCount = results.filter((r) => r.resilient).length;
579
+ const crashedCount = results.filter((r) => r.crashed).length;
580
+ const syntaxErrorsInOutput = results.filter((r) => !r.outputValidSyntax && r.parsed).length;
581
+ const resilienceScore = totalMutations > 0
582
+ ? Number(((resilientCount / totalMutations) * 100).toFixed(1))
583
+ : 100.0;
584
+
585
+ return {
586
+ timestamp: new Date().toISOString(),
587
+ totalMutations,
588
+ resilientCount,
589
+ crashedCount,
590
+ syntaxErrorsInOutput,
591
+ resilienceScore,
592
+ allResilient: resilientCount === totalMutations && crashedCount === 0,
593
+ results,
594
+ };
595
+ }
596
+
597
+ module.exports = {
598
+ MUTATION_TYPES,
599
+ DEFAULT_SAMPLE_COMPONENT,
600
+ applyMutation,
601
+ generateFuzzCorpus,
602
+ testMutationResilience,
603
+ runFuzzHarness,
604
+ };