@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
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const {
6
+ STANDARD_VIEWPORTS,
7
+ calculateVisualPreservation,
8
+ extractDesignSnapshot,
9
+ } = require('../visual-regression.cjs');
10
+
11
+ test('Visual Regression: extracts element tags, layout classes, and compound icons', () => {
12
+ const code = `
13
+ export function Hero() {
14
+ return (
15
+ <section className="relative flex flex-col md:grid md:grid-cols-2 gap-8 p-12 bg-white">
16
+ <h1 className="text-4xl font-bold">Original Title</h1>
17
+ <button className="btn flex items-center gap-2">
18
+ <svg className="w-5 h-5" />
19
+ <span>Order Now</span>
20
+ </button>
21
+ </section>
22
+ );
23
+ }
24
+ `;
25
+
26
+ const snapshot = extractDesignSnapshot(code);
27
+ assert.ok(snapshot.tags.includes('section'));
28
+ assert.ok(snapshot.tags.includes('h1'));
29
+ assert.ok(snapshot.tags.includes('button'));
30
+ assert.ok(snapshot.classes.has('flex'));
31
+ assert.ok(snapshot.classes.has('md:grid-cols-2'));
32
+
33
+ const btnElem = snapshot.elements.find((e) => e.tag === 'button');
34
+ assert.ok(btnElem);
35
+ assert.equal(btnElem.hasSvg, true);
36
+ assert.equal(btnElem.hasSpan, true);
37
+ });
38
+
39
+ test('Visual Regression: calculates >= 98% design preservation on valid ARC transformation', () => {
40
+ const beforeCode = `
41
+ export function Banner() {
42
+ return (
43
+ <div className="grid grid-cols-3 gap-6 p-8">
44
+ <h2>Featured Items</h2>
45
+ <p>Hand-picked for you</p>
46
+ </div>
47
+ );
48
+ }
49
+ `;
50
+
51
+ // Transformed code has siteData getters and preview marker, but identical layout classes & tags
52
+ const afterCode = `
53
+ export function Banner() {
54
+ return (
55
+ <div className="grid grid-cols-3 gap-6 p-8">
56
+ <h2 data-preview-field-path="home.banner.title">{siteData?.content?.home?.banner?.title ?? "Featured Items"}</h2>
57
+ <p data-preview-field-path="home.banner.subtitle">{siteData?.content?.home?.banner?.subtitle ?? "Hand-picked for you"}</p>
58
+ </div>
59
+ );
60
+ }
61
+ `;
62
+
63
+ const report = calculateVisualPreservation(beforeCode, afterCode);
64
+ assert.equal(report.passed, true);
65
+ assert.ok(report.overallPreservationScore >= 98.0);
66
+ assert.ok(report.viewports.mobile.preservationScore >= 98.0);
67
+ assert.ok(report.viewports.tablet.preservationScore >= 98.0);
68
+ assert.ok(report.viewports.desktop.preservationScore >= 98.0);
69
+ assert.equal(report.blockingIssues.length, 0);
70
+ });
71
+
72
+ test('Visual Regression: detects dropped layout classes or icons and reports blocking issue', () => {
73
+ const beforeCode = `
74
+ export function Action() {
75
+ return (
76
+ <button className="btn">
77
+ <svg className="icon" />
78
+ <span>Checkout</span>
79
+ </button>
80
+ );
81
+ }
82
+ `;
83
+
84
+ // Broken transform accidentally removed the svg icon
85
+ const brokenCode = `
86
+ export function Action() {
87
+ return (
88
+ <button className="btn">
89
+ <span>Checkout</span>
90
+ </button>
91
+ );
92
+ }
93
+ `;
94
+
95
+ const report = calculateVisualPreservation(beforeCode, brokenCode);
96
+ assert.equal(report.passed, false);
97
+ assert.ok(report.blockingIssues.length > 0);
98
+ assert.ok(report.blockingIssues[0].includes('SVG or icon dropped'));
99
+ });
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deneb ARC v3 — Production Acceptance Gates Engine
5
+ * Implements the 12-Gate Acceptance Matrix recommended for enterprise Next.js -> Fivora compilation.
6
+ */
7
+ const ACCEPTANCE_GATES = {
8
+ SOURCE_ANALYSIS: 'Source Analysis',
9
+ AST_TRANSFORMATION: 'AST Transformation',
10
+ TYPESCRIPT_VALIDATION: 'TypeScript / JS Validation',
11
+ BUILD_SUCCESS: 'Next.js Build',
12
+ FIVORA_CONTRACT: 'Fivora Contract',
13
+ MANIFEST_SITE_DATA: 'Manifest / Site Data',
14
+ RUNTIME_DATA_BINDING: 'Runtime Data Binding',
15
+ RUNTIME_COLLECTION_OPS: 'Runtime Collection Operations',
16
+ RUNTIME_IMAGE_EDITING: 'Runtime Image Editing',
17
+ ROUTE_COVERAGE: 'Route Coverage',
18
+ DESIGN_PRESERVATION: 'Design Preservation',
19
+ CONTROL_ONLY_PROTECTION: 'Control-Only Protection',
20
+ };
21
+
22
+ /**
23
+ * Evaluates compilation metrics against the 12-Gate Acceptance Matrix.
24
+ */
25
+ function evaluateAcceptanceGates(metrics = {}) {
26
+ const gates = {
27
+ sourceAnalysis: {
28
+ name: ACCEPTANCE_GATES.SOURCE_ANALYSIS,
29
+ passed: Boolean(metrics.sourceAnalysis?.passed !== false),
30
+ critical: true,
31
+ },
32
+ astTransformation: {
33
+ name: ACCEPTANCE_GATES.AST_TRANSFORMATION,
34
+ passed: Boolean(metrics.astTransformation?.passed !== false),
35
+ critical: true,
36
+ },
37
+ typescriptValidation: {
38
+ name: ACCEPTANCE_GATES.TYPESCRIPT_VALIDATION,
39
+ passed: Boolean(metrics.typescriptValidation?.passed !== false),
40
+ critical: true,
41
+ },
42
+ buildSuccess: {
43
+ name: ACCEPTANCE_GATES.BUILD_SUCCESS,
44
+ passed: Boolean(metrics.buildSuccess?.passed !== false),
45
+ critical: true,
46
+ },
47
+ fivoraContract: {
48
+ name: ACCEPTANCE_GATES.FIVORA_CONTRACT,
49
+ passed: Boolean(
50
+ metrics.fivoraAudit?.passed === true ||
51
+ metrics.fivoraContract?.passed === true ||
52
+ (metrics.fivoraAudit === undefined && metrics.fivoraContract === undefined)
53
+ ),
54
+ critical: true,
55
+ },
56
+ manifestSiteData: {
57
+ name: ACCEPTANCE_GATES.MANIFEST_SITE_DATA,
58
+ passed: Boolean(metrics.manifestValid !== false),
59
+ critical: true,
60
+ },
61
+ runtimeDataBinding: {
62
+ name: ACCEPTANCE_GATES.RUNTIME_DATA_BINDING,
63
+ passed: Boolean((metrics.runtimeEditabilityScore ?? 100) >= 98),
64
+ critical: true,
65
+ },
66
+ runtimeCollectionOps: {
67
+ name: ACCEPTANCE_GATES.RUNTIME_COLLECTION_OPS,
68
+ passed: Boolean(metrics.collectionOpsPassed !== false),
69
+ critical: true,
70
+ },
71
+ runtimeImageEditing: {
72
+ name: ACCEPTANCE_GATES.RUNTIME_IMAGE_EDITING,
73
+ passed: Boolean(metrics.imageEditingPassed !== false),
74
+ critical: false,
75
+ },
76
+ routeCoverage: {
77
+ name: ACCEPTANCE_GATES.ROUTE_COVERAGE,
78
+ passed: Boolean(metrics.routeCoveragePassed !== false),
79
+ critical: false,
80
+ },
81
+ designPreservation: {
82
+ name: ACCEPTANCE_GATES.DESIGN_PRESERVATION,
83
+ passed: Boolean((metrics.designPreservationScore ?? 100) >= 98),
84
+ critical: false,
85
+ },
86
+ controlOnlyProtection: {
87
+ name: ACCEPTANCE_GATES.CONTROL_ONLY_PROTECTION,
88
+ passed: Boolean(metrics.controlOnlyValid !== false),
89
+ critical: true,
90
+ },
91
+ };
92
+
93
+ const criticalGates = Object.values(gates).filter((g) => g.critical);
94
+ const allCriticalPassed = criticalGates.every((g) => g.passed);
95
+ const editabilityCoverage = metrics.editabilityCoverage ?? 100;
96
+ const designPreservation = metrics.designPreservationScore ?? 100;
97
+
98
+ // Conversion only passes when all critical gates pass and editability coverage >= 98%
99
+ const conversionPassed = allCriticalPassed && editabilityCoverage >= 98;
100
+ const status = conversionPassed
101
+ ? 'CONVERSION_PASSED'
102
+ : allCriticalPassed
103
+ ? 'CONVERSION_INCOMPLETE'
104
+ : 'CONVERSION_FAILED';
105
+
106
+ return {
107
+ passed: conversionPassed,
108
+ status,
109
+ gates,
110
+ allCriticalPassed,
111
+ editabilityCoverage,
112
+ designPreservation,
113
+ };
114
+ }
115
+
116
+ module.exports = {
117
+ ACCEPTANCE_GATES,
118
+ evaluateAcceptanceGates,
119
+ };
@@ -4,37 +4,82 @@ const recast = require('recast');
4
4
  const b = recast.types.builders;
5
5
 
6
6
  /**
7
- * Canonical Path Abstraction for Deneb ARC.
8
- * Prevents subtle path drift between runtime siteData getters and visual DOM markers.
7
+ * Deneb ARC v3 — Canonical Path Engine
8
+ * Single Source of Truth for runtime siteData accessors, visual DOM markers,
9
+ * schema generation, and diagnostics identity.
9
10
  */
10
11
  class CanonicalFieldRef {
11
- constructor(canonicalPath) {
12
+ /**
13
+ * @param {string} canonicalPath - e.g. "home.hero.title" or "home.categories[0].name"
14
+ * @param {object} [options]
15
+ * @param {object} [options.provenance] - Origin tracking (file, loc, component, kind)
16
+ * @param {boolean} [options.isCollection] - Whether path is an array collection
17
+ */
18
+ constructor(canonicalPath, options = {}) {
12
19
  if (!canonicalPath || typeof canonicalPath !== 'string') {
13
20
  throw new Error(`Invalid canonical path: ${canonicalPath}`);
14
21
  }
15
- this.path = canonicalPath;
16
- this.parts = canonicalPath.split('.').filter(Boolean);
17
- this.scope = this.parts[0] || 'home';
18
- this.section = this.parts.length > 2 ? this.parts[1] : null;
19
- this.fieldName = this.parts[this.parts.length - 1];
22
+ const cleanPath = canonicalPath.trim().replace(/^\.+|\.+$/g, '');
23
+ if (!cleanPath) {
24
+ throw new Error(`Empty canonical path after normalization: ${canonicalPath}`);
25
+ }
26
+
27
+ this.path = cleanPath;
28
+ this.canonicalPath = cleanPath;
29
+ this.provenance = options.provenance || {};
30
+
31
+ // Segment split while preserving bracket indices
32
+ this.parts = cleanPath.split('.').filter(Boolean);
33
+ const firstPart = this.parts[0] || 'home';
34
+ this.scope = firstPart.replace(/\[.*$/, '');
35
+ this.section = this.parts.length > 2 ? this.parts[1].replace(/\[.*$/, '') : null;
36
+ this.fieldName = (this.parts[this.parts.length - 1] || '').replace(/\[.*$/, '');
37
+
38
+ this.previewPath = cleanPath;
39
+ this.previewMarker = `data-preview-field-path="${cleanPath}"`;
40
+ this.runtimePath = `siteData.content.${cleanPath}`;
41
+ this.manifestSchemaPath = cleanPath;
42
+ this.diagnosticsId = `field:${cleanPath}`;
43
+ this.isCollection = Boolean(options.isCollection || /\[\d+\]|\[\$\{[^}]+\}\]/.test(cleanPath));
44
+
45
+ // Construct readable runtime accessor string
46
+ const accessorSegments = ['siteData', 'content', ...this.parts];
47
+ this.runtimeAccessor = accessorSegments
48
+ .map((seg, i) => {
49
+ if (i === 0) return seg;
50
+ if (/^([a-zA-Z0-9_-]+)\[(\d+|\$\{[^}]+\})\]$/.test(seg)) {
51
+ const match = seg.match(/^([a-zA-Z0-9_-]+)\[(\d+|\$\{[^}]+\})\]$/);
52
+ return `?.${match[1]}?.[${match[2]}]`;
53
+ }
54
+ return `?.${seg}`;
55
+ })
56
+ .join('');
20
57
  }
21
58
 
22
59
  /**
23
- * Builds the optional chaining AST expression:
24
- * siteData?.content?.home?.section?.field
60
+ * Builds optional chaining AST expression:
61
+ * siteData?.content?.home?.hero?.title
25
62
  */
26
63
  toGetterAst() {
27
64
  let expr = b.identifier('siteData');
28
65
  const allParts = ['content', ...this.parts];
29
66
  for (const part of allParts) {
30
- expr = b.optionalMemberExpression(expr, b.identifier(part), false, true);
67
+ const arrayMatch = part.match(/^([a-zA-Z0-9_-]+)\[(\d+)\]$/);
68
+ if (arrayMatch) {
69
+ const propName = arrayMatch[1];
70
+ const idx = parseInt(arrayMatch[2], 10);
71
+ expr = b.optionalMemberExpression(expr, b.identifier(propName), false, true);
72
+ expr = b.optionalMemberExpression(expr, b.numericLiteral(idx), true, true);
73
+ } else {
74
+ expr = b.optionalMemberExpression(expr, b.identifier(part), false, true);
75
+ }
31
76
  }
32
77
  return expr;
33
78
  }
34
79
 
35
80
  /**
36
- * Builds the fallback expression:
37
- * siteData?.content?.home?.section?.field ?? fallback
81
+ * Builds fallback expression:
82
+ * siteData?.content?.home?.hero?.title ?? fallback
38
83
  */
39
84
  toBindingAst(fallbackValue = '') {
40
85
  const getter = this.toGetterAst();
@@ -50,8 +95,8 @@ class CanonicalFieldRef {
50
95
  }
51
96
 
52
97
  /**
53
- * Builds the JSX attribute AST:
54
- * data-preview-field-path="home.section.field"
98
+ * Builds JSX attribute AST:
99
+ * data-preview-field-path="home.hero.title"
55
100
  */
56
101
  toPreviewAttrAst() {
57
102
  return b.jsxAttribute(
@@ -61,9 +106,71 @@ class CanonicalFieldRef {
61
106
  }
62
107
 
63
108
  /**
64
- * Generates a nested item path for sub-collections:
65
- * e.g. parentPath="home.categories[0]", childProp="products", childIndex="2"
66
- * -> "home.categories[0].products[2]"
109
+ * Builds JSX attribute AST for style binding targets:
110
+ * data-preview-style-target="home.hero.title"
111
+ */
112
+ toPreviewStyleTargetAst() {
113
+ return b.jsxAttribute(
114
+ b.jsxIdentifier('data-preview-style-target'),
115
+ b.stringLiteral(this.path)
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Generates assignment code for runtime or testing mutations:
121
+ * siteData.content.home.hero.title = value;
122
+ */
123
+ toSetterCode(valueExpr = 'value') {
124
+ return `siteData.content.${this.path} = ${valueExpr};`;
125
+ }
126
+
127
+ /**
128
+ * Derives a child item reference for collections:
129
+ * e.g. "home.categories" -> "home.categories[0]"
130
+ */
131
+ toCollectionItem(index) {
132
+ const itemPath = `${this.path}[${index}]`;
133
+ return new CanonicalFieldRef(itemPath, {
134
+ provenance: { ...this.provenance, parentPath: this.path, index },
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Derives a nested collection child reference:
140
+ * e.g. "home.categories[0]" -> "home.categories[0].products[2]"
141
+ */
142
+ toCollectionChild(childProp, childIndexExpr) {
143
+ const childPath = childIndexExpr !== undefined
144
+ ? `${this.path}.${childProp}[${childIndexExpr}]`
145
+ : `${this.path}.${childProp}`;
146
+ return new CanonicalFieldRef(childPath, {
147
+ provenance: { ...this.provenance, parentPath: this.path, childProp },
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Validates canonical path syntax and structure
153
+ */
154
+ validate() {
155
+ if (!isValidCanonicalPath(this.path)) {
156
+ return {
157
+ valid: false,
158
+ error: `Syntax violation in canonical path "${this.path}"`,
159
+ };
160
+ }
161
+ if (!this.scope) {
162
+ return {
163
+ valid: false,
164
+ error: `Missing root scope in canonical path "${this.path}"`,
165
+ };
166
+ }
167
+ return { valid: true };
168
+ }
169
+
170
+ /**
171
+ * Static helper for nested collection template literals:
172
+ * e.g. parentPath="home.categories[0]", childProp="products", childIndexExpr="index"
173
+ * -> "home.categories[0].products[${index}]"
67
174
  */
68
175
  static buildNestedItemPath(parentItemPath, childProp, childIndexExpr = 'index') {
69
176
  return `${parentItemPath}.${childProp}[\${${childIndexExpr}}]`;
@@ -72,23 +179,35 @@ class CanonicalFieldRef {
72
179
 
73
180
  function isValidCanonicalPath(canonicalPath) {
74
181
  if (typeof canonicalPath !== 'string' || !canonicalPath.trim()) return false;
75
- return /^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+|\[\d+\]|\[\$\{[^}]+\}\])*$/.test(canonicalPath);
182
+ return /^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+|\[\d+\]|\[\$\{[^}]+\}\])*$/.test(canonicalPath.trim());
76
183
  }
77
184
 
78
185
  function validatePathAlignment(getterCode, previewAttrCode) {
79
186
  const normalizedGetter = String(getterCode || '').replace(/\?\./g, '.');
80
- const getterMatch = normalizedGetter.match(/siteData\.content\.([a-zA-Z0-9_.]+)/);
81
- const attrMatch = String(previewAttrCode || '').match(/data-preview-field-path=["'`{]([a-zA-Z0-9_.]+)/);
187
+ const getterMatch = normalizedGetter.match(/siteData\.content\.([a-zA-Z0-9_.[\]]+)/);
188
+ const attrMatch = String(previewAttrCode || '').match(/data-preview-field-path=["'`{]([a-zA-Z0-9_.[\]]+)/);
82
189
  if (!getterMatch || !attrMatch) return false;
83
190
  return getterMatch[1] === attrMatch[1];
84
191
  }
85
192
 
86
- function fieldRef(canonicalPath) {
87
- return new CanonicalFieldRef(canonicalPath);
193
+ /**
194
+ * Primary factory function for CanonicalFieldRef
195
+ * Single source of truth across compiler, transformer, and runtime validator.
196
+ */
197
+ function canonicalField(rawPath, options) {
198
+ return new CanonicalFieldRef(rawPath, options);
199
+ }
200
+
201
+ /**
202
+ * Backward-compatible alias
203
+ */
204
+ function fieldRef(canonicalPath, options) {
205
+ return canonicalField(canonicalPath, options);
88
206
  }
89
207
 
90
208
  module.exports = {
91
209
  CanonicalFieldRef,
210
+ canonicalField,
92
211
  fieldRef,
93
212
  isValidCanonicalPath,
94
213
  validatePathAlignment,