@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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +26 -0
- package/README.md +166 -358
- package/dist/src/commands/generate-ia.d.ts +9 -1
- package/dist/src/commands/generate-ia.d.ts.map +1 -1
- package/dist/src/commands/generate-ia.js +34 -7
- package/dist/src/commands/generate-ia.js.map +1 -1
- package/dist/src/generate-ia-schema.js +4 -4
- package/dist/src/generate-ia-schema.js.map +1 -1
- package/dist/src/ia-agent.d.ts +13 -5
- package/dist/src/ia-agent.d.ts.map +1 -1
- package/dist/src/ia-agent.js +72 -7
- package/dist/src/ia-agent.js.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -9
- package/src/commands/generate-ia.ts +55 -14
- package/src/generate-ia-schema.ts +5 -5
- package/src/ia-agent.ts +116 -7
- package/src/index.ts +8 -2
package/package.json
CHANGED
|
@@ -14,23 +14,19 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"fast-glob": "^3.3.2",
|
|
17
|
-
"@auto-engineer/
|
|
18
|
-
"@auto-engineer/
|
|
19
|
-
"@auto-engineer/
|
|
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.
|
|
22
|
+
"@auto-engineer/cli": "0.15.0"
|
|
23
23
|
},
|
|
24
|
-
"version": "0.
|
|
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 {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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 {
|
|
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
|
|
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: [
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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(
|
|
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 {
|
|
3
|
-
|
|
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';
|