@auto-engineer/information-architect 0.9.12 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -14,13 +14,14 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "fast-glob": "^3.3.2",
17
- "@auto-engineer/ai-gateway": "0.9.12",
18
- "@auto-engineer/message-bus": "0.9.12"
17
+ "@auto-engineer/ai-gateway": "0.10.0",
18
+ "@auto-engineer/message-bus": "0.10.0",
19
+ "@auto-engineer/flow": "0.10.0"
19
20
  },
20
21
  "devDependencies": {
21
- "@auto-engineer/cli": "0.9.12"
22
+ "@auto-engineer/cli": "0.10.0"
22
23
  },
23
- "version": "0.9.12",
24
+ "version": "0.10.0",
24
25
  "scripts": {
25
26
  "build": "tsc && tsx ../../scripts/fix-esm-imports.ts && cp src/auto-ux-schema.json dist/",
26
27
  "test": "vitest run --reporter=dot",
@@ -183,6 +183,9 @@
183
183
  "description": {
184
184
  "type": "string"
185
185
  },
186
+ "template": {
187
+ "type": "string"
188
+ },
186
189
  "layout": {
187
190
  "type": "object",
188
191
  "properties": {
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  import { processFlowsWithAI } from '../index';
6
6
  import { type UXSchema } from '../types';
7
7
  import createDebug from 'debug';
8
+ import { Model } from '@auto-engineer/flow';
8
9
 
9
10
  const debug = createDebug('ia:generate-command');
10
11
  const debugSchema = createDebug('ia:generate-command:schema');
@@ -18,8 +19,8 @@ const __dirname = path.dirname(__filename);
18
19
  export type GenerateIACommand = Command<
19
20
  'GenerateIA',
20
21
  {
22
+ modelPath: string;
21
23
  outputDir: string;
22
- flowFiles: string[];
23
24
  }
24
25
  >;
25
26
 
@@ -28,7 +29,6 @@ export type IAGeneratedEvent = Event<
28
29
  {
29
30
  outputPath: string;
30
31
  outputDir: string;
31
- flowFiles: string[];
32
32
  }
33
33
  >;
34
34
 
@@ -37,30 +37,32 @@ export type IAGenerationFailedEvent = Event<
37
37
  {
38
38
  error: string;
39
39
  outputDir: string;
40
- flowFiles: string[];
41
40
  }
42
41
  >;
43
42
 
44
43
  export type GenerateIAEvents = IAGeneratedEvent | IAGenerationFailedEvent;
45
44
 
46
- export const commandHandler = defineCommandHandler<GenerateIACommand>({
45
+ export const commandHandler = defineCommandHandler<
46
+ GenerateIACommand,
47
+ (command: GenerateIACommand) => Promise<IAGeneratedEvent | IAGenerationFailedEvent>
48
+ >({
47
49
  name: 'GenerateIA',
48
50
  alias: 'generate:ia',
49
51
  description: 'Generate Information Architecture',
50
52
  category: 'generate',
51
53
  icon: 'building',
54
+ events: ['IAGenerated', 'IAGenerationFailed'],
52
55
  fields: {
53
56
  outputDir: {
54
57
  description: 'Context directory',
55
58
  required: true,
56
59
  },
57
- flowFiles: {
58
- description: 'Flow files to analyze',
60
+ modelPath: {
61
+ description: 'Path to the model file',
59
62
  required: true,
60
- // Note: flowFiles is an array, so it doesn't have a simple position
61
63
  },
62
64
  },
63
- examples: ['$ auto generate:ia --output-dir=./.context --flow-files=./flows/*.flow.ts'],
65
+ examples: ['$ auto generate:ia --output-dir=./.context --model-path=./.context/schema.json'],
64
66
  handle: async (command: GenerateIACommand): Promise<IAGeneratedEvent | IAGenerationFailedEvent> => {
65
67
  const result = await handleGenerateIACommandInternal(command);
66
68
  if (result.type === 'IAGenerated') {
@@ -102,6 +104,12 @@ async function getAtomsFromMarkdown(designSystemDir: string): Promise<string[]>
102
104
  return components;
103
105
  }
104
106
 
107
+ async function getModelFromContext(modelPath: string): Promise<Model> {
108
+ debug('Loading model from context: %s', modelPath);
109
+ const modelContent = await fs.readFile(modelPath, 'utf-8');
110
+ return JSON.parse(modelContent) as Model;
111
+ }
112
+
105
113
  async function getUniqueSchemaPath(
106
114
  outputDir: string,
107
115
  ): Promise<{ filePath: string; existingSchema: object | undefined }> {
@@ -130,11 +138,10 @@ async function getUniqueSchemaPath(
130
138
  async function handleGenerateIACommandInternal(
131
139
  command: GenerateIACommand,
132
140
  ): Promise<IAGeneratedEvent | IAGenerationFailedEvent> {
133
- const { outputDir, flowFiles } = command.data;
141
+ const { outputDir, modelPath } = command.data;
134
142
 
135
143
  debug('Handling GenerateIA command');
136
144
  debug(' Output directory: %s', outputDir);
137
- debug(' Flow files: %d', flowFiles.length);
138
145
  debug(' Request ID: %s', command.requestId);
139
146
  debug(' Correlation ID: %s', command.correlationId ?? 'none');
140
147
 
@@ -142,22 +149,11 @@ async function handleGenerateIACommandInternal(
142
149
  // Load UX schema
143
150
  const uxSchemaPath = path.join(__dirname, '..', 'auto-ux-schema.json');
144
151
  debugSchema('Loading UX schema from: %s', uxSchemaPath);
145
-
146
152
  const uxSchemaContent = await fs.readFile(uxSchemaPath, 'utf-8');
147
153
  const uxSchema = JSON.parse(uxSchemaContent) as UXSchema;
148
154
  debugSchema('UX schema loaded successfully');
149
-
150
- // Read all flow files
151
- debugFiles('Reading %d flow files', flowFiles.length);
152
- const flows: string[] = await Promise.all(
153
- flowFiles.map(async (flow: string) => {
154
- debugFiles(' Reading: %s', flow);
155
- const content = await fs.readFile(flow, 'utf-8');
156
- debugFiles(' Size: %d bytes', content.length);
157
- return content;
158
- }),
159
- );
160
- debugFiles('All flow files read successfully');
155
+ const model = await getModelFromContext(modelPath);
156
+ debugFiles('Reading %d flow files', model.flows.length);
161
157
 
162
158
  // Get unique schema path and existing schema
163
159
  const { filePath, existingSchema } = await getUniqueSchemaPath(outputDir);
@@ -170,11 +166,10 @@ async function handleGenerateIACommandInternal(
170
166
 
171
167
  // Generate IA schema using AI
172
168
  debug('Processing flows with AI...');
173
- debug(' Flow count: %d', flows.length);
174
169
  debug(' Existing schema: %s', existingSchema ? 'yes' : 'no');
175
170
  debug(' Atom count: %d', atoms.length);
176
171
 
177
- const iaSchema = await processFlowsWithAI(flows, uxSchema, existingSchema, atoms);
172
+ const iaSchema = await processFlowsWithAI(model, uxSchema, existingSchema, atoms);
178
173
  debug('AI processing complete');
179
174
 
180
175
  // Write the schema to file
@@ -191,7 +186,6 @@ async function handleGenerateIACommandInternal(
191
186
  data: {
192
187
  outputPath: filePath,
193
188
  outputDir,
194
- flowFiles,
195
189
  },
196
190
  timestamp: new Date(),
197
191
  requestId: command.requestId,
@@ -211,7 +205,6 @@ async function handleGenerateIACommandInternal(
211
205
  data: {
212
206
  error: errorMessage,
213
207
  outputDir,
214
- flowFiles,
215
208
  },
216
209
  timestamp: new Date(),
217
210
  requestId: command.requestId,
@@ -2,25 +2,97 @@ import { processFlowsWithAI } from './index';
2
2
  import uxSchema from './auto-ux-schema.json';
3
3
  import * as fs from 'fs/promises';
4
4
  import * as path from 'path';
5
+ import { type Model } from '@auto-engineer/flow';
5
6
 
6
- async function getAtomsFromMarkdown(designSystemDir: string): Promise<string[]> {
7
+ interface DesignSystemItem {
8
+ name: string;
9
+ description: string;
10
+ }
11
+
12
+ function determineSectionType(line: string): 'components' | 'layouts' | null {
13
+ if (line === '## Components') return 'components';
14
+ if (line === '## Layouts') return 'layouts';
15
+ return null;
16
+ }
17
+
18
+ function addItemToSection(
19
+ item: DesignSystemItem,
20
+ sectionType: 'components' | 'layouts',
21
+ components: DesignSystemItem[],
22
+ layouts: DesignSystemItem[],
23
+ ): void {
24
+ if (sectionType === 'components') {
25
+ components.push(item);
26
+ } else {
27
+ layouts.push(item);
28
+ }
29
+ }
30
+
31
+ function processLine(
32
+ line: string,
33
+ currentSection: 'components' | 'layouts' | null,
34
+ currentItem: DesignSystemItem | null,
35
+ components: DesignSystemItem[],
36
+ layouts: DesignSystemItem[],
37
+ ): { section: typeof currentSection; item: typeof currentItem } {
38
+ const trimmedLine = line.trim();
39
+
40
+ if (trimmedLine.startsWith('## ')) {
41
+ return {
42
+ section: determineSectionType(trimmedLine),
43
+ item: null,
44
+ };
45
+ }
46
+
47
+ if (trimmedLine.startsWith('### ') && currentSection) {
48
+ if (currentItem) {
49
+ addItemToSection(currentItem, currentSection, components, layouts);
50
+ }
51
+
52
+ return {
53
+ section: currentSection,
54
+ item: {
55
+ name: trimmedLine.replace(/^###\s+/, '').trim(),
56
+ description: '',
57
+ },
58
+ };
59
+ }
60
+
61
+ if (currentItem && trimmedLine.startsWith('Description: ')) {
62
+ currentItem.description = trimmedLine.replace(/^Description:\s+/, '').trim();
63
+ }
64
+
65
+ return { section: currentSection, item: currentItem };
66
+ }
67
+
68
+ async function getComponentsAndLayouts(
69
+ designSystemDir: string,
70
+ ): Promise<{ components: DesignSystemItem[]; layouts: DesignSystemItem[] }> {
7
71
  const mdPath = path.join(designSystemDir, 'design-system.md');
8
72
  let content: string;
9
73
  try {
10
74
  content = await fs.readFile(mdPath, 'utf-8');
11
75
  } catch {
12
- return [];
76
+ return { components: [], layouts: [] };
13
77
  }
14
- // Find all lines that start with '### ' and extract the component name
78
+
15
79
  const lines = content.split('\n');
16
- const components: string[] = [];
80
+ const components: DesignSystemItem[] = [];
81
+ const layouts: DesignSystemItem[] = [];
82
+ let currentSection: 'components' | 'layouts' | null = null;
83
+ let currentItem: DesignSystemItem | null = null;
84
+
17
85
  for (const line of lines) {
18
- const match = line.match(/^###\s+(.+)/);
19
- if (match) {
20
- components.push(match[1].trim());
21
- }
86
+ const result = processLine(line, currentSection, currentItem, components, layouts);
87
+ currentSection = result.section;
88
+ currentItem = result.item;
89
+ }
90
+
91
+ if (currentItem && currentSection) {
92
+ addItemToSection(currentItem, currentSection, components, layouts);
22
93
  }
23
- return components;
94
+
95
+ return { components, layouts };
24
96
  }
25
97
 
26
98
  async function getUniqueSchemaPath(
@@ -57,19 +129,32 @@ async function getUniqueSchemaPath(
57
129
  return { filePath, existingSchema };
58
130
  }
59
131
 
132
+ async function getModelFromContext(outputDir: string): Promise<Model> {
133
+ const modelPath = path.join(outputDir, 'schema.json');
134
+ try {
135
+ const modelContent = await fs.readFile(modelPath, 'utf-8');
136
+ return JSON.parse(modelContent) as Model;
137
+ } catch (error) {
138
+ console.error(`Error reading model from ${modelPath}:`, error);
139
+ console.error('Please ensure the model schema.json exists in the output directory.');
140
+ process.exit(1);
141
+ }
142
+ }
143
+
60
144
  async function main() {
61
- const [, , outputDir, ...flowFiles] = process.argv;
145
+ const [, , outputDir] = process.argv;
62
146
  if (!outputDir) {
63
- console.error('Usage: tsx src/generate-ia-schema.ts <output-dir> <flow-file-1> <flow-file-2> ...');
147
+ console.error('Usage: tsx src/generate-ia-schema.ts <output-dir>');
148
+ console.error('Note: The model schema.json must exist in the output directory.');
64
149
  process.exit(1);
65
150
  }
66
151
 
67
- const flows: string[] = await Promise.all(flowFiles.map((flow) => fs.readFile(flow, 'utf-8')));
152
+ const model = await getModelFromContext(outputDir);
68
153
  const { filePath, existingSchema } = await getUniqueSchemaPath(outputDir);
69
- const atomNames = await getAtomsFromMarkdown(outputDir);
70
- const atoms = atomNames.map((name) => ({ name, props: [] }));
154
+ const { components: atomsNames } = await getComponentsAndLayouts(outputDir);
155
+ const atoms = atomsNames.map((atom) => ({ name: atom.name, props: [] }));
71
156
 
72
- const iaSchema = await processFlowsWithAI(flows, uxSchema, existingSchema, atoms);
157
+ const iaSchema = await processFlowsWithAI(model, uxSchema, existingSchema, atoms);
73
158
 
74
159
  await fs.writeFile(filePath, JSON.stringify(iaSchema, null, 2));
75
160
  console.log(`Generated IA schema written to ${filePath}`);
package/src/ia-agent.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { generateTextWithAI, AIProvider } from '@auto-engineer/ai-gateway';
2
2
  import { type UXSchema, type AIAgentOutput } from './types.js';
3
+ import { type Model } from '@auto-engineer/flow';
3
4
 
4
5
  function extractJsonFromMarkdown(text: string): string {
5
6
  return text.replace(/```(?:json)?\s*([\s\S]*?)\s*```/, '$1').trim();
@@ -22,17 +23,18 @@ export class InformationArchitectAgent {
22
23
  }
23
24
 
24
25
  async generateUXComponents(
25
- flows: string[],
26
+ model: Model,
26
27
  uxSchema: UXSchema,
27
28
  existingSchema?: object,
28
29
  atoms?: { name: string; props: { name: string; type: string }[] }[],
30
+ // layouts?: { name: string; description: string }[],
29
31
  ): Promise<AIAgentOutput> {
30
- const prompt = this.constructPrompt(flows, uxSchema, existingSchema, atoms);
32
+ const prompt = this.constructPrompt(model, uxSchema, existingSchema, atoms);
31
33
  try {
32
34
  const response = await generateTextWithAI(prompt, {
33
35
  provider: this.provider,
34
36
  temperature: 0.7,
35
- maxTokens: 4096,
37
+ maxTokens: 4096 * 2,
36
38
  });
37
39
  if (!response) {
38
40
  throw new Error('No response from AI agent');
@@ -49,20 +51,21 @@ export class InformationArchitectAgent {
49
51
  }
50
52
 
51
53
  private constructPrompt(
52
- flows: string[],
54
+ model: Model,
53
55
  uxSchema: UXSchema,
54
56
  existingSchema?: object,
55
57
  atoms?: { name: string; props: { name: string; type: string }[] }[],
58
+ // layouts?: { name: string; description: string }[],
56
59
  ): string {
57
60
  return `
58
- You are an expert UI architect and product designer. Given the following flows and UX schema, generate a detailed JSON specification for the application's UI components and pages.
61
+ You are an expert UI architect and product designer. Given the following model (containing flows, messages, and integrations) and UX schema, generate a detailed JSON specification for the application's UI components and pages.
59
62
 
60
- IMPORTANT: Only generate pages and components that are directly referenced in the provided flows. Do NOT add any extra pages or components, and do NOT make assumptions outside the flows. If something is not mentioned in the flows, it should NOT appear in the output.
63
+ IMPORTANT: Only generate pages and components that are directly referenced in the provided model's flows. Do NOT add any extra pages or components, and do NOT make assumptions outside the flows. If something is not mentioned in the flows, it should NOT appear in the output.
61
64
  IMPORTANT: try your best to reuse the existing atoms, and try not to generate atoms with context: like Submit Button, because the submit part is mainly irrelevant, instead just use the Button atom if provided.
62
65
 
63
66
  $${atoms ? `Here is a list of available atomic components (atoms) from the design system. Use these atoms and their props as much as possible. Only create new atoms if absolutely necessary. And only put the new atoms created into the schema. \n\nAtoms:\n${JSON.stringify(atoms, null, 2)}\n` : ''}
64
- Flows:
65
- ${JSON.stringify(flows, null, 2)}
67
+ System Model (flows, messages, integrations):
68
+ ${JSON.stringify(model, null, 2)}
66
69
 
67
70
  UX Schema:
68
71
  ${JSON.stringify(uxSchema, null, 2)}
@@ -70,6 +73,7 @@ ${JSON.stringify(uxSchema, null, 2)}
70
73
  ${existingSchema ? `Here is the current IA schema. Only add, update, or remove what is necessary based on the new flows and UX schema. Preserve what is still relevant and do not make unnecessary changes.\n\nCurrent IA Schema:\n${JSON.stringify(existingSchema, null, 2)}\n` : ''}
71
74
  Instructions:
72
75
 
76
+ - NEVER generate any data_requirements queries or mutations if NONE were provided from the flow schema
73
77
  - Respond ONLY with a JSON object, no explanation, no markdown, no text before or after.
74
78
  - The JSON should have two main sections: "components" and "pages".
75
79
  - In "components", define composite UI elements (atoms, molecules, organisms) with:
@@ -87,11 +91,11 @@ Instructions:
87
91
  - In "pages", define each page as a key, with:
88
92
  - route (URL path)
89
93
  - description
90
- - layout (listing the organisms used)
94
+ - template (what wrapper does the page use)
91
95
  - navigation (array of navigation actions, e.g., { "on": "Click Listing Card", "to": "ListingDetailPage" })
92
96
  - data_requirements (array, as above, for page-level data fetching)
93
- - For each component or page, if there are any specs defined in the flows using specs('ComponentOrPageName', ...), extract all should(...) statements from .client(() => { ... }) blocks and assign them as an array of strings to a 'specs' field for the corrosponding component/page.
94
- - Only include specs from .client(() => { ... }) blocks, not from server() or other blocks.
97
+ - For each component or page, if there are any specs defined in the model's flow slices (look for slice.specs array where specs have context='client'), extract all spec descriptions and assign them as an array of strings to a 'specs' field for the corresponding component/page.
98
+ - Only include specs where the context is 'client', not 'server' or other contexts.
95
99
  - If no specs are found for a component/page, omit the 'specs' field.
96
100
 
97
101
  Use the following structure as a template for your response:
@@ -128,7 +132,6 @@ Use the following structure as a template for your response:
128
132
  "PageName": {
129
133
  "route": "/route",
130
134
  "description": "What this page does.",
131
- "layout": { "organisms": ["Organism1", "Organism2"] },
132
135
  "navigation": [{ "on": "Event", "to": "TargetPage" }],
133
136
  "data_requirements": [
134
137
  // ... as above
@@ -146,11 +149,12 @@ Do not include any text, explanation, or markdown—only the JSON object as desc
146
149
  }
147
150
 
148
151
  export async function processFlowsWithAI(
149
- flows: string[],
152
+ model: Model,
150
153
  uxSchema: UXSchema,
151
154
  existingSchema?: object,
152
155
  atoms?: { name: string; props: { name: string; type: string }[] }[],
156
+ // layouts?: { name: string; description: string }[],
153
157
  ): Promise<AIAgentOutput> {
154
158
  const agent = new InformationArchitectAgent();
155
- return agent.generateUXComponents(flows, uxSchema, existingSchema, atoms);
159
+ return agent.generateUXComponents(model, uxSchema, existingSchema, atoms);
156
160
  }