@deneb-ui/cli 2.0.87 → 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 (55) 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
  55. package/src/tools/deneb-template-validator.cjs +17 -17
@@ -2959,6 +2959,928 @@ test('ARC v2 Phase 6: runTransactionalPipeline executes atomic rollback on criti
2959
2959
  fs.rmSync(tmpDir, { recursive: true, force: true });
2960
2960
  });
2961
2961
 
2962
+ test('ARC v3 Phase 7: createRunWorkspace creates isolated run structure and commits files cleanly', () => {
2963
+ const fs = require('fs');
2964
+ const path = require('path');
2965
+ const os = require('os');
2966
+ const {
2967
+ createRunWorkspace,
2968
+ copyProjectToWorkspace,
2969
+ commitWorkspaceToProject,
2970
+ cleanupWorkspace,
2971
+ saveRunArtifacts,
2972
+ formatFailureExplanation,
2973
+ } = require('../workspace.cjs');
2974
+
2975
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deneb-workspace-test-'));
2976
+ fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
2977
+ fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'ws-test' }), 'utf8');
2978
+ fs.writeFileSync(path.join(tmpDir, 'src', 'App.tsx'), 'export default function App() {}', 'utf8');
2979
+
2980
+ const runId = 'arc-test-phase7';
2981
+ const runDirs = createRunWorkspace(tmpDir, runId);
2982
+ assert.ok(fs.existsSync(runDirs.workspaceDir), 'workspaceDir should exist');
2983
+ assert.ok(fs.existsSync(runDirs.snapshotDir), 'snapshotDir should exist');
2984
+ assert.ok(fs.existsSync(runDirs.reportsDir), 'reportsDir should exist');
2985
+
2986
+ // Copy project to workspace
2987
+ copyProjectToWorkspace(tmpDir, runDirs.workspaceDir, runDirs.snapshotDir);
2988
+ assert.ok(fs.existsSync(path.join(runDirs.workspaceDir, 'src', 'App.tsx')), 'App.tsx copied to workspace');
2989
+ assert.ok(fs.existsSync(path.join(runDirs.snapshotDir, 'src', 'App.tsx')), 'App.tsx snapshotted');
2990
+
2991
+ // Modify in workspace
2992
+ fs.writeFileSync(path.join(runDirs.workspaceDir, 'src', 'App.tsx'), 'export default function App() { return <h1>Updated</h1>; }', 'utf8');
2993
+ // Original is unchanged before commit
2994
+ assert.equal(fs.readFileSync(path.join(tmpDir, 'src', 'App.tsx'), 'utf8'), 'export default function App() {}');
2995
+
2996
+ // Save artifacts
2997
+ saveRunArtifacts(runDirs, {
2998
+ report: { outcome: 'success', engine: 'ARC v3' },
2999
+ runtimeResults: { passed: true, score: 100 },
3000
+ });
3001
+ assert.ok(fs.existsSync(path.join(runDirs.reportsDir, 'conversion-report.json')));
3002
+
3003
+ // Commit to project
3004
+ const committed = commitWorkspaceToProject(runDirs.workspaceDir, tmpDir, ['src/App.tsx']);
3005
+ assert.ok(committed.includes('src/App.tsx'));
3006
+ assert.equal(fs.readFileSync(path.join(tmpDir, 'src', 'App.tsx'), 'utf8'), 'export default function App() { return <h1>Updated</h1>; }');
3007
+
3008
+ // Format failure explanation
3009
+ const explanation = formatFailureExplanation({ fivoraContractErrors: ['Missing key'], uncoveredVisibleText: 2 }, ['Contract failed']);
3010
+ assert.ok(explanation.includes('DENEB CONVERSION BLOCKED'));
3011
+ assert.ok(explanation.includes('Contract failed'));
3012
+
3013
+ cleanupWorkspace(runDirs.workspaceDir);
3014
+ assert.ok(!fs.existsSync(runDirs.workspaceDir), 'workspaceDir cleaned up');
3015
+
3016
+ fs.rmSync(tmpDir, { recursive: true, force: true });
3017
+ });
3018
+
3019
+ test('ARC v3 Phase 7: blocking Fivora contract enforcement prevents commit when contract checks fail', () => {
3020
+ const fs = require('fs');
3021
+ const path = require('path');
3022
+ const os = require('os');
3023
+ const { runTransactionalPipeline } = require('../index.cjs');
3024
+
3025
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deneb-blocking-contract-test-'));
3026
+ fs.mkdirSync(path.join(tmpDir, 'src', 'components'), { recursive: true });
3027
+ fs.writeFileSync(
3028
+ path.join(tmpDir, 'package.json'),
3029
+ JSON.stringify({ name: 'blocking-contract-test', dependencies: { next: '14.0.0', react: '18.2.0' } }),
3030
+ 'utf8'
3031
+ );
3032
+ // An invalid component that violates Fivora contract (uncovered text or invalid syntax)
3033
+ const initialCode = `export function Broken() { return <div>Unbound static text</div>; }`;
3034
+ const compPath = path.join(tmpDir, 'src', 'components', 'Broken.tsx');
3035
+ fs.writeFileSync(compPath, initialCode, 'utf8');
3036
+
3037
+ // Strict mode is true by default in ARC v3
3038
+ const pipelineRes = runTransactionalPipeline(tmpDir);
3039
+ assert.ok(pipelineRes !== null);
3040
+
3041
+ // If pipeline rolls back or fails contract, original file must remain untouched
3042
+ if (pipelineRes.rolledBack) {
3043
+ const afterCode = fs.readFileSync(compPath, 'utf8');
3044
+ assert.equal(afterCode, initialCode, 'Developer project file must remain untouched upon rollback');
3045
+ }
3046
+
3047
+ fs.rmSync(tmpDir, { recursive: true, force: true });
3048
+ });
3049
+
3050
+ test('ARC v3 Phase 8: modular transforms architecture exports all sub-modules with 100% facade compatibility', () => {
3051
+ const facade = require('../transformer.cjs');
3052
+ const modular = require('../transforms/index.cjs');
3053
+
3054
+ assert.equal(typeof facade.applyFilePlan, 'function');
3055
+ assert.equal(typeof facade.applyCollectionTransform, 'function');
3056
+ assert.equal(typeof facade.instrumentChildCardComponent, 'function');
3057
+ assert.equal(typeof facade.instrumentReusableComponent, 'function');
3058
+ assert.equal(typeof facade.instrumentLayoutSource, 'function');
3059
+ assert.equal(typeof facade.instrumentPageKey, 'function');
3060
+ assert.equal(typeof facade.resolveSiteDataSpecifier, 'function');
3061
+ assert.equal(typeof facade.rewriteRecursiveSiteDataContext, 'function');
3062
+
3063
+ assert.strictEqual(facade.applyFilePlan, modular.applyFilePlan);
3064
+ assert.strictEqual(facade.applyCollectionTransform, modular.applyCollectionTransform);
3065
+ assert.strictEqual(facade.instrumentChildCardComponent, modular.instrumentChildCardComponent);
3066
+ assert.strictEqual(facade.instrumentLayoutSource, modular.instrumentLayoutSource);
3067
+ });
3068
+
3069
+ test('ARC v3 Phase 8: direct sub-module transformation works for primitives, compound-content, and background assets', () => {
3070
+ const { replaceTextChildren } = require('../transforms/primitives/text.cjs');
3071
+ const { extractTailwindBg } = require('../transforms/assets/tailwind-bg.cjs');
3072
+ const { bindButtonWithIcon } = require('../transforms/components/compound-content.cjs');
3073
+ const { parseSource, printSource } = require('../ast.cjs');
3074
+
3075
+ // 1. Primitive text transform
3076
+ const textCode = `export function Heading() { return <h1>Original Title</h1>; }`;
3077
+ const textAst = parseSource(textCode);
3078
+ const h1Node = textAst.program.body[0].declaration.body.body[0].argument;
3079
+ replaceTextChildren(h1Node, 'home.heroTitle', 'Original Title', 'text');
3080
+ const updatedText = printSource(textAst, textCode);
3081
+ assert.ok(updatedText.includes('siteData.content.home.heroTitle') || updatedText.includes('siteData?.content?.home?.heroTitle') || updatedText.includes('siteData.home.heroTitle'));
3082
+
3083
+ // 2. Asset tailwind background image extraction
3084
+ const bgCode = `export function Banner() { return <div className="h-64 bg-[url('/hero.png')] text-white" />; }`;
3085
+ const bgAst = parseSource(bgCode);
3086
+ const divNode = bgAst.program.body[0].declaration.body.body[0].argument;
3087
+ extractTailwindBg(divNode, { field: 'home.heroBanner', fallback: '/hero.png' });
3088
+ const updatedBg = printSource(bgAst, bgCode);
3089
+ assert.ok(updatedBg.includes('backgroundImage: `url(${'));
3090
+ assert.ok(!updatedBg.includes("bg-[url('/hero.png')]"));
3091
+
3092
+ // 3. Compound content button with icon
3093
+ const btnCode = `export function Action() { return <button className="btn"><svg /><span>Click Me</span></button>; }`;
3094
+ const btnAst = parseSource(btnCode);
3095
+ const btnNode = btnAst.program.body[0].declaration.body.body[0].argument;
3096
+ bindButtonWithIcon(btnNode, { field: 'home.ctaLabel' });
3097
+ const updatedBtn = printSource(btnAst, btnCode);
3098
+ assert.ok(updatedBtn.includes('data-preview-field-path="home.ctaLabel"'));
3099
+ });
3100
+
3101
+ test('ARC v3 Phase 9: canonicalField engine provides single source of truth for runtime, markers, schema, and setters', () => {
3102
+ const { canonicalField, fieldRef, isValidCanonicalPath, validatePathAlignment } = require('../canonical-paths.cjs');
3103
+
3104
+ // 1. Core derivation from canonical string
3105
+ const field = canonicalField('home.hero.title', { provenance: { file: 'Hero.tsx', loc: '12:4' } });
3106
+ assert.equal(field.path, 'home.hero.title');
3107
+ assert.equal(field.scope, 'home');
3108
+ assert.equal(field.section, 'hero');
3109
+ assert.equal(field.fieldName, 'title');
3110
+ assert.equal(field.previewPath, 'home.hero.title');
3111
+ assert.equal(field.previewMarker, 'data-preview-field-path="home.hero.title"');
3112
+ assert.equal(field.runtimePath, 'siteData.content.home.hero.title');
3113
+ assert.equal(field.manifestSchemaPath, 'home.hero.title');
3114
+ assert.equal(field.diagnosticsId, 'field:home.hero.title');
3115
+ assert.equal(field.toSetterCode('newVal'), 'siteData.content.home.hero.title = newVal;');
3116
+ assert.equal(field.validate().valid, true);
3117
+
3118
+ // 2. Collection and child derivation
3119
+ const col = canonicalField('home.categories');
3120
+ const item0 = col.toCollectionItem(0);
3121
+ assert.equal(item0.path, 'home.categories[0]');
3122
+ assert.equal(item0.isCollection, true);
3123
+
3124
+ const nestedChild = item0.toCollectionChild('products', 2);
3125
+ assert.equal(nestedChild.path, 'home.categories[0].products[2]');
3126
+
3127
+ // 3. Backward compatible alias
3128
+ const aliasRef = fieldRef('about.story.heading');
3129
+ assert.equal(aliasRef.fieldName, 'heading');
3130
+ assert.equal(aliasRef.scope, 'about');
3131
+
3132
+ // 4. AST generation
3133
+ const getterAst = field.toGetterAst();
3134
+ assert.equal(getterAst.type, 'OptionalMemberExpression');
3135
+
3136
+ const bindingAst = field.toBindingAst('Fallback');
3137
+ assert.equal(bindingAst.type, 'LogicalExpression');
3138
+
3139
+ const attrAst = field.toPreviewAttrAst();
3140
+ assert.equal(attrAst.name.name, 'data-preview-field-path');
3141
+ assert.equal(attrAst.value.value, 'home.hero.title');
3142
+
3143
+ const styleAttrAst = field.toPreviewStyleTargetAst();
3144
+ assert.equal(styleAttrAst.name.name, 'data-preview-style-target');
3145
+
3146
+ // 5. Validation and alignment
3147
+ assert.ok(isValidCanonicalPath('home.hero.title'));
3148
+ assert.ok(isValidCanonicalPath('home.categories[0].products[${index}].title'));
3149
+ assert.ok(!isValidCanonicalPath(''));
3150
+ assert.ok(!isValidCanonicalPath('invalid path with spaces'));
3151
+
3152
+ assert.ok(validatePathAlignment('siteData?.content?.home?.hero?.title', 'data-preview-field-path="home.hero.title"'));
3153
+ });
3154
+
3155
+ test('ARC v3 Phase 9: formalized typed IR models and validateProjectIR verify project structure integrity', () => {
3156
+ const {
3157
+ createDenebComponent,
3158
+ createDataSource,
3159
+ createCollectionDefinition,
3160
+ validateProjectIR,
3161
+ } = require('../ir-builder.cjs');
3162
+
3163
+ // 1. Factory validation
3164
+ const comp = createDenebComponent({
3165
+ name: 'ProductCard',
3166
+ file: 'components/ProductCard.tsx',
3167
+ propsSignature: { kind: 'destructured', names: ['product'], paramName: null, typeAnnotationName: 'CardProps' },
3168
+ memberAccesses: { product: ['name', 'price', 'image'] },
3169
+ });
3170
+ assert.equal(comp.id, 'components/ProductCard.tsx:ProductCard');
3171
+ assert.equal(comp.clientBoundary, 'server');
3172
+
3173
+ const ds = createDataSource({
3174
+ name: 'featuredItems',
3175
+ file: 'data/products.json',
3176
+ items: [{ id: 1, name: 'Item 1' }],
3177
+ fieldKeys: ['id', 'name'],
3178
+ });
3179
+ assert.equal(ds.editable, true);
3180
+ assert.equal(ds.kind, 'inline-array');
3181
+
3182
+ const col = createCollectionDefinition({
3183
+ file: 'app/shop/page.tsx',
3184
+ arrayName: 'featuredItems',
3185
+ itemParam: 'item',
3186
+ rootTagName: 'ProductCard',
3187
+ childComponent: comp,
3188
+ dataSource: ds,
3189
+ });
3190
+ assert.equal(col.arrayName, 'featuredItems');
3191
+ assert.equal(col.childComponent.name, 'ProductCard');
3192
+
3193
+ // 2. Validate valid IR
3194
+ const mockIR = {
3195
+ components: new Map([['ProductCard', comp]]),
3196
+ dataSources: new Map([['featuredItems', ds]]),
3197
+ collections: [col],
3198
+ assetImports: new Map(),
3199
+ backgroundImages: [],
3200
+ componentUsages: [],
3201
+ };
3202
+
3203
+ const validation = validateProjectIR(mockIR);
3204
+ assert.equal(validation.valid, true);
3205
+ assert.equal(validation.errors.length, 0);
3206
+ assert.equal(validation.stats.componentCount, 1);
3207
+ assert.equal(validation.stats.dataSourceCount, 1);
3208
+ assert.equal(validation.stats.collectionCount, 1);
3209
+
3210
+ // 3. Catch corrupted IR
3211
+ const brokenIR = {
3212
+ components: new Map([['BrokenComp', { name: 'MismatchedName', file: '' }]]),
3213
+ dataSources: new Map([['BrokenDS', { name: 'BrokenDS', file: '' }]]),
3214
+ collections: [{ file: '' }],
3215
+ };
3216
+ const brokenValidation = validateProjectIR(brokenIR);
3217
+ assert.equal(brokenValidation.valid, false);
3218
+ assert.ok(brokenValidation.errors.length >= 3);
3219
+ });
3220
+
3221
+ test('ARC v3 Phase 10: data-classification engine accurately categorizes 7 data tiers and protects platform/runtime data', () => {
3222
+ const { DATA_CLASSIFICATION, classifyDataCandidate, isEditableClassification } = require('../data-classification.cjs');
3223
+
3224
+ // 1. CONTENT -> editable
3225
+ const contentRes = classifyDataCandidate({ tag: 'h1', kind: 'text', value: 'Special Summer Blend' });
3226
+ assert.equal(contentRes.classification, DATA_CLASSIFICATION.CONTENT);
3227
+ assert.equal(contentRes.editable, true);
3228
+ assert.equal(isEditableClassification(contentRes.classification), true);
3229
+
3230
+ // 2. COMMERCE_CONTENT -> editable
3231
+ const commerceRes = classifyDataCandidate({ tag: 'h3', kind: 'text', value: 'Arabica Dark Roast', propName: 'productTitle' });
3232
+ assert.equal(commerceRes.classification, DATA_CLASSIFICATION.COMMERCE_CONTENT);
3233
+ assert.equal(commerceRes.editable, true);
3234
+ assert.equal(isEditableClassification(commerceRes.classification), true);
3235
+
3236
+ // 3. PLATFORM_CONTROLLED -> not editable (no marker)
3237
+ const platformRes = classifyDataCandidate({ tag: 'span', kind: 'text', value: 'LKR', field: 'common.currency' });
3238
+ assert.equal(platformRes.classification, DATA_CLASSIFICATION.PLATFORM_CONTROLLED);
3239
+ assert.equal(platformRes.editable, false);
3240
+ assert.equal(isEditableClassification(platformRes.classification), false);
3241
+
3242
+ const orderIdRes = classifyDataCandidate({ tag: 'span', kind: 'text', value: '#10492', propName: 'orderId' });
3243
+ assert.equal(orderIdRes.classification, DATA_CLASSIFICATION.PLATFORM_CONTROLLED);
3244
+ assert.equal(orderIdRes.editable, false);
3245
+
3246
+ // 4. INTERACTION_STATE -> not editable
3247
+ const modalRes = classifyDataCandidate({ tag: 'div', kind: 'text', propName: 'isOpen', value: 'true' });
3248
+ assert.equal(modalRes.classification, DATA_CLASSIFICATION.INTERACTION_STATE);
3249
+ assert.equal(modalRes.editable, false);
3250
+
3251
+ // 5. COMPUTED_DATA -> not editable
3252
+ const computedRes = classifyDataCandidate({ tag: 'span', kind: 'text', value: 'items.reduce((a, b) => a + b)' });
3253
+ assert.equal(computedRes.classification, DATA_CLASSIFICATION.COMPUTED_DATA);
3254
+ assert.equal(computedRes.editable, false);
3255
+
3256
+ // 6. RUNTIME_DATA -> not editable
3257
+ const runtimeRes = classifyDataCandidate({ tag: 'span', kind: 'text', value: 'user.email' });
3258
+ assert.equal(runtimeRes.classification, DATA_CLASSIFICATION.RUNTIME_DATA);
3259
+ assert.equal(runtimeRes.editable, false);
3260
+
3261
+ // 7. DECORATIVE -> not editable
3262
+ const decorRes = classifyDataCandidate({ tag: 'span', kind: 'text', value: '•' });
3263
+ assert.equal(decorRes.classification, DATA_CLASSIFICATION.DECORATIVE);
3264
+ assert.equal(decorRes.editable, false);
3265
+ });
3266
+
3267
+ test('ARC v3 Phase 10: planner enforces non-editable skip on platform-controlled, interaction, and computed candidates', () => {
3268
+ const { planTransformations } = require('../planner.cjs');
3269
+
3270
+ const analyses = [
3271
+ {
3272
+ relativeFile: 'components/OrderSummary.tsx',
3273
+ candidates: [
3274
+ {
3275
+ loc: '5:4',
3276
+ tag: 'h2',
3277
+ kind: 'text',
3278
+ value: 'Your Order Summary',
3279
+ confidence: 0.95,
3280
+ },
3281
+ {
3282
+ loc: '12:6',
3283
+ tag: 'span',
3284
+ kind: 'text',
3285
+ value: 'user.email',
3286
+ confidence: 0.95,
3287
+ },
3288
+ {
3289
+ loc: '18:6',
3290
+ tag: 'span',
3291
+ kind: 'text',
3292
+ value: 'LKR',
3293
+ field: 'common.currency',
3294
+ confidence: 0.95,
3295
+ },
3296
+ {
3297
+ loc: '24:6',
3298
+ tag: 'div',
3299
+ kind: 'text',
3300
+ propName: 'isOpen',
3301
+ value: 'true',
3302
+ confidence: 0.95,
3303
+ },
3304
+ ],
3305
+ code: 'export function OrderSummary() {}',
3306
+ },
3307
+ ];
3308
+
3309
+ const plan = planTransformations({ profile: { jsxFiles: ['components/OrderSummary.tsx'] }, analyses });
3310
+ const filePlan = plan.files[0];
3311
+
3312
+ // Only the content headline should be planned for transformation
3313
+ assert.equal(filePlan.transformations.length, 1);
3314
+ assert.equal(filePlan.transformations[0].fallback, 'Your Order Summary');
3315
+ assert.equal(filePlan.transformations[0].dataClassification, 'CONTENT');
3316
+
3317
+ // The 3 runtime/platform/interaction candidates must be skipped
3318
+ assert.equal(plan.skipped.length, 3);
3319
+ const skippedClassifications = plan.skipped.map((s) => s.classification);
3320
+ assert.ok(skippedClassifications.includes('RUNTIME_DATA'));
3321
+ assert.ok(skippedClassifications.includes('PLATFORM_CONTROLLED'));
3322
+ assert.ok(skippedClassifications.includes('INTERACTION_STATE'));
3323
+ });
3324
+
3325
+ test('ARC v3 Phase 11: data-flow engine unrolls nested member collections (category.products.map) and spread props', () => {
3326
+ const { unrollCollectionSource } = require('../data-flow.cjs');
3327
+ const parseSource = (c) => require('recast').parse(c, { parser: require('recast/parsers/babel-ts') });
3328
+
3329
+ // 1. Nested collection member: category.products.map(...)
3330
+ const code = `category.products.map((product) => <ProductCard key={product.id} {...product} />);`;
3331
+ const ast = parseSource(code);
3332
+ const mapExpr = ast.program.body[0].expression;
3333
+ const calleeObj = mapExpr.callee.object; // category.products
3334
+
3335
+ const unrolled = unrollCollectionSource(calleeObj);
3336
+ assert.equal(unrolled.kind, 'nested-member');
3337
+ assert.equal(unrolled.parentIdentifier, 'category');
3338
+ assert.equal(unrolled.childProp, 'products');
3339
+ assert.equal(unrolled.rootName, 'category.products');
3340
+
3341
+ // 2. Chained filter on nested member: category.products.filter(p => p.active)
3342
+ const chainedCode = `category.products.filter(p => p.active).slice(0, 5);`;
3343
+ const chainedAst = parseSource(chainedCode);
3344
+ const chainedExpr = chainedAst.program.body[0].expression;
3345
+ const chainedUnrolled = unrollCollectionSource(chainedExpr);
3346
+ assert.equal(chainedUnrolled.rootName, 'category.products');
3347
+ assert.ok(chainedUnrolled.chain.includes('slice'));
3348
+ });
3349
+
3350
+ test('ARC v3 Phase 11: data-flow engine resolves renamed destructuring and deep nested destructuring aliases', () => {
3351
+ const { collectVariableAliases } = require('../data-flow.cjs');
3352
+ const parseSource = (c) => require('recast').parse(c, { parser: require('recast/parsers/babel-ts') });
3353
+
3354
+ const code = `
3355
+ const { title: heading, image: heroImg } = hero;
3356
+ const { product: { name: productName, price: productPrice } } = props;
3357
+ `;
3358
+ const ast = parseSource(code);
3359
+ const aliases = collectVariableAliases(ast);
3360
+
3361
+ // 1. Renamed destructuring check
3362
+ const headingAlias = aliases.get('heading');
3363
+ assert.ok(headingAlias);
3364
+ assert.equal(headingAlias.rootName, 'hero');
3365
+ assert.equal(headingAlias.sourceProp, 'title');
3366
+ assert.equal(headingAlias.kind, 'destructured-alias');
3367
+
3368
+ const heroImgAlias = aliases.get('heroImg');
3369
+ assert.ok(heroImgAlias);
3370
+ assert.equal(heroImgAlias.rootName, 'hero');
3371
+ assert.equal(heroImgAlias.sourceProp, 'image');
3372
+
3373
+ // 2. Deep nested destructuring check
3374
+ const nameAlias = aliases.get('productName');
3375
+ assert.ok(nameAlias);
3376
+ assert.equal(nameAlias.rootName, 'props.product');
3377
+ assert.equal(nameAlias.sourceProp, 'name');
3378
+ assert.equal(nameAlias.kind, 'deep-destructured-alias');
3379
+ });
3380
+
3381
+ test('ARC v3 Phase 12: RSC boundary optimizer prevents converting async server components and metadata exporters', () => {
3382
+ const {
3383
+ isAsyncServerComponent,
3384
+ canSafelyInjectClientDirective,
3385
+ calculateMinimalClientBoundary,
3386
+ } = require('../rsc-boundary.cjs');
3387
+ const parseSource = (c) => require('recast').parse(c, { parser: require('recast/parsers/babel-ts') });
3388
+
3389
+ // 1. Async Server Component cannot be converted
3390
+ const asyncCode = `export default async function ProductPage() { return <div>Server Data</div>; }`;
3391
+ const asyncAst = parseSource(asyncCode);
3392
+ assert.equal(isAsyncServerComponent(asyncAst), true);
3393
+ assert.equal(canSafelyInjectClientDirective(asyncAst, 'app/product/[id]/page.tsx', { router: 'next-app', appDir: 'app' }), false);
3394
+
3395
+ // 2. Metadata exporter cannot be converted
3396
+ const metaCode = `
3397
+ export const metadata = { title: 'Store Catalog' };
3398
+ export default function Catalog() { return <div>Catalog</div>; }
3399
+ `;
3400
+ const metaAst = parseSource(metaCode);
3401
+ assert.equal(canSafelyInjectClientDirective(metaAst, 'app/shop/page.tsx', { router: 'next-app', appDir: 'app' }), false);
3402
+
3403
+ // 3. Minimal client boundary calculation
3404
+ assert.equal(calculateMinimalClientBoundary({ isAsync: true, hasHooks: true }), 'server');
3405
+ assert.equal(calculateMinimalClientBoundary({ hasMetadata: true, hasHooks: true }), 'server');
3406
+ assert.equal(calculateMinimalClientBoundary({ isLeaf: true, hasHooks: true }), 'client');
3407
+ assert.equal(calculateMinimalClientBoundary({ isLeaf: false, hasHooks: false }), 'server');
3408
+ });
3409
+
3410
+ test('ARC v3 Phase 12: RSC metrics tracking computes boundary counts and validates conversion threshold', () => {
3411
+ const { computeRscMetrics, validateRscBoundaryIntegrity } = require('../rsc-boundary.cjs');
3412
+
3413
+ const files = [
3414
+ { file: 'app/layout.tsx', hadClientDirective: false, hasClientDirective: false },
3415
+ { file: 'app/page.tsx', hadClientDirective: false, hasClientDirective: false },
3416
+ { file: 'components/Hero.tsx', hadClientDirective: false, hasClientDirective: true },
3417
+ { file: 'components/ProductCard.tsx', hadClientDirective: false, hasClientDirective: true },
3418
+ { file: 'components/CartModal.tsx', hadClientDirective: true, hasClientDirective: true },
3419
+ ];
3420
+
3421
+ const metrics = computeRscMetrics(files);
3422
+ assert.equal(metrics.clientBoundariesBefore, 1);
3423
+ assert.equal(metrics.clientBoundariesAfter, 3);
3424
+ assert.equal(metrics.serverComponentsConverted, 2);
3425
+ assert.equal(metrics.serverComponentsPreserved, 2);
3426
+ assert.equal(metrics.conversionExceededThreshold, false);
3427
+
3428
+ const validation = validateRscBoundaryIntegrity(metrics, 5);
3429
+ assert.equal(validation.valid, true);
3430
+
3431
+ // Exceed threshold
3432
+ const failValidation = validateRscBoundaryIntegrity(metrics, 1);
3433
+ assert.equal(failValidation.valid, false);
3434
+ assert.ok(failValidation.error.includes('Excessive RSC conversions'));
3435
+ });
3436
+
3437
+ test('ARC v3 Phase 13: validateCollectionOperations verifies runtime add, remove, reorder, and mutation operations', () => {
3438
+ const { validateCollectionOperations } = require('../runtime-validator.cjs');
3439
+
3440
+ const mockSiteData = {
3441
+ content: {
3442
+ home: {
3443
+ products: [
3444
+ { id: '1', title: 'Product 1', price: 100 },
3445
+ { id: '2', title: 'Product 2', price: 200 },
3446
+ ],
3447
+ testimonials: [
3448
+ { id: 't1', author: 'Jane Doe', quote: 'Great coffee' },
3449
+ ],
3450
+ },
3451
+ },
3452
+ };
3453
+
3454
+ const report = validateCollectionOperations(mockSiteData);
3455
+ assert.equal(report.passed, true);
3456
+ assert.equal(report.totalCollections, 2);
3457
+
3458
+ const prodCol = report.collections.find((c) => c.path === 'home.products');
3459
+ assert.ok(prodCol);
3460
+ assert.equal(prodCol.operations.addItem, true);
3461
+ assert.equal(prodCol.operations.removeItem, true);
3462
+ assert.equal(prodCol.operations.reorderItems, true);
3463
+ assert.equal(prodCol.operations.mutateItemField, true);
3464
+ assert.equal(prodCol.passed, true);
3465
+ });
3466
+
3467
+ test('ARC v3 Phase 13: 12-Gate Acceptance Matrix evaluates critical gates, thresholds, and incomplete/failed statuses', () => {
3468
+ const { evaluateAcceptanceGates } = require('../acceptance-gates.cjs');
3469
+
3470
+ // 1. All Gates Passing
3471
+ const passingMetrics = {
3472
+ sourceAnalysis: { passed: true },
3473
+ astTransformation: { passed: true },
3474
+ typescriptValidation: { passed: true },
3475
+ buildSuccess: { passed: true },
3476
+ fivoraAudit: { passed: true },
3477
+ manifestValid: true,
3478
+ runtimeEditabilityScore: 99.5,
3479
+ collectionOpsPassed: true,
3480
+ imageEditingPassed: true,
3481
+ routeCoveragePassed: true,
3482
+ designPreservationScore: 99.1,
3483
+ controlOnlyValid: true,
3484
+ editabilityCoverage: 99.2,
3485
+ };
3486
+ const passRes = evaluateAcceptanceGates(passingMetrics);
3487
+ assert.equal(passRes.passed, true);
3488
+ assert.equal(passRes.status, 'CONVERSION_PASSED');
3489
+ assert.equal(passRes.allCriticalPassed, true);
3490
+
3491
+ // 2. Critical Gate Failure (Fivora Contract) -> CONVERSION_FAILED
3492
+ const failMetrics = {
3493
+ ...passingMetrics,
3494
+ fivoraAudit: { passed: false },
3495
+ };
3496
+ const failRes = evaluateAcceptanceGates(failMetrics);
3497
+ assert.equal(failRes.passed, false);
3498
+ assert.equal(failRes.status, 'CONVERSION_FAILED');
3499
+ assert.equal(failRes.allCriticalPassed, false);
3500
+
3501
+ // 3. Sub-Threshold Coverage (e.g. 96%) -> CONVERSION_INCOMPLETE
3502
+ const incompleteMetrics = {
3503
+ ...passingMetrics,
3504
+ editabilityCoverage: 96.0,
3505
+ };
3506
+ const incRes = evaluateAcceptanceGates(incompleteMetrics);
3507
+ assert.equal(incRes.passed, false);
3508
+ assert.equal(incRes.status, 'CONVERSION_INCOMPLETE');
3509
+ assert.equal(incRes.allCriticalPassed, true);
3510
+ });
3511
+
3512
+ test('ARC v3 Phase 15: calculateVisualPreservation computes >= 98% score across mobile, tablet, and desktop viewports', () => {
3513
+ const { calculateVisualPreservation } = require('../visual-regression.cjs');
3514
+
3515
+ const beforeCode = `
3516
+ export function Hero() {
3517
+ return (
3518
+ <section className="flex flex-col md:grid md:grid-cols-2 gap-8 p-12 bg-white">
3519
+ <h1 className="text-4xl font-bold">Original Hero Title</h1>
3520
+ <p className="text-lg text-gray-600">Subtitle copy text</p>
3521
+ <button className="btn flex items-center gap-2">
3522
+ <svg className="w-5 h-5" />
3523
+ <span>Discover Products</span>
3524
+ </button>
3525
+ </section>
3526
+ );
3527
+ }
3528
+ `;
3529
+
3530
+ const afterCode = `
3531
+ export function Hero() {
3532
+ return (
3533
+ <section className="flex flex-col md:grid md:grid-cols-2 gap-8 p-12 bg-white">
3534
+ <h1 data-preview-field-path="home.hero.title" className="text-4xl font-bold">
3535
+ {siteData?.content?.home?.hero?.title ?? "Original Hero Title"}
3536
+ </h1>
3537
+ <p data-preview-field-path="home.hero.subtitle" className="text-lg text-gray-600">
3538
+ {siteData?.content?.home?.hero?.subtitle ?? "Subtitle copy text"}
3539
+ </p>
3540
+ <button className="btn flex items-center gap-2">
3541
+ <svg className="w-5 h-5" />
3542
+ <span data-preview-field-path="home.hero.cta">
3543
+ {siteData?.content?.home?.hero?.cta ?? "Discover Products"}
3544
+ </span>
3545
+ </button>
3546
+ </section>
3547
+ );
3548
+ }
3549
+ `;
3550
+
3551
+ const report = calculateVisualPreservation(beforeCode, afterCode);
3552
+ assert.equal(report.passed, true);
3553
+ assert.ok(report.overallPreservationScore >= 98.0);
3554
+ assert.ok(report.viewports.mobile.preservationScore >= 98.0);
3555
+ assert.ok(report.viewports.tablet.preservationScore >= 98.0);
3556
+ assert.ok(report.viewports.desktop.preservationScore >= 98.0);
3557
+ assert.equal(report.blockingIssues.length, 0);
3558
+ });
3559
+
3560
+ test('ARC v3 Phase 15: calculateVisualPreservation detects dropped icons or layout shifts as blocking issues', () => {
3561
+ const { calculateVisualPreservation } = require('../visual-regression.cjs');
3562
+
3563
+ const beforeCode = `
3564
+ export function ButtonWithIcon() {
3565
+ return (
3566
+ <button className="btn">
3567
+ <svg className="icon" />
3568
+ <span>Add to Cart</span>
3569
+ </button>
3570
+ );
3571
+ }
3572
+ `;
3573
+
3574
+ const brokenAfterCode = `
3575
+ export function ButtonWithIcon() {
3576
+ return (
3577
+ <button className="btn">
3578
+ <span>Add to Cart</span>
3579
+ </button>
3580
+ );
3581
+ }
3582
+ `;
3583
+
3584
+ const report = calculateVisualPreservation(beforeCode, brokenAfterCode);
3585
+ assert.equal(report.passed, false);
3586
+ assert.ok(report.blockingIssues.length > 0);
3587
+ assert.ok(report.blockingIssues[0].includes('SVG or icon dropped'));
3588
+ });
3589
+
3590
+ test('ARC v3 Phase 15: verifyInteractionsSync validates mobile nav toggle, accordions, tabs, and carousels', () => {
3591
+ const { verifyInteractionsSync, INTERACTIVE_PATTERNS } = require('../interaction-verifier.cjs');
3592
+
3593
+ const files = [
3594
+ {
3595
+ file: 'components/Header.tsx',
3596
+ code: `
3597
+ export function Header() {
3598
+ const [open, setOpen] = useState(false);
3599
+ return (
3600
+ <header>
3601
+ <button aria-label="Toggle Menu" onClick={() => setOpen(!open)}>
3602
+ <span>Menu</span>
3603
+ </button>
3604
+ </header>
3605
+ );
3606
+ }
3607
+ `,
3608
+ },
3609
+ {
3610
+ file: 'components/FaqSection.tsx',
3611
+ code: `export function Faq() { return <AccordionTrigger>Item</AccordionTrigger>; }`,
3612
+ },
3613
+ {
3614
+ file: 'components/ProductTabs.tsx',
3615
+ code: `export function Tabs() { return <TabsTrigger value="one">Tab</TabsTrigger>; }`,
3616
+ },
3617
+ {
3618
+ file: 'components/Slider.tsx',
3619
+ code: `export function Slider() { return <Swiper><SwiperSlide>Slide 1</SwiperSlide></Swiper>; }`,
3620
+ },
3621
+ ];
3622
+
3623
+ const report = verifyInteractionsSync({ files });
3624
+ assert.equal(report.passed, true);
3625
+ assert.equal(report.interactionScore, 100.0);
3626
+ assert.equal(report.totalTested, 4);
3627
+ assert.equal(report.failedTests, 0);
3628
+
3629
+ const patterns = report.patterns.map((p) => p.pattern);
3630
+ assert.ok(patterns.includes(INTERACTIVE_PATTERNS.NAVBAR_MOBILE_TOGGLE));
3631
+ assert.ok(patterns.includes(INTERACTIVE_PATTERNS.ACCORDION));
3632
+ assert.ok(patterns.includes(INTERACTIVE_PATTERNS.TABS));
3633
+ assert.ok(patterns.includes(INTERACTIVE_PATTERNS.CAROUSEL));
3634
+ });
3635
+
3636
+ function createMockCompliantProject() {
3637
+ const fs = require('fs');
3638
+ const os = require('os');
3639
+ const path = require('path');
3640
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deneb-compliant-'));
3641
+ const appDir = path.join(tempDir, 'src', 'app');
3642
+ const dataDir = path.join(tempDir, 'src', 'data');
3643
+ fs.mkdirSync(appDir, { recursive: true });
3644
+ fs.mkdirSync(dataDir, { recursive: true });
3645
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify({ name: 'sample', dependencies: { next: '15.0.0' } }));
3646
+ fs.writeFileSync(path.join(tempDir, 'fivora-template.json'), JSON.stringify({
3647
+ framework: 'nextjs-static-export',
3648
+ version: 2,
3649
+ arcVersion: '2.1.0',
3650
+ schemaVersion: 2,
3651
+ siteDataFile: 'src/data/site-data.json',
3652
+ pages: [{ id: 'home', label: 'Home', route: '/', required: true }],
3653
+ editorSchema: {
3654
+ version: 1,
3655
+ sections: [
3656
+ { id: 'home', path: 'home', type: 'object', label: 'Home', fields: [{ key: 'heroTitle', type: 'text' }] }
3657
+ ]
3658
+ },
3659
+ visualEditing: {
3660
+ contractVersion: 1,
3661
+ mode: 'strict',
3662
+ controlOnlyPaths: []
3663
+ }
3664
+ }, null, 2));
3665
+ fs.writeFileSync(path.join(dataDir, 'site-data.json'), JSON.stringify({
3666
+ content: {
3667
+ home: { heroTitle: 'Welcome' }
3668
+ }
3669
+ }, null, 2));
3670
+ fs.writeFileSync(path.join(appDir, 'layout.tsx'), 'export default function RootLayout({ children }: any) { return <html><body>{children}</body></html>; }');
3671
+ fs.writeFileSync(path.join(appDir, 'page.tsx'), 'export default function HomePage() { return <main data-preview-page-id="home"><h1 data-preview-field-path="home.heroTitle">Welcome</h1></main>; }');
3672
+
3673
+ return {
3674
+ dir: tempDir,
3675
+ cleanup: () => fs.rmSync(tempDir, { recursive: true, force: true }),
3676
+ };
3677
+ }
3678
+
3679
+ test('ARC v3 Phase 16: auditStorefrontTemplate verifies real-world storefront against 12-Gate Acceptance Matrix', () => {
3680
+ const { auditStorefrontTemplate, resolveWorkspaceStorefrontsDir } = require('../corpus-verifier.cjs');
3681
+ const path = require('path');
3682
+ const fs = require('fs');
3683
+ const baseDir = resolveWorkspaceStorefrontsDir();
3684
+ const coffeeDir = path.join(baseDir, 'coffee');
3685
+ const hasRealStorefront = fs.existsSync(coffeeDir) && fs.existsSync(path.join(coffeeDir, 'fivora-template.json'));
3686
+
3687
+ let targetDir = coffeeDir;
3688
+ let cleanup = null;
3689
+ if (!hasRealStorefront) {
3690
+ const fixture = createMockCompliantProject();
3691
+ targetDir = fixture.dir;
3692
+ cleanup = fixture.cleanup;
3693
+ }
3694
+
3695
+ try {
3696
+ const result = auditStorefrontTemplate(targetDir, { templateId: 'coffee', templateName: 'Coffee Shop' });
3697
+ assert.equal(result.validStructure, true);
3698
+ assert.equal(result.fivoraContract.passed, true);
3699
+ assert.equal(result.fivoraContract.violationCount, 0);
3700
+ assert.equal(result.runtimeEditability.passed, true);
3701
+ assert.equal(result.collectionOperations.passed, true);
3702
+ assert.equal(result.acceptanceGates.passed, true);
3703
+ assert.equal(result.acceptanceGates.status, 'CONVERSION_PASSED');
3704
+ assert.equal(result.passed, true);
3705
+ } finally {
3706
+ if (cleanup) cleanup();
3707
+ }
3708
+ });
3709
+
3710
+ test('ARC v3 Phase 16: verifyStorefrontCorpus audits all real-world storefronts and blocks invalid conversions', () => {
3711
+ const { verifyStorefrontCorpus, resolveWorkspaceStorefrontsDir } = require('../corpus-verifier.cjs');
3712
+ const fs = require('fs');
3713
+ const path = require('path');
3714
+
3715
+ const baseDir = resolveWorkspaceStorefrontsDir();
3716
+ const hasWorkspaceCorpus = fs.existsSync(path.join(baseDir, 'coffee')) && fs.existsSync(path.join(baseDir, 'car-sale'));
3717
+
3718
+ const report = verifyStorefrontCorpus();
3719
+ assert.equal(report.totalTemplates, 6);
3720
+
3721
+ if (hasWorkspaceCorpus) {
3722
+ // When run in full workspace with all 6 storefronts available
3723
+ assert.equal(report.passedTemplates, 6);
3724
+ assert.equal(report.failedTemplates, 0);
3725
+ assert.equal(report.corpusSuccessRate, 100);
3726
+ assert.equal(report.allTemplatesPassed, true);
3727
+
3728
+ // All 6 real-world storefronts pass with 100% compliance
3729
+ for (const result of report.results) {
3730
+ assert.ok(result.passed, `Storefront ${result.templateId} must pass 100%`);
3731
+ assert.equal(result.acceptanceGates.status, 'CONVERSION_PASSED');
3732
+ }
3733
+ } else {
3734
+ // In standalone CI environment without sibling storefront repositories,
3735
+ // verify graceful non-crashing handling
3736
+ assert.ok(report.results.length === 6);
3737
+ assert.equal(report.failedTemplates, 6);
3738
+ assert.equal(report.allTemplatesPassed, false);
3739
+ for (const result of report.results) {
3740
+ assert.equal(result.passed, false);
3741
+ assert.equal(result.acceptanceGates.status, 'CONVERSION_FAILED');
3742
+ assert.ok(result.fivoraContract.errors.length > 0);
3743
+ }
3744
+ }
3745
+ });
3746
+
3747
+ test('ARC v3 Phase 17: applyMutation generates AST mutations and testMutationResilience prevents unhandled compiler crashes', () => {
3748
+ const {
3749
+ applyMutation,
3750
+ testMutationResilience,
3751
+ DEFAULT_SAMPLE_COMPONENT,
3752
+ } = require('../fuzz-engine.cjs');
3753
+
3754
+ // Test spread mutation
3755
+ const spread = applyMutation(DEFAULT_SAMPLE_COMPONENT, 'SPREAD_PROPS');
3756
+ assert.equal(spread.applied, true);
3757
+ const spreadRes = testMutationResilience(spread.mutatedCode, 'SPREAD_PROPS');
3758
+ assert.equal(spreadRes.crashed, false);
3759
+ assert.equal(spreadRes.resilient, true);
3760
+ assert.equal(spreadRes.outputValidSyntax, true);
3761
+
3762
+ // Test corrupt syntax recovery (must catch cleanly without dying)
3763
+ const corrupt = applyMutation(DEFAULT_SAMPLE_COMPONENT, 'CORRUPT_SYNTAX');
3764
+ assert.equal(corrupt.applied, true);
3765
+ const corruptRes = testMutationResilience(corrupt.mutatedCode, 'CORRUPT_SYNTAX');
3766
+ assert.equal(corruptRes.crashed, false);
3767
+ assert.equal(corruptRes.status, 'PARSER_REJECTED_CLEANLY');
3768
+ assert.equal(corruptRes.resilient, true);
3769
+ });
3770
+
3771
+ test('ARC v3 Phase 17: runFuzzHarness achieves 100% resilience score across all mutation strategies', () => {
3772
+ const { runFuzzHarness, MUTATION_TYPES } = require('../fuzz-engine.cjs');
3773
+
3774
+ const report = runFuzzHarness();
3775
+ assert.equal(report.totalMutations, MUTATION_TYPES.length);
3776
+ assert.equal(report.crashedCount, 0, 'Must have zero unhandled crashes');
3777
+ assert.equal(report.syntaxErrorsInOutput, 0, 'Must have zero syntax errors in output');
3778
+ assert.equal(report.resilienceScore, 100.0, 'Resilience score must be 100%');
3779
+ assert.equal(report.allResilient, true);
3780
+ });
3781
+
3782
+ test('ARC v3 Phase 18: analyzeBlockedProject identifies exact unmapped fields, files, and line numbers for blocked conversions', () => {
3783
+ const { analyzeBlockedProject } = require('../explain-blocked.cjs');
3784
+ const fs = require('fs');
3785
+ const os = require('os');
3786
+ const path = require('path');
3787
+
3788
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deneb-blocked-'));
3789
+ const compDir = path.join(tempDir, 'src', 'components');
3790
+ const dataDir = path.join(tempDir, 'src', 'data');
3791
+ fs.mkdirSync(compDir, { recursive: true });
3792
+ fs.mkdirSync(dataDir, { recursive: true });
3793
+ fs.writeFileSync(path.join(dataDir, 'site-data.json'), JSON.stringify({ content: { home: { unmappedField: 'Hello' } } }));
3794
+ fs.writeFileSync(path.join(tempDir, 'fivora-template.json'), JSON.stringify({
3795
+ framework: 'nextjs-static-export',
3796
+ siteDataFile: 'src/data/site-data.json',
3797
+ editorSchema: {
3798
+ sections: [{ id: 'home', path: 'home', type: 'object', fields: [{ key: 'unmappedField', type: 'currency' }] }]
3799
+ }
3800
+ }));
3801
+ fs.writeFileSync(path.join(compDir, 'Showcase.tsx'), 'export function Showcase() {\n return <div><span>Hello</span></div>;\n}');
3802
+
3803
+ try {
3804
+ const report = analyzeBlockedProject(tempDir);
3805
+ assert.equal(report.blocked, true);
3806
+ assert.equal(report.totalBlockingIssues, 2);
3807
+
3808
+ const finding = report.findings.find((f) => f.ruleId === 'FIVORA_UNMAPPED_FIELD');
3809
+ assert.ok(finding);
3810
+ assert.ok(finding.file.includes('Showcase.tsx'));
3811
+ assert.ok(finding.line >= 1);
3812
+ assert.ok(finding.remediation.includes('data-preview-field-path') || finding.remediation.includes('controlOnlyPaths'));
3813
+
3814
+ const unsupported = report.findings.find((f) => f.ruleId === 'SCHEMA_UNSUPPORTED_TYPE');
3815
+ assert.ok(unsupported);
3816
+ assert.ok(unsupported.message.includes('currency'));
3817
+ } finally {
3818
+ fs.rmSync(tempDir, { recursive: true, force: true });
3819
+ }
3820
+ });
3821
+
3822
+ test('ARC v3 Phase 18: formatBlockedExplanationTerminal renders actionable remediation guide with zero false positives', () => {
3823
+ const { analyzeBlockedProject, formatBlockedExplanationTerminal } = require('../explain-blocked.cjs');
3824
+ const { resolveWorkspaceStorefrontsDir } = require('../corpus-verifier.cjs');
3825
+ const fs = require('fs');
3826
+ const os = require('os');
3827
+ const path = require('path');
3828
+
3829
+ const baseDir = resolveWorkspaceStorefrontsDir();
3830
+ const coffeeDir = path.join(baseDir, 'coffee');
3831
+ const hasRealCoffee = fs.existsSync(coffeeDir) && fs.existsSync(path.join(coffeeDir, 'fivora-template.json'));
3832
+
3833
+ let compliantDir = coffeeDir;
3834
+ let compliantCleanup = null;
3835
+ if (!hasRealCoffee) {
3836
+ const fixture = createMockCompliantProject();
3837
+ compliantDir = fixture.dir;
3838
+ compliantCleanup = fixture.cleanup;
3839
+ }
3840
+
3841
+ try {
3842
+ // Compliant project formats clean success
3843
+ const coffeeReport = analyzeBlockedProject(compliantDir);
3844
+ assert.equal(coffeeReport.blocked, false);
3845
+ const coffeeText = formatBlockedExplanationTerminal(coffeeReport);
3846
+ assert.ok(coffeeText.includes('CONVERSION_PASSED'));
3847
+ assert.ok(coffeeText.includes('No blocking issues detected'));
3848
+ } finally {
3849
+ if (compliantCleanup) compliantCleanup();
3850
+ }
3851
+
3852
+ // Synthetic blocked project formats detailed remediation
3853
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deneb-blocked-ui-'));
3854
+ const compDir = path.join(tempDir, 'src', 'components');
3855
+ const dataDir = path.join(tempDir, 'src', 'data');
3856
+ fs.mkdirSync(compDir, { recursive: true });
3857
+ fs.mkdirSync(dataDir, { recursive: true });
3858
+ fs.writeFileSync(path.join(dataDir, 'site-data.json'), JSON.stringify({ content: { home: { testField: 'Text' } } }));
3859
+ fs.writeFileSync(path.join(tempDir, 'fivora-template.json'), JSON.stringify({
3860
+ framework: 'nextjs-static-export',
3861
+ siteDataFile: 'src/data/site-data.json',
3862
+ editorSchema: {
3863
+ sections: [{ id: 'home', path: 'home', type: 'object', fields: [{ key: 'testField', type: 'currency' }] }]
3864
+ }
3865
+ }));
3866
+ fs.writeFileSync(path.join(compDir, 'Comp.tsx'), 'export function Comp() { return <div>Text</div>; }');
3867
+
3868
+ try {
3869
+ const blockedReport = analyzeBlockedProject(tempDir);
3870
+ assert.equal(blockedReport.blocked, true);
3871
+ const blockedText = formatBlockedExplanationTerminal(blockedReport);
3872
+ assert.ok(blockedText.includes('DENEB CONVERSION BLOCKED'));
3873
+ assert.ok(blockedText.includes('SCHEMA_UNSUPPORTED_TYPE'));
3874
+ assert.ok(blockedText.includes('Fix:'));
3875
+ } finally {
3876
+ fs.rmSync(tempDir, { recursive: true, force: true });
3877
+ }
3878
+ });
3879
+
3880
+
3881
+
3882
+
3883
+
2962
3884
 
2963
3885
 
2964
3886