@deneb-ui/cli 2.0.41 → 2.0.42

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.
package/bin/index.js CHANGED
@@ -1021,6 +1021,8 @@ if (command === 'init') {
1021
1021
  let explain = false;
1022
1022
  let legacy = false;
1023
1023
  let telemetry = 'off';
1024
+ let aiEnabled = false;
1025
+ let aiDryRun = false;
1024
1026
  for (let i = 0; i < commandArgs.length; i++) {
1025
1027
  const arg = commandArgs[i];
1026
1028
  if (arg === '--recipe' || arg === '-r') {
@@ -1033,6 +1035,11 @@ if (command === 'init') {
1033
1035
  explain = true;
1034
1036
  } else if (arg === '--legacy') {
1035
1037
  legacy = true;
1038
+ } else if (arg === '--ai') {
1039
+ aiEnabled = true;
1040
+ } else if (arg === '--ai-dry-run') {
1041
+ aiEnabled = true;
1042
+ aiDryRun = true;
1036
1043
  } else if (arg === '--telemetry' && commandArgs[i + 1]) {
1037
1044
  telemetry = commandArgs[++i];
1038
1045
  } else if (arg.startsWith('--telemetry=')) {
@@ -1041,7 +1048,7 @@ if (command === 'init') {
1041
1048
  targetInput = arg;
1042
1049
  }
1043
1050
  }
1044
- initProject(targetInput, { recipeName, dryRun, explain, legacy, telemetry });
1051
+ initProject(targetInput, { recipeName, dryRun, explain, legacy, telemetry, aiEnabled, aiDryRun });
1045
1052
  } else if (command === 'create') {
1046
1053
  createTemplate(commandArgs[0]);
1047
1054
  } else if (command === 'add') {
@@ -1108,7 +1115,7 @@ if (command === 'init') {
1108
1115
 
1109
1116
  Core Commands:
1110
1117
  init Deneb ARC: convert an existing React/Next.js app into a Fivora-editable storefront
1111
- flags: --dry-run --explain --recipe <name> --legacy --telemetry off|anonymous|enhanced
1118
+ flags: --dry-run --explain --recipe <name> --legacy --ai --ai-dry-run --telemetry off|anonymous|enhanced
1112
1119
  doctor Run comprehensive environment, manifest, visual editing AST & asset diagnostic checks (flags: --fix, --json)
1113
1120
  save-recipe Learn and save calibrated fixes & schemas into reusable recipe bank (e.g. deneb save-recipe . shoes-store)
1114
1121
  learn Alias for save-recipe
@@ -1136,6 +1143,8 @@ Examples:
1136
1143
  deneb init --recipe electronics
1137
1144
  deneb init --recipe cosmetics
1138
1145
  deneb init --legacy
1146
+ deneb init --ai
1147
+ deneb init --ai-dry-run
1139
1148
  deneb fonts list
1140
1149
  deneb fonts install .
1141
1150
  deneb fonts install . --font inter --font playfair-display
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/cli",
3
- "version": "2.0.41",
3
+ "version": "2.0.42",
4
4
  "description": "Official DENEB CLI — scaffold, convert, validate, and package Fivora-ready storefront templates.",
5
5
  "bin": {
6
6
  "deneb": "bin/index.js",
@@ -49,8 +49,10 @@
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
51
  "@babel/parser": "^7.28.0",
52
- "@deneb-ui/core": "^2.0.41",
52
+ "@deneb-ui/core": "^2.0.42",
53
+ "@octokit/rest": "^22.0.1",
53
54
  "adm-zip": "^0.6.0",
55
+ "dotenv": "^17.4.2",
54
56
  "recast": "^0.23.11"
55
57
  },
56
58
  "devDependencies": {
@@ -0,0 +1,328 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deneb ARC — AI Agent
5
+ *
6
+ * OpenAI API integration with self-healing validation loop.
7
+ * Generates editable wrapper components for unknown UI components
8
+ * and validates them through ARC's strict pipeline before accepting.
9
+ *
10
+ * Default engine: GPT-4o-mini (configurable via OPENAI_CONTENT_MODEL).
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { parseSource } = require('./ast.cjs');
16
+ const { buildComponentPrompt, buildFixPrompt, buildDocsPrompt } = require('./ai-prompts.cjs');
17
+
18
+ const MAX_RETRIES = 3;
19
+
20
+ /**
21
+ * Load AI configuration from environment variables.
22
+ * Call loadEnv() before using this if .env is not loaded yet.
23
+ */
24
+ function getAiConfig() {
25
+ return {
26
+ apiKey: process.env.OPENAI_API_KEY || '',
27
+ model: process.env.OPENAI_CONTENT_MODEL || 'gpt-4o-mini',
28
+ maxTokens: parseInt(process.env.OPENAI_MAX_OUTPUT_TOKENS || '4000', 10),
29
+ timeoutMs: parseInt(process.env.OPENAI_CONTENT_TIMEOUT_MS || '120000', 10),
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Load .env file purely locally.
35
+ * Searches upward from targetDir or process.cwd(), supporting local .env and .env.local files.
36
+ * Eliminates the need for any global system configuration.
37
+ *
38
+ * @param {string} [startDir] - Starting directory (defaults to process.cwd())
39
+ */
40
+ function loadEnv(startDir) {
41
+ const checked = new Set();
42
+ const candidates = [];
43
+
44
+ let curr = path.resolve(startDir || process.cwd());
45
+ for (let i = 0; i < 5; i++) {
46
+ candidates.push(path.join(curr, '.env'));
47
+ candidates.push(path.join(curr, '.env.local'));
48
+ candidates.push(path.join(curr, 'core', '.env'));
49
+ const parent = path.dirname(curr);
50
+ if (parent === curr) break;
51
+ curr = parent;
52
+ }
53
+
54
+ candidates.push(path.resolve(__dirname, '..', '..', '..', '..', '.env'));
55
+ candidates.push(path.resolve(__dirname, '..', '..', '.env'));
56
+
57
+ for (const envPath of candidates) {
58
+ if (!checked.has(envPath) && fs.existsSync(envPath)) {
59
+ try {
60
+ require('dotenv').config({ path: envPath });
61
+ } catch {
62
+ // dotenv not available — env vars must be set manually
63
+ }
64
+ return;
65
+ }
66
+ checked.add(envPath);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Check if the AI agent is properly configured and ready to use.
72
+ * @returns {{ ready: boolean, reason?: string }}
73
+ */
74
+ function checkAiReady() {
75
+ loadEnv();
76
+ const config = getAiConfig();
77
+ if (!config.apiKey) {
78
+ return { ready: false, reason: 'OPENAI_API_KEY not set. Create a .env file (see .env.example) or set the environment variable.' };
79
+ }
80
+ if (!config.apiKey.startsWith('sk-')) {
81
+ return { ready: false, reason: 'OPENAI_API_KEY does not look valid (should start with sk-).' };
82
+ }
83
+ return { ready: true };
84
+ }
85
+
86
+ /**
87
+ * Call the OpenAI Chat Completions API.
88
+ *
89
+ * Uses native fetch (Node 18+) — no external HTTP library needed.
90
+ *
91
+ * @param {string} prompt - The user prompt
92
+ * @param {object} [opts] - Optional overrides
93
+ * @returns {Promise<{ content: string, inputTokens: number, outputTokens: number }>}
94
+ */
95
+ async function callOpenAI(prompt, opts = {}) {
96
+ const config = getAiConfig();
97
+ const model = opts.model || config.model;
98
+ const maxTokens = opts.maxTokens || config.maxTokens;
99
+ const timeoutMs = opts.timeoutMs || config.timeoutMs;
100
+
101
+ const controller = new AbortController();
102
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
103
+
104
+ try {
105
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ 'Authorization': `Bearer ${config.apiKey}`,
110
+ },
111
+ body: JSON.stringify({
112
+ model,
113
+ messages: [
114
+ { role: 'system', content: 'You are a senior React/TypeScript developer specializing in the DENEB UI framework. Output only raw code — no markdown, no explanations.' },
115
+ { role: 'user', content: prompt },
116
+ ],
117
+ max_tokens: maxTokens,
118
+ temperature: 0.2,
119
+ }),
120
+ signal: controller.signal,
121
+ });
122
+
123
+ if (!response.ok) {
124
+ const errorBody = await response.text().catch(() => '');
125
+ throw new Error(`OpenAI API error ${response.status}: ${errorBody.slice(0, 200)}`);
126
+ }
127
+
128
+ const data = await response.json();
129
+ const choice = data.choices?.[0];
130
+ if (!choice || !choice.message?.content) {
131
+ throw new Error('OpenAI returned empty response — no content in choices[0].message');
132
+ }
133
+
134
+ let content = choice.message.content.trim();
135
+ // Strip markdown fences if the model wraps output despite instructions
136
+ content = content.replace(/^```(?:tsx?|jsx?|typescript|javascript)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
137
+
138
+ return {
139
+ content,
140
+ inputTokens: data.usage?.prompt_tokens || 0,
141
+ outputTokens: data.usage?.completion_tokens || 0,
142
+ };
143
+ } finally {
144
+ clearTimeout(timer);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Validate generated component code using ARC's validators.
150
+ *
151
+ * @param {string} code - The generated TypeScript/JSX code
152
+ * @param {string} filename - Virtual filename for error messages
153
+ * @returns {{ passed: boolean, errors: string[] }}
154
+ */
155
+ function validateGeneratedCode(code, filename) {
156
+ const errors = [];
157
+
158
+ // 1. AST syntax check
159
+ try {
160
+ parseSource(code, filename);
161
+ } catch (err) {
162
+ errors.push(`AST syntax error: ${err.message}`);
163
+ }
164
+
165
+ // 2. Must have data-preview-field-path markers
166
+ if (!code.includes('data-preview-field-path')) {
167
+ errors.push('Missing data-preview-field-path markers — the component has no editable fields.');
168
+ }
169
+
170
+ // 3. Must have data-preview-item-path on the root wrapper
171
+ if (!code.includes('data-preview-item-path')) {
172
+ errors.push('Missing data-preview-item-path on root wrapper element.');
173
+ }
174
+
175
+ // 4. Must have proper TypeScript exports
176
+ if (!code.includes('export function') && !code.includes('export const')) {
177
+ errors.push('Missing named export — component must use `export function Editable...()`.');
178
+ }
179
+
180
+ // 5. Must have TypeScript interface
181
+ if (!code.includes('export interface')) {
182
+ errors.push('Missing exported TypeScript interface for component props.');
183
+ }
184
+
185
+ // 6. Must import EditableText or EditableImage (at least one)
186
+ if (!code.includes("from './EditableText'") && !code.includes("from './EditableImage'")) {
187
+ errors.push('Must import at least EditableText or EditableImage from relative path.');
188
+ }
189
+
190
+ // 7. Must NOT have default export (Deneb convention)
191
+ if (/export\s+default\s+/m.test(code)) {
192
+ errors.push('Must NOT use default export — use named export only (Deneb convention).');
193
+ }
194
+
195
+ // 8. Check for forbidden patterns
196
+ if (code.includes('useState') || code.includes('useEffect') || code.includes('useContext')) {
197
+ errors.push('Must NOT import React hooks (useState, useEffect, useContext). Component must be stateless.');
198
+ }
199
+
200
+ if (code.includes('useSiteData')) {
201
+ errors.push('Must NOT import useSiteData directly. The wrapper uses EditableText/EditableImage primitives instead.');
202
+ }
203
+
204
+ return {
205
+ passed: errors.length === 0,
206
+ errors,
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Extract the list of editable field paths from generated component code.
212
+ *
213
+ * @param {string} code - The generated component code
214
+ * @returns {string[]} Array of field path patterns found
215
+ */
216
+ function extractFieldPaths(code) {
217
+ const matches = code.matchAll(/data-preview-field-path=\{[`"']([^`"']+)[`"']\}/g);
218
+ const paths = new Set();
219
+ for (const m of matches) {
220
+ // Normalize template literal patterns like `${itemPath}.name`
221
+ const path = m[1].replace(/\$\{[^}]+\}/g, '*');
222
+ paths.add(path);
223
+ }
224
+ return [...paths];
225
+ }
226
+
227
+ /**
228
+ * Main AI adaptation function with self-healing validation loop.
229
+ *
230
+ * @param {string} sourceCode - The unknown component's source code
231
+ * @param {string} componentName - The component name (e.g. "Carousel")
232
+ * @param {object} profile - ARC project profile
233
+ * @param {object} [callbacks] - Optional progress callbacks
234
+ * @param {function} [callbacks.onAttempt] - Called at start of each attempt: (attemptNum, maxRetries)
235
+ * @param {function} [callbacks.onValidationFail] - Called when validation fails: (errors, attemptNum)
236
+ * @param {function} [callbacks.onSuccess] - Called on success: (attemptNum)
237
+ * @returns {Promise<{ success: boolean, code?: string, fields?: string[], attempts: number, totalInputTokens: number, totalOutputTokens: number, error?: string }>}
238
+ */
239
+ async function adaptComponent(sourceCode, componentName, profile, callbacks = {}) {
240
+ let totalInputTokens = 0;
241
+ let totalOutputTokens = 0;
242
+
243
+ // Generate initial code
244
+ const prompt = buildComponentPrompt(sourceCode, componentName, profile);
245
+ let lastCode = '';
246
+ let lastErrors = [];
247
+
248
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
249
+ if (callbacks.onAttempt) callbacks.onAttempt(attempt, MAX_RETRIES);
250
+
251
+ try {
252
+ let result;
253
+ if (attempt === 1) {
254
+ result = await callOpenAI(prompt);
255
+ } else {
256
+ const fixPrompt = buildFixPrompt(lastCode, lastErrors.join('\n'), attempt);
257
+ result = await callOpenAI(fixPrompt);
258
+ }
259
+
260
+ totalInputTokens += result.inputTokens;
261
+ totalOutputTokens += result.outputTokens;
262
+ lastCode = result.content;
263
+
264
+ // Validate the generated code
265
+ const filename = `Editable${componentName}.tsx`;
266
+ const validation = validateGeneratedCode(lastCode, filename);
267
+
268
+ if (validation.passed) {
269
+ if (callbacks.onSuccess) callbacks.onSuccess(attempt);
270
+ return {
271
+ success: true,
272
+ code: lastCode,
273
+ fields: extractFieldPaths(lastCode),
274
+ attempts: attempt,
275
+ totalInputTokens,
276
+ totalOutputTokens,
277
+ };
278
+ }
279
+
280
+ // Validation failed — prepare for retry
281
+ lastErrors = validation.errors;
282
+ if (callbacks.onValidationFail) callbacks.onValidationFail(validation.errors, attempt);
283
+ } catch (err) {
284
+ lastErrors = [err.message];
285
+ if (callbacks.onValidationFail) callbacks.onValidationFail([err.message], attempt);
286
+ }
287
+ }
288
+
289
+ // All retries exhausted
290
+ return {
291
+ success: false,
292
+ attempts: MAX_RETRIES,
293
+ totalInputTokens,
294
+ totalOutputTokens,
295
+ error: `Failed after ${MAX_RETRIES} attempts. Last errors:\n${lastErrors.join('\n')}`,
296
+ };
297
+ }
298
+
299
+ /**
300
+ * Generate documentation page for a successfully adapted component.
301
+ *
302
+ * @param {string} componentName - e.g. "Carousel"
303
+ * @param {string} componentCode - The validated Editable*.tsx source
304
+ * @param {string[]} editableFields - List of field paths
305
+ * @returns {Promise<{ success: boolean, code?: string, error?: string }>}
306
+ */
307
+ async function generateDocsPage(componentName, componentCode, editableFields) {
308
+ const prompt = buildDocsPrompt(componentName, componentCode, editableFields);
309
+
310
+ try {
311
+ const result = await callOpenAI(prompt);
312
+ return { success: true, code: result.content };
313
+ } catch (err) {
314
+ return { success: false, error: err.message };
315
+ }
316
+ }
317
+
318
+ module.exports = {
319
+ loadEnv,
320
+ checkAiReady,
321
+ getAiConfig,
322
+ callOpenAI,
323
+ validateGeneratedCode,
324
+ extractFieldPaths,
325
+ adaptComponent,
326
+ generateDocsPage,
327
+ MAX_RETRIES,
328
+ };
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deneb ARC — AI Prompt Templates
5
+ *
6
+ * Carefully engineered prompt templates that teach GPT-4o-mini the exact
7
+ * Deneb editable component conventions. Uses a real EditableServiceCard
8
+ * as a reference pattern so the AI learns the correct structure.
9
+ */
10
+
11
+ // ─── Reference component (shortened for token efficiency) ────────
12
+ const REFERENCE_COMPONENT = `
13
+ import React from 'react';
14
+ import { EditableText } from './EditableText';
15
+ import { EditableImage } from './EditableImage';
16
+
17
+ export interface ServiceItem {
18
+ id?: string | number;
19
+ name?: string;
20
+ title?: string;
21
+ price?: string | number;
22
+ description?: string;
23
+ imageUrl?: string;
24
+ image?: string;
25
+ features?: unknown[];
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ export interface EditableServiceCardProps extends React.HTMLAttributes<HTMLElement> {
30
+ itemPath: string;
31
+ service: ServiceItem;
32
+ imageFallback?: string;
33
+ as?: React.ElementType;
34
+ align?: 'left' | 'center' | 'right';
35
+ showPrice?: boolean;
36
+ }
37
+
38
+ export function EditableServiceCard({
39
+ itemPath,
40
+ service,
41
+ imageFallback = '/placeholder.svg',
42
+ as: Component = 'article',
43
+ align = 'left',
44
+ showPrice = true,
45
+ className = '',
46
+ style,
47
+ children,
48
+ ...props
49
+ }: EditableServiceCardProps) {
50
+ const name = String(service?.name || service?.title || '');
51
+ const description = String(service?.description || '');
52
+ const imageUrl = String(service?.imageUrl || service?.image || '');
53
+
54
+ return (
55
+ <Component
56
+ data-preview-item-path={itemPath}
57
+ className={\`editable-service-card \${className}\`.trim()}
58
+ style={{ textAlign: align, ...style }}
59
+ {...(props as any)}
60
+ >
61
+ <div className="service-card-image-wrap">
62
+ <EditableImage
63
+ id={\`\${itemPath}.imageUrl\`}
64
+ data-preview-field-path={\`\${itemPath}.imageUrl\`}
65
+ src={imageUrl}
66
+ fallbackSrc={imageFallback}
67
+ alt={name}
68
+ className="service-card-image"
69
+ />
70
+ </div>
71
+ <div className="service-card-body">
72
+ <EditableText
73
+ as="h2"
74
+ id={\`\${itemPath}.name\`}
75
+ data-preview-field-path={\`\${itemPath}.name\`}
76
+ defaultValue={name}
77
+ className="service-card-title"
78
+ />
79
+ <EditableText
80
+ as="p"
81
+ id={\`\${itemPath}.description\`}
82
+ data-preview-field-path={\`\${itemPath}.description\`}
83
+ defaultValue={description}
84
+ className="service-card-description"
85
+ />
86
+ {children}
87
+ </div>
88
+ </Component>
89
+ );
90
+ }
91
+ `.trim();
92
+
93
+ /**
94
+ * Build the main component generation prompt.
95
+ *
96
+ * @param {string} sourceCode - The unknown component's source code
97
+ * @param {string} componentName - The component's name (e.g. "Carousel")
98
+ * @param {object} profile - ARC project profile (framework, libraries, etc.)
99
+ * @returns {string} The complete prompt for OpenAI
100
+ */
101
+ function buildComponentPrompt(sourceCode, componentName, profile) {
102
+ return `You are an expert React/TypeScript developer working on the DENEB UI framework.
103
+ Your task is to create an editable wrapper component for an existing component.
104
+
105
+ ## RULES (MANDATORY — violating any rule means the output is rejected):
106
+
107
+ 1. **Import only from relative paths**: Use \`./EditableText\` and \`./EditableImage\` (these are the ONLY two Deneb primitives you may use).
108
+ 2. **Every user-visible text** must be wrapped in \`<EditableText>\` with:
109
+ - \`as\` prop matching the original HTML tag (h1, h2, p, span, etc.)
110
+ - \`id={\`\${itemPath}.fieldName\`}\`
111
+ - \`data-preview-field-path={\`\${itemPath}.fieldName\`}\`
112
+ - \`defaultValue={value}\`
113
+ 3. **Every user-visible image** must use \`<EditableImage>\` with:
114
+ - \`id={\`\${itemPath}.fieldName\`}\`
115
+ - \`data-preview-field-path={\`\${itemPath}.fieldName\`}\`
116
+ - \`src={value}\`
117
+ - \`fallbackSrc={imageFallback}\`
118
+ 4. **Lists/arrays** must have \`data-preview-list-path\` on the container and \`data-preview-item-path\` on each item.
119
+ 5. **Root wrapper** must have \`data-preview-item-path={itemPath}\`.
120
+ 6. **Export a TypeScript interface** named \`Editable${componentName}Props\` extending \`React.HTMLAttributes<HTMLElement>\`.
121
+ 7. **Export a named function** (not default export) named \`Editable${componentName}\`.
122
+ 8. **Preserve all original CSS classes and styling** — do NOT remove or change className values.
123
+ 9. **Props must include**: \`itemPath: string\` (required), data object, \`as?: React.ElementType\`, \`className?\`, \`style?\`, \`children?\`.
124
+ 10. **Use \`'use strict'\` is NOT needed** — this is a .tsx file.
125
+ 11. **Do NOT import React hooks** like useState, useEffect, useContext. The component must be stateless.
126
+ 12. **Do NOT import useSiteData** — the wrapper component does not need it directly.
127
+
128
+ ## REFERENCE PATTERN (follow this structure exactly):
129
+
130
+ \`\`\`tsx
131
+ ${REFERENCE_COMPONENT}
132
+ \`\`\`
133
+
134
+ ## SOURCE COMPONENT TO ADAPT:
135
+
136
+ Component name: ${componentName}
137
+ Framework: ${profile.framework || 'nextjs'}
138
+ Libraries: ${(profile.componentLibraries || []).join(', ') || 'none detected'}
139
+
140
+ \`\`\`tsx
141
+ ${sourceCode}
142
+ \`\`\`
143
+
144
+ ## YOUR OUTPUT:
145
+
146
+ Generate ONLY the complete TypeScript (.tsx) file content for \`Editable${componentName}.tsx\`.
147
+ Do NOT include markdown fences, explanations, or comments outside the code.
148
+ Output the raw TypeScript code directly.`;
149
+ }
150
+
151
+ /**
152
+ * Build the error-fix prompt when the validator rejects AI-generated code.
153
+ *
154
+ * @param {string} previousCode - The code that failed validation
155
+ * @param {string} errorMessage - The exact validator error
156
+ * @param {number} attempt - Current attempt number
157
+ * @returns {string} The fix prompt
158
+ */
159
+ function buildFixPrompt(previousCode, errorMessage, attempt) {
160
+ return `The DENEB ARC validator REJECTED your generated component (attempt ${attempt}/3).
161
+
162
+ ## EXACT ERROR:
163
+ ${errorMessage}
164
+
165
+ ## YOUR PREVIOUS CODE:
166
+ \`\`\`tsx
167
+ ${previousCode}
168
+ \`\`\`
169
+
170
+ ## INSTRUCTIONS:
171
+ 1. Fix ONLY the specific error described above.
172
+ 2. Do NOT change anything else — preserve all existing field paths, markers, and class names.
173
+ 3. Output the COMPLETE corrected .tsx file (not a diff, not a snippet — the full file).
174
+ 4. Do NOT include markdown fences or explanations. Output raw TypeScript code only.`;
175
+ }
176
+
177
+ /**
178
+ * Build the docs page generation prompt.
179
+ *
180
+ * @param {string} componentName - e.g. "Carousel"
181
+ * @param {string} componentCode - The validated EditableCarousel.tsx source
182
+ * @param {string[]} editableFields - List of field paths discovered
183
+ * @returns {string} The docs prompt
184
+ */
185
+ function buildDocsPrompt(componentName, componentCode, editableFields) {
186
+ return `Generate a Next.js documentation page for the DENEB UI component "Editable${componentName}".
187
+
188
+ ## COMPONENT CODE:
189
+ \`\`\`tsx
190
+ ${componentCode}
191
+ \`\`\`
192
+
193
+ ## EDITABLE FIELDS:
194
+ ${editableFields.map((f) => `- ${f}`).join('\n')}
195
+
196
+ ## OUTPUT FORMAT:
197
+ Generate a React component that exports a default function named \`Editable${componentName}DocsPage\`.
198
+ It should render:
199
+ 1. A title section with the component name
200
+ 2. A description paragraph explaining what the component does
201
+ 3. A props table showing all available props with their types and defaults
202
+ 4. A usage example code block
203
+ 5. A list of editable field paths
204
+
205
+ Use plain HTML/JSX with className for styling. Do NOT import any external UI libraries.
206
+ Output raw TSX code only — no markdown fences or explanations.`;
207
+ }
208
+
209
+ module.exports = {
210
+ REFERENCE_COMPONENT,
211
+ buildComponentPrompt,
212
+ buildFixPrompt,
213
+ buildDocsPrompt,
214
+ };
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deneb ARC — Component Registry
5
+ *
6
+ * Master lookup of all known editable components in @deneb-ui/ui.
7
+ * Used by the AI agent to classify "known vs unknown" components
8
+ * during `deneb init --ai`.
9
+ */
10
+
11
+ const KNOWN_EDITABLE_COMPONENTS = new Set([
12
+ // ─── Core Text & Primitives ────────────────────────────────────
13
+ 'EditableText',
14
+ 'EditableHeading',
15
+ 'EditableParagraph',
16
+ 'EditableBadge',
17
+ 'EditableQuote',
18
+ 'EditableButton',
19
+ 'EditableImage',
20
+ 'EditableMap',
21
+ 'EditableList',
22
+ 'EditableBox',
23
+ 'EditableGrid',
24
+ 'EditableSection',
25
+ 'EditableDialog',
26
+
27
+ // ─── Product & Commerce ────────────────────────────────────────
28
+ 'EditableProductCard',
29
+ 'EditableProductGrid',
30
+ 'EditableProductDetail',
31
+ 'EditableCartDrawer',
32
+ 'EditableFilterSidebar',
33
+ 'EditablePricingCard',
34
+ 'ProductQuickView',
35
+
36
+ // ─── Content & Social Proof ────────────────────────────────────
37
+ 'EditableServiceCard',
38
+ 'EditableCard',
39
+ 'EditableTestimonialCard',
40
+ 'EditableTestimonialSection',
41
+ 'EditableCustomerReviews',
42
+ 'EditableGoogleFeedback',
43
+ 'EditableFAQAccordion',
44
+ 'EditableContactForm',
45
+
46
+ // ─── Layout & Navigation ───────────────────────────────────────
47
+ 'EditableNavbar',
48
+ 'EditableFooter',
49
+ 'EditableHero',
50
+ 'EditableHeroCentered',
51
+ 'EditableHeroSplit',
52
+ 'EditableAnnouncementBar',
53
+ 'EditableCategoryPills',
54
+ 'StickyMobileBar',
55
+ 'TrustBadges',
56
+ 'CookieConsentBanner',
57
+
58
+ // ─── Infrastructure ────────────────────────────────────────────
59
+ 'SiteDataProvider',
60
+ 'ThemeStyles',
61
+ 'ResponsiveBaseStyles',
62
+ 'DenebComponentStyles',
63
+ 'FontLoader',
64
+ 'PreviewField',
65
+ ]);
66
+
67
+ /**
68
+ * Canonical short-name aliases that Shadcn/HeroUI developers might use.
69
+ * Maps common short names to their Deneb equivalents.
70
+ */
71
+ const ALIAS_MAP = {
72
+ 'Button': 'EditableButton',
73
+ 'Card': 'EditableCard',
74
+ 'Dialog': 'EditableDialog',
75
+ 'Text': 'EditableText',
76
+ 'Heading': 'EditableHeading',
77
+ 'Paragraph': 'EditableParagraph',
78
+ 'Badge': 'EditableBadge',
79
+ 'Quote': 'EditableQuote',
80
+ 'Image': 'EditableImage',
81
+ 'Map': 'EditableMap',
82
+ 'Grid': 'EditableGrid',
83
+ 'Section': 'EditableSection',
84
+ 'Box': 'EditableBox',
85
+ 'List': 'EditableList',
86
+ 'ProductCard': 'EditableProductCard',
87
+ 'ProductGrid': 'EditableProductGrid',
88
+ 'ProductDetail': 'EditableProductDetail',
89
+ 'CustomerReviews': 'EditableCustomerReviews',
90
+ 'GoogleFeedback': 'EditableGoogleFeedback',
91
+ 'ServiceCard': 'EditableServiceCard',
92
+ 'PricingCard': 'EditablePricingCard',
93
+ 'TestimonialCard': 'EditableTestimonialCard',
94
+ 'TestimonialSection': 'EditableTestimonialSection',
95
+ 'Testimonials': 'EditableTestimonialSection',
96
+ 'FAQ': 'EditableFAQAccordion',
97
+ 'Accordion': 'EditableFAQAccordion',
98
+ 'ContactForm': 'EditableContactForm',
99
+ 'Navbar': 'EditableNavbar',
100
+ 'Header': 'EditableNavbar',
101
+ 'Footer': 'EditableFooter',
102
+ 'Hero': 'EditableHeroCentered',
103
+ 'HeroSplit': 'EditableHeroSplit',
104
+ 'AnnouncementBar': 'EditableAnnouncementBar',
105
+ 'CategoryPills': 'EditableCategoryPills',
106
+ 'CartDrawer': 'EditableCartDrawer',
107
+ 'FilterSidebar': 'EditableFilterSidebar',
108
+ };
109
+
110
+ /**
111
+ * Check if a component name is already covered by @deneb-ui/ui.
112
+ * Checks both the full Editable* name and common aliases.
113
+ */
114
+ function isKnownComponent(name) {
115
+ if (!name || typeof name !== 'string') return false;
116
+ if (KNOWN_EDITABLE_COMPONENTS.has(name)) return true;
117
+ if (ALIAS_MAP[name]) return true;
118
+ // Also check if user passed the Editable-prefixed version
119
+ if (KNOWN_EDITABLE_COMPONENTS.has(`Editable${name}`)) return true;
120
+ return false;
121
+ }
122
+
123
+ /**
124
+ * Returns the list of all known editable component names.
125
+ */
126
+ function getKnownComponentNames() {
127
+ return [...KNOWN_EDITABLE_COMPONENTS];
128
+ }
129
+
130
+ /**
131
+ * Given a list of discovered component names from a project scan,
132
+ * returns { known: string[], unknown: string[] }.
133
+ */
134
+ function classifyComponents(componentNames) {
135
+ const known = [];
136
+ const unknown = [];
137
+ const seen = new Set();
138
+
139
+ for (const name of componentNames) {
140
+ if (seen.has(name)) continue;
141
+ seen.add(name);
142
+ if (isKnownComponent(name)) {
143
+ known.push(name);
144
+ } else {
145
+ unknown.push(name);
146
+ }
147
+ }
148
+
149
+ return { known, unknown };
150
+ }
151
+
152
+ module.exports = {
153
+ KNOWN_EDITABLE_COMPONENTS,
154
+ ALIAS_MAP,
155
+ isKnownComponent,
156
+ getKnownComponentNames,
157
+ classifyComponents,
158
+ };
package/src/arc/index.cjs CHANGED
@@ -36,6 +36,9 @@ const {
36
36
  findUncoveredVisibleText,
37
37
  } = require('./fivora-contract.cjs');
38
38
  const printer = require('./printer.cjs');
39
+ const { classifyComponents } = require('./component-registry.cjs');
40
+ const { checkAiReady, adaptComponent, generateDocsPage, loadEnv } = require('./ai-agent.cjs');
41
+ const { checkGithubReady, createComponentPR } = require('./pr-agent.cjs');
39
42
 
40
43
  function parseArcOptions(raw = {}) {
41
44
  return {
@@ -46,6 +49,8 @@ function parseArcOptions(raw = {}) {
46
49
  skipInstall: Boolean(raw.skipInstall),
47
50
  detectedPages: raw.detectedPages || null,
48
51
  json: Boolean(raw.json),
52
+ aiEnabled: Boolean(raw.aiEnabled),
53
+ aiDryRun: Boolean(raw.aiDryRun),
49
54
  };
50
55
  }
51
56
 
@@ -224,7 +229,7 @@ function analyzeProjectFiles(profile, graph) {
224
229
  return analyses;
225
230
  }
226
231
 
227
- function runDenebArc(projectDir, projectName, options = {}) {
232
+ async function runDenebArcAsync(projectDir, projectName, options = {}) {
228
233
  const opts = parseArcOptions(options);
229
234
  const runId = createRunId();
230
235
  const startedAt = new Date().toISOString();
@@ -255,6 +260,133 @@ function runDenebArc(projectDir, projectName, options = {}) {
255
260
  );
256
261
  printer.printScan(profile, graph, candidateCount, actionCount);
257
262
 
263
+ // ─── AI Agent: Classify & Adapt Unknown Components ──────────
264
+ let aiResults = [];
265
+ loadEnv(projectDir);
266
+ const aiCheck = checkAiReady();
267
+ if (!aiCheck.ready) {
268
+ printer.warn(`AI Agent disabled: ${aiCheck.reason}`);
269
+ } else {
270
+ const componentNames = (profile.components || []).map((c) => c.name || c.tag).filter(Boolean);
271
+ const { unknown } = classifyComponents(componentNames);
272
+
273
+ if (unknown.length > 0) {
274
+ printer.printAiDetected(unknown.length);
275
+
276
+ for (const componentName of unknown) {
277
+ const meta = (profile.components || []).find((c) => (c.name || c.tag) === componentName);
278
+ const absFile = meta?.file ? path.join(projectDir, meta.file) : null;
279
+ let sourceCode = '';
280
+ if (absFile && fs.existsSync(absFile)) {
281
+ try { sourceCode = fs.readFileSync(absFile, 'utf8'); } catch { /* skip */ }
282
+ }
283
+ if (!sourceCode) {
284
+ printer.printAiSkipped(componentName, 'could not read source file');
285
+ continue;
286
+ }
287
+
288
+ try {
289
+ const aiResult = await adaptComponent(sourceCode, componentName, profile, {
290
+ onAttempt: (attempt, max) => printer.printAiAttempt(componentName, attempt, max),
291
+ onValidationFail: (errors, attempt) => printer.printAiValidationFail(errors, attempt),
292
+ onSuccess: (attempt) => printer.printAiSuccess(componentName, attempt, 0),
293
+ });
294
+
295
+ if (aiResult.success) {
296
+ aiResults.push({ componentName, ...aiResult });
297
+ printer.printAiSuccess(componentName, aiResult.attempts, aiResult.fields.length);
298
+ } else {
299
+ printer.printAiSkipped(componentName, aiResult.error || 'failed after max retries');
300
+ }
301
+ } catch (err) {
302
+ printer.printAiSkipped(componentName, err.message);
303
+ }
304
+ }
305
+
306
+ // Background: Open PRs for successfully adapted components
307
+ const ghCheck = checkGithubReady();
308
+ if (ghCheck.ready && aiResults.length > 0) {
309
+ for (const aiResult of aiResults) {
310
+ try {
311
+ const docsResult = await generateDocsPage(aiResult.componentName, aiResult.code, aiResult.fields);
312
+ const prResult = await createComponentPR({
313
+ componentName: aiResult.componentName,
314
+ componentCode: aiResult.code,
315
+ docsCode: docsResult.success ? docsResult.code : null,
316
+ editableFields: aiResult.fields,
317
+ attempts: aiResult.attempts,
318
+ totalInputTokens: aiResult.totalInputTokens,
319
+ totalOutputTokens: aiResult.totalOutputTokens,
320
+ dryRun: opts.aiDryRun,
321
+ });
322
+ if (prResult.corePr) {
323
+ printer.printAiPr('chamikathereal/core → deneb-ui/core', `feat/ai-editable-${aiResult.componentName.toLowerCase()}`, prResult.corePr.number);
324
+ }
325
+ if (prResult.uiPr) {
326
+ printer.printAiPr('chamikathereal/ui → deneb-ui/ui', `feat/ai-docs-${aiResult.componentName.toLowerCase()}`, prResult.uiPr.number);
327
+ }
328
+ for (const err of prResult.errors) {
329
+ printer.warn(`PR: ${err}`);
330
+ }
331
+ } catch (err) {
332
+ printer.warn(`PR creation failed for ${aiResult.componentName}: ${err.message}`);
333
+ }
334
+ }
335
+ } else if (!ghCheck.ready && aiResults.length > 0) {
336
+ printer.warn(`GitHub PRs skipped: ${ghCheck.reason}`);
337
+ }
338
+
339
+ const totalTokens = aiResults.reduce((n, r) => n + r.totalInputTokens + r.totalOutputTokens, 0);
340
+ printer.printAiSummary(aiResults.length, unknown.length - aiResults.length, totalTokens);
341
+ }
342
+ }
343
+
344
+ return runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt);
345
+ }
346
+
347
+ function runDenebArcSync(projectDir, projectName, options = {}) {
348
+ const opts = parseArcOptions(options);
349
+ const runId = createRunId();
350
+ const startedAt = new Date().toISOString();
351
+
352
+ printer.printBanner(opts.dryRun ? 'dry-run' : opts.explain ? 'explain' : 'run');
353
+
354
+ const profile = scanProject(projectDir);
355
+ if (opts.detectedPages && Array.isArray(opts.detectedPages) && opts.detectedPages.length) {
356
+ const scannedIds = new Set(profile.routes.map((r) => r.id));
357
+ for (const page of opts.detectedPages) {
358
+ if (page && page.id && !scannedIds.has(page.id)) {
359
+ profile.routes.push(page);
360
+ }
361
+ }
362
+ }
363
+
364
+ printer.printProfile(profile);
365
+ const graph = buildDependencyGraph(profile);
366
+ const analyses = analyzeProjectFiles(profile, graph);
367
+
368
+ const candidateCount = analyses.reduce(
369
+ (n, a) => n + (a.candidates || []).filter((c) => c.kind !== 'decoration' && c.kind !== 'already-editable' && !c.skip).length,
370
+ 0
371
+ );
372
+ const actionCount = analyses.reduce(
373
+ (n, a) => n + (a.candidates || []).filter((c) => c.operation === 'split-action-contract' || c.kind === 'url').length,
374
+ 0
375
+ );
376
+ printer.printScan(profile, graph, candidateCount, actionCount);
377
+
378
+ return runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt);
379
+ }
380
+
381
+ function runDenebArc(projectDir, projectName, options = {}) {
382
+ const opts = parseArcOptions(options);
383
+ if (opts.aiEnabled) {
384
+ return runDenebArcAsync(projectDir, projectName, options);
385
+ }
386
+ return runDenebArcSync(projectDir, projectName, options);
387
+ }
388
+
389
+ function runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt) {
258
390
  const sourceAbs = profile.jsxFiles.map((f) => path.join(projectDir, f));
259
391
  const recipeMatch = matchRecipeV2(projectDir, profile, sourceAbs, opts.recipeName);
260
392
  if (recipeMatch.recipe) {
@@ -0,0 +1,295 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deneb ARC — PR Agent
5
+ *
6
+ * GitHub API integration for automated Pull Request creation.
7
+ * Creates branches, commits files, and opens PRs from the user's
8
+ * personal fork into the upstream deneb-ui organization repos.
9
+ *
10
+ * All commits are authored under the configured GITHUB_USERNAME
11
+ * and GITHUB_EMAIL for full contribution credit.
12
+ */
13
+
14
+ const { loadEnv } = require('./ai-agent.cjs');
15
+
16
+ /**
17
+ * Get GitHub configuration from environment variables.
18
+ */
19
+ function getGithubConfig() {
20
+ return {
21
+ pat: process.env.GITHUB_PAT || '',
22
+ username: process.env.GITHUB_USERNAME || 'chamikathereal',
23
+ email: process.env.GITHUB_EMAIL || 'dmforceeg@gmail.com',
24
+ org: process.env.GITHUB_ORG || 'deneb-ui',
25
+ coreRepo: process.env.GITHUB_CORE_REPO || 'core',
26
+ uiRepo: process.env.GITHUB_UI_REPO || 'ui',
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Check if GitHub integration is ready.
32
+ * @returns {{ ready: boolean, reason?: string }}
33
+ */
34
+ function checkGithubReady() {
35
+ loadEnv();
36
+ const config = getGithubConfig();
37
+ if (!config.pat) {
38
+ return { ready: false, reason: 'GITHUB_PAT not set. Generate a Personal Access Token at https://github.com/settings/tokens with "repo" scope.' };
39
+ }
40
+ if (!config.pat.startsWith('ghp_') && !config.pat.startsWith('github_pat_')) {
41
+ return { ready: false, reason: 'GITHUB_PAT does not look valid (should start with ghp_ or github_pat_).' };
42
+ }
43
+ return { ready: true };
44
+ }
45
+
46
+ /**
47
+ * Create a GitHub API client using @octokit/rest.
48
+ * Returns null if the PAT is not configured.
49
+ */
50
+ function createOctokitClient() {
51
+ const config = getGithubConfig();
52
+ if (!config.pat) return null;
53
+
54
+ try {
55
+ const { Octokit } = require('@octokit/rest');
56
+ return new Octokit({ auth: config.pat });
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Get the SHA of the latest commit on a branch.
64
+ */
65
+ async function getLatestCommitSha(octokit, owner, repo, branch = 'main') {
66
+ const { data } = await octokit.repos.getBranch({
67
+ owner,
68
+ repo,
69
+ branch,
70
+ });
71
+ return data.commit.sha;
72
+ }
73
+
74
+ /**
75
+ * Create a new branch from the latest main.
76
+ */
77
+ async function createBranch(octokit, owner, repo, branchName, baseSha) {
78
+ await octokit.git.createRef({
79
+ owner,
80
+ repo,
81
+ ref: `refs/heads/${branchName}`,
82
+ sha: baseSha,
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Create or update a file in a repository on a specific branch.
88
+ */
89
+ async function createOrUpdateFile(octokit, owner, repo, branch, filePath, content, commitMessage) {
90
+ const config = getGithubConfig();
91
+
92
+ // Check if file already exists to get its SHA
93
+ let existingSha;
94
+ try {
95
+ const { data } = await octokit.repos.getContent({
96
+ owner,
97
+ repo,
98
+ path: filePath,
99
+ ref: branch,
100
+ });
101
+ existingSha = data.sha;
102
+ } catch {
103
+ // File does not exist — will create
104
+ }
105
+
106
+ const params = {
107
+ owner,
108
+ repo,
109
+ path: filePath,
110
+ message: commitMessage,
111
+ content: Buffer.from(content, 'utf8').toString('base64'),
112
+ branch,
113
+ committer: {
114
+ name: config.username,
115
+ email: config.email,
116
+ },
117
+ author: {
118
+ name: config.username,
119
+ email: config.email,
120
+ },
121
+ };
122
+ if (existingSha) params.sha = existingSha;
123
+
124
+ await octokit.repos.createOrUpdateFileContents(params);
125
+ }
126
+
127
+ /**
128
+ * Open a Pull Request from the user's fork to the upstream repo.
129
+ */
130
+ async function openPullRequest(octokit, { upstreamOwner, upstreamRepo, forkOwner, branch, title, body }) {
131
+ const { data } = await octokit.pulls.create({
132
+ owner: upstreamOwner,
133
+ repo: upstreamRepo,
134
+ title,
135
+ body,
136
+ head: `${forkOwner}:${branch}`,
137
+ base: 'main',
138
+ });
139
+ return { number: data.number, url: data.html_url };
140
+ }
141
+
142
+ /**
143
+ * Build a PR body with validation results and component details.
144
+ */
145
+ function buildPrBody({ componentName, editableFields, attempts, totalInputTokens, totalOutputTokens, fingerprint }) {
146
+ const fieldsTable = editableFields.map((f) => `| \`${f}\` |`).join('\n');
147
+
148
+ return `## 🤖 AI-Generated Editable Component: \`Editable${componentName}\`
149
+
150
+ This component was automatically generated by **Deneb ARC AI Agent** and passed all validation checks.
151
+
152
+ ### Editable Fields Discovered
153
+ | Field Path |
154
+ |---|
155
+ ${fieldsTable}
156
+
157
+ ### Validation Results
158
+ - ✅ AST Syntax: Passed
159
+ - ✅ Fivora Contract Markers: Passed
160
+ - ✅ TypeScript Interface: Passed
161
+ - ✅ Design Preservation: Intact
162
+
163
+ ### Generation Stats
164
+ - **Model:** ${process.env.OPENAI_CONTENT_MODEL || 'gpt-4o-mini'}
165
+ - **Attempts:** ${attempts}/3
166
+ - **Tokens:** ${totalInputTokens.toLocaleString()} input / ${totalOutputTokens.toLocaleString()} output
167
+ - **Fingerprint:** \`${fingerprint || 'n/a'}\`
168
+
169
+ ---
170
+ *Generated by Deneb ARC AI Agent — [deneb.fivora.site](https://deneb.fivora.site)*`;
171
+ }
172
+
173
+ /**
174
+ * Full PR workflow: create branch → commit files → open PR.
175
+ *
176
+ * Opens PRs for both the core repo (component) and the ui repo (docs).
177
+ *
178
+ * @param {object} params
179
+ * @param {string} params.componentName - e.g. "Carousel"
180
+ * @param {string} params.componentCode - The validated EditableCarousel.tsx code
181
+ * @param {string} params.docsCode - The generated docs page code (optional)
182
+ * @param {string[]} params.editableFields - List of editable field paths
183
+ * @param {number} params.attempts - Number of AI attempts taken
184
+ * @param {number} params.totalInputTokens - Total tokens used
185
+ * @param {number} params.totalOutputTokens - Total tokens used
186
+ * @param {string} [params.fingerprint] - Structural fingerprint
187
+ * @param {boolean} [params.dryRun=false] - If true, skip actual API calls
188
+ * @returns {Promise<{ corePr?: { number: number, url: string }, uiPr?: { number: number, url: string }, errors: string[] }>}
189
+ */
190
+ async function createComponentPR(params) {
191
+ const {
192
+ componentName,
193
+ componentCode,
194
+ docsCode,
195
+ editableFields,
196
+ attempts,
197
+ totalInputTokens,
198
+ totalOutputTokens,
199
+ fingerprint,
200
+ dryRun = false,
201
+ } = params;
202
+
203
+ const errors = [];
204
+ let corePr = null;
205
+ let uiPr = null;
206
+
207
+ if (dryRun) {
208
+ return {
209
+ corePr: { number: 0, url: '(dry-run — PR not created)' },
210
+ uiPr: docsCode ? { number: 0, url: '(dry-run — PR not created)' } : null,
211
+ errors: [],
212
+ };
213
+ }
214
+
215
+ const octokit = createOctokitClient();
216
+ if (!octokit) {
217
+ return { errors: ['GitHub client not available — check GITHUB_PAT and @octokit/rest installation.'] };
218
+ }
219
+
220
+ const config = getGithubConfig();
221
+ const slugName = componentName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
222
+ const timestamp = Date.now().toString(36);
223
+ const prBody = buildPrBody({ componentName, editableFields, attempts, totalInputTokens, totalOutputTokens, fingerprint });
224
+
225
+ // ── Core Repo PR ───────────────────────────────────────────────
226
+ try {
227
+ const coreBranch = `feat/ai-editable-${slugName}-${timestamp}`;
228
+ const baseSha = await getLatestCommitSha(octokit, config.username, config.coreRepo);
229
+ await createBranch(octokit, config.username, config.coreRepo, coreBranch, baseSha);
230
+
231
+ // Commit the component file
232
+ await createOrUpdateFile(
233
+ octokit,
234
+ config.username,
235
+ config.coreRepo,
236
+ coreBranch,
237
+ `packages/deneb-ui/src/Editable${componentName}.tsx`,
238
+ componentCode,
239
+ `feat(ui): add Editable${componentName} component [AI-generated]`
240
+ );
241
+
242
+ // Open PR to upstream
243
+ corePr = await openPullRequest(octokit, {
244
+ upstreamOwner: config.org,
245
+ upstreamRepo: config.coreRepo,
246
+ forkOwner: config.username,
247
+ branch: coreBranch,
248
+ title: `feat(ui): add Editable${componentName} component [AI-generated]`,
249
+ body: prBody,
250
+ });
251
+ } catch (err) {
252
+ errors.push(`Core PR failed: ${err.message}`);
253
+ }
254
+
255
+ // ── UI Repo PR (docs page) ─────────────────────────────────────
256
+ if (docsCode) {
257
+ try {
258
+ const uiBranch = `feat/ai-docs-${slugName}-${timestamp}`;
259
+ const uiBaseSha = await getLatestCommitSha(octokit, config.username, config.uiRepo);
260
+ await createBranch(octokit, config.username, config.uiRepo, uiBranch, uiBaseSha);
261
+
262
+ // Commit the docs page
263
+ await createOrUpdateFile(
264
+ octokit,
265
+ config.username,
266
+ config.uiRepo,
267
+ uiBranch,
268
+ `src/app/docs/components/editable-${slugName}/page.tsx`,
269
+ docsCode,
270
+ `docs: add Editable${componentName} documentation [AI-generated]`
271
+ );
272
+
273
+ // Open PR to upstream
274
+ uiPr = await openPullRequest(octokit, {
275
+ upstreamOwner: config.org,
276
+ upstreamRepo: config.uiRepo,
277
+ forkOwner: config.username,
278
+ branch: uiBranch,
279
+ title: `docs: add Editable${componentName} documentation [AI-generated]`,
280
+ body: prBody,
281
+ });
282
+ } catch (err) {
283
+ errors.push(`UI docs PR failed: ${err.message}`);
284
+ }
285
+ }
286
+
287
+ return { corePr, uiPr, errors };
288
+ }
289
+
290
+ module.exports = {
291
+ getGithubConfig,
292
+ checkGithubReady,
293
+ createComponentPR,
294
+ buildPrBody,
295
+ };
@@ -163,6 +163,45 @@ function printRollback(reason) {
163
163
  console.log(`${C.dim}${reason}${C.reset}\n`);
164
164
  }
165
165
 
166
+ // ─── AI Agent Progress Messages ──────────────────────────────────
167
+
168
+ function printAiDetected(unknownCount) {
169
+ console.log(`\n ${C.cyan}⚡${C.reset} ${C.bold}AI Agent:${C.reset} Detected ${unknownCount} unknown component${unknownCount > 1 ? 's' : ''}`);
170
+ }
171
+
172
+ function printAiAttempt(componentName, attempt, maxRetries) {
173
+ console.log(` ${C.cyan}⚡${C.reset} Adapting ${C.bold}<${componentName} />${C.reset}... attempt ${attempt}/${maxRetries}`);
174
+ }
175
+
176
+ function printAiValidationFail(errors, attempt) {
177
+ for (const err of errors.slice(0, 3)) {
178
+ console.log(` ${C.red}❌${C.reset} ${err}`);
179
+ }
180
+ if (errors.length > 3) {
181
+ console.log(` ${C.dim}... ${errors.length - 3} more errors${C.reset}`);
182
+ }
183
+ }
184
+
185
+ function printAiSuccess(componentName, attempt, fieldsCount) {
186
+ console.log(` ${C.green}✅${C.reset} All checks passed! (${fieldsCount} editable field${fieldsCount !== 1 ? 's' : ''})`);
187
+ }
188
+
189
+ function printAiSkipped(componentName, reason) {
190
+ console.log(` ${C.yellow}⚠${C.reset} Skipped ${C.bold}<${componentName} />${C.reset}: ${reason}`);
191
+ }
192
+
193
+ function printAiPr(repoName, branchName, prNumber) {
194
+ if (prNumber === 0) {
195
+ console.log(` ${C.cyan}📦${C.reset} ${C.dim}(dry-run)${C.reset} ${repoName}: ${branchName}`);
196
+ } else {
197
+ console.log(` ${C.cyan}📦${C.reset} PR opened: ${C.bold}${repoName}${C.reset} (#${prNumber})`);
198
+ }
199
+ }
200
+
201
+ function printAiSummary(adapted, skipped, totalTokens) {
202
+ console.log(`\n ${C.cyan}⚡${C.reset} AI Agent Summary: ${C.green}${adapted} adapted${C.reset}, ${C.yellow}${skipped} skipped${C.reset}, ~${totalTokens.toLocaleString()} tokens used`);
203
+ }
204
+
166
205
  module.exports = {
167
206
  printBanner,
168
207
  printProfile,
@@ -177,6 +216,13 @@ module.exports = {
177
216
  printUncoveredText,
178
217
  printDeveloperNextSteps,
179
218
  printRollback,
219
+ printAiDetected,
220
+ printAiAttempt,
221
+ printAiValidationFail,
222
+ printAiSuccess,
223
+ printAiSkipped,
224
+ printAiPr,
225
+ printAiSummary,
180
226
  ok,
181
227
  warn,
182
228
  info,
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const ARC_NAME = 'Deneb ARC';
4
- const ARC_FULL_NAME = 'Deneb Adaptive Refactoring Compiler';
5
- const ARC_VERSION = '1.1.0';
4
+ const ARC_FULL_NAME = 'Deneb Adaptive Refactoring Compiler — AI-Augmented';
5
+ const ARC_VERSION = '2.0.0';
6
6
  const SCHEMA_VERSION = 2;
7
7
  const ENGINE_ID = 'deneb-arc';
8
8