@auto-engineer/information-architect 0.13.3 → 0.15.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,23 +14,19 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "fast-glob": "^3.3.2",
17
- "@auto-engineer/ai-gateway": "0.13.3",
18
- "@auto-engineer/message-bus": "0.13.3",
19
- "@auto-engineer/narrative": "0.13.3"
17
+ "@auto-engineer/narrative": "0.15.0",
18
+ "@auto-engineer/ai-gateway": "0.15.0",
19
+ "@auto-engineer/message-bus": "0.15.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@auto-engineer/cli": "0.13.3"
22
+ "@auto-engineer/cli": "0.15.0"
23
23
  },
24
- "version": "0.13.3",
24
+ "version": "0.15.0",
25
25
  "scripts": {
26
26
  "build": "tsc && tsx ../../scripts/fix-esm-imports.ts && cp src/auto-ux-schema.json dist/",
27
27
  "test": "vitest run --reporter=dot",
28
- "lint": "eslint 'src/**/*.ts' --max-warnings 0 --config ../../eslint.config.ts",
29
28
  "type-check": "tsc --noEmit",
30
29
  "generate-ia-schema": "tsx src/generate-ia-schema.ts",
31
- "format": "prettier --write \"**/*.{js,ts,json,md,yml,yaml}\" --ignore-path ../../.prettierignore --log-level warn",
32
- "lint:fix": "eslint 'src/**/*.ts' --fix --config ../../eslint.config.ts",
33
- "format:fix": "prettier --write \"**/*.{js,ts,json,md,yml,yaml}\" --ignore-path ../../.prettierignore --log-level warn",
34
30
  "link:dev": "pnpm build && pnpm link --global",
35
31
  "unlink:dev": "pnpm unlink --global"
36
32
  }
@@ -1,12 +1,12 @@
1
- import { type Command, type Event, defineCommandHandler } from '@auto-engineer/message-bus';
2
- import { promises as fs } from 'fs';
3
- import * as path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { processFlowsWithAI } from '../index';
6
- import { type UXSchema } from '../types';
1
+ import { promises as fs } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { type Command, defineCommandHandler, type Event } from '@auto-engineer/message-bus';
5
+ import type { Model } from '@auto-engineer/narrative';
7
6
  import createDebug from 'debug';
8
- import { Model } from '@auto-engineer/narrative';
7
+ import { processFlowsWithAI, type ValidationError, validateCompositionReferences } from '../index';
9
8
  import { flattenClientSpecs } from '../spec-utils';
9
+ import type { UXSchema } from '../types';
10
10
 
11
11
  const debug = createDebug('auto:information-architect:generate-command');
12
12
  const debugSchema = createDebug('auto:information-architect:generate-command:schema');
@@ -22,6 +22,7 @@ export type GenerateIACommand = Command<
22
22
  {
23
23
  modelPath: string;
24
24
  outputDir: string;
25
+ previousErrors?: string;
25
26
  }
26
27
  >;
27
28
 
@@ -41,18 +42,32 @@ export type IAGenerationFailedEvent = Event<
41
42
  }
42
43
  >;
43
44
 
44
- export type GenerateIAEvents = IAGeneratedEvent | IAGenerationFailedEvent;
45
+ export type IAValidationFailedEvent = Event<
46
+ 'IAValidationFailed',
47
+ {
48
+ errors: ValidationError[];
49
+ outputDir: string;
50
+ modelPath: string;
51
+ }
52
+ >;
53
+
54
+ export type GenerateIAEvents = IAGeneratedEvent | IAGenerationFailedEvent | IAValidationFailedEvent;
45
55
 
46
56
  export const commandHandler = defineCommandHandler<
47
57
  GenerateIACommand,
48
- (command: GenerateIACommand) => Promise<IAGeneratedEvent | IAGenerationFailedEvent>
58
+ (command: GenerateIACommand) => Promise<IAGeneratedEvent | IAGenerationFailedEvent | IAValidationFailedEvent>
49
59
  >({
50
60
  name: 'GenerateIA',
61
+ displayName: 'Generate IA',
51
62
  alias: 'generate:ia',
52
63
  description: 'Generate Information Architecture',
53
64
  category: 'generate',
54
65
  icon: 'building',
55
- events: ['IAGenerated', 'IAGenerationFailed'],
66
+ events: [
67
+ { name: 'IAGenerated', displayName: 'IA Generated' },
68
+ { name: 'IAGenerationFailed', displayName: 'IA Generation Failed' },
69
+ { name: 'IAValidationFailed', displayName: 'IA Validation Failed' },
70
+ ],
56
71
  fields: {
57
72
  outputDir: {
58
73
  description: 'Context directory',
@@ -64,10 +79,14 @@ export const commandHandler = defineCommandHandler<
64
79
  },
65
80
  },
66
81
  examples: ['$ auto generate:ia --output-dir=./.context --model-path=./.context/schema.json'],
67
- handle: async (command: GenerateIACommand): Promise<IAGeneratedEvent | IAGenerationFailedEvent> => {
82
+ handle: async (
83
+ command: GenerateIACommand,
84
+ ): Promise<IAGeneratedEvent | IAGenerationFailedEvent | IAValidationFailedEvent> => {
68
85
  const result = await handleGenerateIACommandInternal(command);
69
86
  if (result.type === 'IAGenerated') {
70
87
  debug('IA schema generated successfully');
88
+ } else if (result.type === 'IAValidationFailed') {
89
+ debug('Validation failed with %d errors', result.data.errors.length);
71
90
  } else {
72
91
  debug('Failed: %s', result.data.error);
73
92
  }
@@ -154,8 +173,8 @@ async function getUniqueSchemaPath(
154
173
 
155
174
  async function handleGenerateIACommandInternal(
156
175
  command: GenerateIACommand,
157
- ): Promise<IAGeneratedEvent | IAGenerationFailedEvent> {
158
- const { outputDir, modelPath } = command.data;
176
+ ): Promise<IAGeneratedEvent | IAGenerationFailedEvent | IAValidationFailedEvent> {
177
+ const { outputDir, modelPath, previousErrors } = command.data;
159
178
 
160
179
  debug('Handling GenerateIA command');
161
180
  debug(' Output directory: %s', outputDir);
@@ -190,9 +209,31 @@ async function handleGenerateIACommandInternal(
190
209
  debug(' Existing schema: %s', existingSchema ? 'yes' : 'no');
191
210
  debug(' Atom count: %d', atoms.length);
192
211
 
193
- const iaSchema = await processFlowsWithAI(processedModel, uxSchema, existingSchema, atoms);
212
+ const iaSchema = await processFlowsWithAI(processedModel, uxSchema, existingSchema, atoms, previousErrors);
194
213
  debug('AI processing complete');
195
214
 
215
+ const validationErrors = validateCompositionReferences(iaSchema, atomNames);
216
+ if (validationErrors.length > 0) {
217
+ debug('Validation failed with %d errors', validationErrors.length);
218
+ for (const err of validationErrors) {
219
+ debug(' - %s', err.message);
220
+ }
221
+
222
+ const validationFailedEvent: IAValidationFailedEvent = {
223
+ type: 'IAValidationFailed',
224
+ data: {
225
+ errors: validationErrors,
226
+ outputDir,
227
+ modelPath,
228
+ },
229
+ timestamp: new Date(),
230
+ requestId: command.requestId,
231
+ correlationId: command.correlationId,
232
+ };
233
+
234
+ return validationFailedEvent;
235
+ }
236
+
196
237
  // Write the schema to file
197
238
  debugResult('Writing IA schema to: %s', filePath);
198
239
  const schemaJson = JSON.stringify(iaSchema, null, 2);
@@ -1,8 +1,8 @@
1
- import { processFlowsWithAI } from './index';
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import type { Model } from '@auto-engineer/narrative';
2
4
  import uxSchema from './auto-ux-schema.json';
3
- import * as fs from 'fs/promises';
4
- import * as path from 'path';
5
- import { type Model } from '@auto-engineer/narrative';
5
+ import { processFlowsWithAI } from './index';
6
6
 
7
7
  interface DesignSystemItem {
8
8
  name: string;
@@ -101,7 +101,7 @@ async function getUniqueSchemaPath(
101
101
  await fs.mkdir(outputDir, { recursive: true });
102
102
  const baseFileName = 'auto-ia-scheme';
103
103
  const basePath = path.join(outputDir, baseFileName);
104
- let existingSchema: object | undefined = undefined;
104
+ let existingSchema: object | undefined;
105
105
 
106
106
  try {
107
107
  existingSchema = JSON.parse(await fs.readFile(`${basePath}.json`, 'utf-8')) as object;
package/src/ia-agent.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { generateTextWithAI, AIProvider } from '@auto-engineer/ai-gateway';
2
- import { type UXSchema, type AIAgentOutput } from './types.js';
3
- import { type Model } from '@auto-engineer/narrative';
1
+ import { type AIProvider, generateTextWithAI } from '@auto-engineer/ai-gateway';
2
+ import type { Model } from '@auto-engineer/narrative';
3
+ import type { AIAgentOutput, UXSchema } from './types.js';
4
4
 
5
5
  function extractJsonFromMarkdown(text: string): string {
6
6
  return text.replace(/```(?:json)?\s*([\s\S]*?)\s*```/, '$1').trim();
@@ -15,6 +15,91 @@ function isJsonString(str: string): boolean {
15
15
  }
16
16
  }
17
17
 
18
+ interface CompositionSection {
19
+ atoms?: string[];
20
+ molecules?: string[];
21
+ organisms?: string[];
22
+ }
23
+
24
+ interface ComponentDefinition {
25
+ composition?: CompositionSection;
26
+ }
27
+
28
+ interface ItemsContainer {
29
+ items?: Record<string, ComponentDefinition>;
30
+ }
31
+
32
+ interface IASchema {
33
+ atoms?: ItemsContainer;
34
+ molecules?: ItemsContainer;
35
+ organisms?: ItemsContainer;
36
+ pages?: Record<string, unknown>;
37
+ }
38
+
39
+ export interface ValidationError {
40
+ component: string;
41
+ type: 'molecule' | 'organism';
42
+ field: string;
43
+ invalidReferences: string[];
44
+ message: string;
45
+ }
46
+
47
+ export function validateCompositionReferences(schema: unknown, designSystemAtoms: string[] = []): ValidationError[] {
48
+ const s = schema as IASchema;
49
+ const errors: ValidationError[] = [];
50
+
51
+ const schemaAtoms = Object.keys(s.atoms?.items ?? {});
52
+ const atomNames = new Set([...schemaAtoms, ...designSystemAtoms]);
53
+ const moleculeNames = new Set(Object.keys(s.molecules?.items ?? {}));
54
+ const organismNames = new Set(Object.keys(s.organisms?.items ?? {}));
55
+
56
+ for (const [name, def] of Object.entries(s.molecules?.items ?? {})) {
57
+ const referencedAtoms = def.composition?.atoms ?? [];
58
+ const invalidAtoms = referencedAtoms.filter((atom: string) => !atomNames.has(atom));
59
+ if (invalidAtoms.length > 0) {
60
+ errors.push({
61
+ component: name,
62
+ type: 'molecule',
63
+ field: 'composition.atoms',
64
+ invalidReferences: invalidAtoms,
65
+ message: `Molecule "${name}" references non-existent atoms: ${invalidAtoms.join(', ')}`,
66
+ });
67
+ }
68
+ }
69
+
70
+ for (const [name, def] of Object.entries(s.organisms?.items ?? {})) {
71
+ const referencedMolecules = def.composition?.molecules ?? [];
72
+
73
+ const nonExistentMolecules = referencedMolecules.filter(
74
+ (mol: string) => !moleculeNames.has(mol) && !organismNames.has(mol),
75
+ );
76
+ if (nonExistentMolecules.length > 0) {
77
+ errors.push({
78
+ component: name,
79
+ type: 'organism',
80
+ field: 'composition.molecules',
81
+ invalidReferences: nonExistentMolecules,
82
+ message: `Organism "${name}" references non-existent molecules: ${nonExistentMolecules.join(', ')}`,
83
+ });
84
+ }
85
+
86
+ const organismsAsMolecules = referencedMolecules.filter(
87
+ (mol: string) => organismNames.has(mol) && !moleculeNames.has(mol),
88
+ );
89
+ if (organismsAsMolecules.length > 0) {
90
+ errors.push({
91
+ component: name,
92
+ type: 'organism',
93
+ field: 'composition.molecules',
94
+ invalidReferences: organismsAsMolecules,
95
+ message: `Organism "${name}" incorrectly references organisms as molecules: ${organismsAsMolecules.join(', ')}. These should be in molecules.items, not organisms.items.`,
96
+ });
97
+ }
98
+ }
99
+
100
+ return errors;
101
+ }
102
+
18
103
  export class InformationArchitectAgent {
19
104
  private provider?: AIProvider;
20
105
 
@@ -27,8 +112,9 @@ export class InformationArchitectAgent {
27
112
  uxSchema: UXSchema,
28
113
  existingSchema?: object,
29
114
  atoms?: { name: string; props: { name: string; type: string }[] }[],
115
+ previousErrors?: string,
30
116
  ): Promise<AIAgentOutput> {
31
- const prompt = this.constructPrompt(model, uxSchema, existingSchema, atoms);
117
+ const prompt = this.constructPrompt(model, uxSchema, existingSchema, atoms, previousErrors);
32
118
  try {
33
119
  const response = await generateTextWithAI(prompt, {
34
120
  provider: this.provider,
@@ -40,7 +126,7 @@ export class InformationArchitectAgent {
40
126
  }
41
127
  const clean = extractJsonFromMarkdown(response);
42
128
  if (!isJsonString(clean)) {
43
- throw new Error('AI did not return valid JSON. Got: ' + clean.slice(0, 100));
129
+ throw new Error(`AI did not return valid JSON. Got: ${clean.slice(0, 100)}`);
44
130
  }
45
131
  return JSON.parse(clean) as AIAgentOutput;
46
132
  } catch (error) {
@@ -54,13 +140,35 @@ export class InformationArchitectAgent {
54
140
  uxSchema: UXSchema,
55
141
  existingSchema?: object,
56
142
  atoms?: { name: string; props: { name: string; type: string }[] }[],
143
+ previousErrors?: string,
57
144
  ): string {
145
+ const errorContext = previousErrors
146
+ ? `
147
+ PREVIOUS ATTEMPT FAILED VALIDATION. You MUST fix these errors:
148
+ ${previousErrors}
149
+
150
+ The schema was rejected because of the composition errors above. Please regenerate the schema with these issues corrected.
151
+ `
152
+ : '';
153
+
58
154
  return `
59
155
  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.
60
-
156
+ ${errorContext}
61
157
  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.
62
158
  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.
63
159
 
160
+ CRITICAL COMPOSITION RULES - THESE ARE STRICT AND MUST BE FOLLOWED:
161
+ 1. Atoms: Basic UI primitives (Button, Text, Input, Icon, etc.). Atoms do NOT compose other atoms.
162
+ 2. Molecules: Composed ONLY of atoms. A molecule's "composition.atoms" array must ONLY reference items that exist in "atoms.items".
163
+ 3. Organisms: Composed of atoms AND molecules. An organism's "composition.molecules" array must ONLY reference items that exist in "molecules.items". An organism MUST NOT reference other organisms.
164
+ 4. Pages: Can reference organisms, molecules, and atoms.
165
+
166
+ VALIDATION CHECKLIST (the schema will be rejected if these rules are violated):
167
+ - Every item in a molecule's "composition.atoms" MUST exist in "atoms.items"
168
+ - Every item in an organism's "composition.molecules" MUST exist in "molecules.items"
169
+ - An organism's "composition.molecules" MUST NOT contain names that only exist in "organisms.items"
170
+ - Cross-check all composition references before finalizing the output
171
+
64
172
  $${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` : ''}
65
173
  System Model (flows, messages, integrations):
66
174
  ${JSON.stringify(model, null, 2)}
@@ -152,7 +260,8 @@ export async function processFlowsWithAI(
152
260
  uxSchema: UXSchema,
153
261
  existingSchema?: object,
154
262
  atoms?: { name: string; props: { name: string; type: string }[] }[],
263
+ previousErrors?: string,
155
264
  ): Promise<AIAgentOutput> {
156
265
  const agent = new InformationArchitectAgent();
157
- return agent.generateUXComponents(model, uxSchema, existingSchema, atoms);
266
+ return agent.generateUXComponents(model, uxSchema, existingSchema, atoms, previousErrors);
158
267
  }
package/src/index.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  // Barrel exports
2
- export { InformationArchitectAgent, processFlowsWithAI } from './ia-agent.js';
3
- export type { UXSchema, AIAgentOutput } from './types.js';
2
+ export {
3
+ InformationArchitectAgent,
4
+ processFlowsWithAI,
5
+ type ValidationError,
6
+ validateCompositionReferences,
7
+ } from './ia-agent.js';
8
+ export type { AIAgentOutput, UXSchema } from './types.js';
4
9
 
5
10
  import { commandHandler as generateIAHandler } from './commands/generate-ia';
6
11
  export const COMMANDS = [generateIAHandler];
@@ -9,4 +14,5 @@ export type {
9
14
  GenerateIAEvents,
10
15
  IAGeneratedEvent,
11
16
  IAGenerationFailedEvent,
17
+ IAValidationFailedEvent,
12
18
  } from './commands/generate-ia';