@chemx/create-chemx 26.9.20-369 → 26.9.20-390

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.
@@ -58,14 +58,13 @@ jobs:
58
58
  if [ -z "$DISCUSSION_URL" ] && [ -f .chemx/discussion.json ]; then
59
59
  DISCUSSION_URL=$(node -e "try { const d = JSON.parse(require('fs').readFileSync('.chemx/discussion.json')); console.log(d.url || ''); } catch (e) {}")
60
60
  fi
61
- if [ -z "$DISCUSSION_URL" ]; then
62
- DISCUSSION_URL="https://github.com/orgs/Chemical-X-Protocol/discussions/categories/npx-chemx-audit"
63
- fi
64
61
 
65
62
  {
66
63
  echo "<!-- chemical-x-audit-pr-comment -->"
67
- echo "> 💬 **Architectural Discussion Topic:** [View Discussion & Community Showcase](\${DISCUSSION_URL})"
68
- echo ""
64
+ if [ -n "$DISCUSSION_URL" ]; then
65
+ echo "> 💬 **Architectural Discussion Topic:** [View Discussion & Community Showcase](\${DISCUSSION_URL})"
66
+ echo ""
67
+ fi
69
68
  cat AUDIT_REPORT.md
70
69
  } > PR_COMMENT.md
71
70
 
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Collection & Data Archetypes: Task List, Data Table, Tree, Chart, Kanban
3
+ */
4
+
5
+ export const COLLECTION_ARCHETYPES = [
6
+ {
7
+ id: 'task-list',
8
+ name: 'Collection & Task List',
9
+ keywords: ['task', 'todo', 'tasklist', 'checklist', 'item', 'queue'],
10
+ destructure: 'items, activeCount, setFilter, toggleItem, removeItem',
11
+ buildState: (name, pascal) => `export interface ${pascal}Item {
12
+ readonly id: string;
13
+ readonly title: string;
14
+ readonly completed: boolean;
15
+ }
16
+
17
+ export type ${pascal}Filter = 'all' | 'active' | 'completed';
18
+
19
+ export interface ${pascal}State {
20
+ readonly items: readonly ${pascal}Item[];
21
+ readonly filter: ${pascal}Filter;
22
+ readonly activeCount: number;
23
+ }
24
+ `,
25
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
26
+ readonly initialItems?: readonly ${pascal}Item[];
27
+ readonly onItemsChange?: (items: readonly ${pascal}Item[]) => void;
28
+ }
29
+ `,
30
+ buildController: (name, pascal) => `import { useState, useMemo } from 'react';
31
+ import type { ${pascal}Item, ${pascal}Filter, ${pascal}State } from './types';
32
+
33
+ export const use${pascal}Controller = (options: { initialItems?: readonly ${pascal}Item[]; onItemsChange?: (items: readonly ${pascal}Item[]) => void } = {}) => {
34
+ const [items, setItems] = useState<readonly ${pascal}Item[]>(options.initialItems || [
35
+ { id: '1', title: 'Initialize Chemical X molecular structure', completed: true },
36
+ { id: '2', title: 'Implement domain controller state', completed: false }
37
+ ]);
38
+ const [filter, setFilter] = useState<${pascal}Filter>('all');
39
+
40
+ const activeCount = useMemo(() => items.filter((i) => !i.completed).length, [items]);
41
+ const visibleItems = useMemo(() => {
42
+ if (filter === 'active') return items.filter((i) => !i.completed);
43
+ if (filter === 'completed') return items.filter((i) => i.completed);
44
+ return items;
45
+ }, [items, filter]);
46
+
47
+ const addItem = (title: string) => {
48
+ if (!title.trim()) return;
49
+ const next = [...items, { id: String(Date.now()), title: title.trim(), completed: false }];
50
+ setItems(next);
51
+ options.onItemsChange?.(next);
52
+ };
53
+
54
+ const toggleItem = (id: string) => {
55
+ const next = items.map((i) => (i.id === id ? { ...i, completed: !i.completed } : i));
56
+ setItems(next);
57
+ options.onItemsChange?.(next);
58
+ };
59
+
60
+ const removeItem = (id: string) => {
61
+ const next = items.filter((i) => i.id !== id);
62
+ setItems(next);
63
+ options.onItemsChange?.(next);
64
+ };
65
+
66
+ return { items: visibleItems, allItems: items, filter, activeCount, setFilter, addItem, toggleItem, removeItem };
67
+ };
68
+ `,
69
+ buildReactBody: (name, pascal) => ` <div className="${name}__controls">
70
+ <span className="${name}__count">{activeCount} pending</span>
71
+ <div className="${name}__filters">
72
+ <button type="button" onClick={() => setFilter('all')}>All</button>
73
+ <button type="button" onClick={() => setFilter('active')}>Active</button>
74
+ <button type="button" onClick={() => setFilter('completed')}>Done</button>
75
+ </div>
76
+ </div>
77
+ <ul className="${name}__list">
78
+ {items.map((item) => (
79
+ <li key={item.id} className={item.completed ? '${name}__item ${name}__item--done' : '${name}__item'}>
80
+ <input type="checkbox" checked={item.completed} onChange={() => toggleItem(item.id)} />
81
+ <span>{item.title}</span>
82
+ <button type="button" onClick={() => removeItem(item.id)}>×</button>
83
+ </li>
84
+ ))}
85
+ </ul>`
86
+ },
87
+ {
88
+ id: 'data-table',
89
+ name: 'Data Table & Grid',
90
+ keywords: ['table', 'grid', 'datatable', 'matrix', 'sheet', 'data-view'],
91
+ destructure: 'rows = [], sortColumn, sortDirection, page, sortBy, setPage',
92
+ buildState: (name, pascal) => `export interface ${pascal}Row {
93
+ readonly id: string;
94
+ readonly [key: string]: unknown;
95
+ }
96
+
97
+ export interface ${pascal}State {
98
+ readonly sortColumn: string;
99
+ readonly sortDirection: 'asc' | 'desc';
100
+ readonly page: number;
101
+ }
102
+ `,
103
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
104
+ readonly rows?: readonly ${pascal}Row[];
105
+ readonly columns?: readonly string[];
106
+ readonly pageSize?: number;
107
+ }
108
+ `,
109
+ buildController: (name, pascal) => `import { useState } from 'react';
110
+ import type { ${pascal}Row } from './types';
111
+
112
+ export const use${pascal}Controller = (options: { rows?: readonly ${pascal}Row[]; pageSize?: number } = {}) => {
113
+ const [sortColumn, setSortColumn] = useState<string>('id');
114
+ const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
115
+ const [page, setPage] = useState<number>(1);
116
+
117
+ const sortBy = (column: string) => {
118
+ if (sortColumn === column) {
119
+ setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc'));
120
+ } else {
121
+ setSortColumn(column);
122
+ setSortDirection('asc');
123
+ }
124
+ };
125
+
126
+ return { sortColumn, sortDirection, page, sortBy, setPage };
127
+ };
128
+ `,
129
+ buildReactBody: (name, pascal) => ` <div className="${name}__header">
130
+ <span className="${name}__sort">Sorted by {sortColumn} ({sortDirection})</span>
131
+ </div>
132
+ <div className="${name}__grid">
133
+ {(rows || []).map((row) => (
134
+ <div key={row.id} className="${name}__row">
135
+ <span>{String(row.id)}</span>
136
+ </div>
137
+ ))}
138
+ </div>`
139
+ },
140
+ {
141
+ id: 'tree-hierarchy',
142
+ name: 'Tree & Hierarchy',
143
+ keywords: ['tree', 'filetree', 'hierarchy', 'node', 'folder', 'explorer'],
144
+ destructure: 'nodes = [], expandedIds, selectedId, toggleNode, selectNode',
145
+ buildState: (name, pascal) => `export interface ${pascal}Node {
146
+ readonly id: string;
147
+ readonly label: string;
148
+ readonly children?: readonly ${pascal}Node[];
149
+ }
150
+
151
+ export interface ${pascal}State {
152
+ readonly expandedIds: readonly string[];
153
+ readonly selectedId: string | null;
154
+ }
155
+ `,
156
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
157
+ readonly nodes?: readonly ${pascal}Node[];
158
+ readonly onSelect?: (id: string) => void;
159
+ }
160
+ `,
161
+ buildController: (name, pascal) => `import { useState } from 'react';
162
+ import type { ${pascal}Node } from './types';
163
+
164
+ export const use${pascal}Controller = (options: { onSelect?: (id: string) => void } = {}) => {
165
+ const [expandedIds, setExpandedIds] = useState<readonly string[]>([]);
166
+ const [selectedId, setSelectedId] = useState<string | null>(null);
167
+
168
+ const toggleNode = (id: string) => {
169
+ setExpandedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
170
+ };
171
+
172
+ const selectNode = (id: string) => {
173
+ setSelectedId(id);
174
+ options.onSelect?.(id);
175
+ };
176
+
177
+ return { expandedIds, selectedId, toggleNode, selectNode };
178
+ };
179
+ `,
180
+ buildReactBody: (name, pascal) => ` <div className="${name}__tree">
181
+ {(nodes || []).map((node) => (
182
+ <div key={node.id} className="${name}__node" onClick={() => selectNode(node.id)}>
183
+ <span>{node.label}</span>
184
+ </div>
185
+ ))}
186
+ </div>`
187
+ },
188
+ {
189
+ id: 'chart-analytics',
190
+ name: 'Chart & Sparkline',
191
+ keywords: ['chart', 'graph', 'plot', 'trend', 'sparkline', 'series', 'analytics'],
192
+ destructure: 'series = [], activeIndex, timeRange, setActiveIndex, setTimeRange',
193
+ buildState: (name, pascal) => `export interface ${pascal}DataPoint {
194
+ readonly label: string;
195
+ readonly value: number;
196
+ }
197
+
198
+ export interface ${pascal}State {
199
+ readonly activeIndex: number | null;
200
+ readonly timeRange: '1h' | '24h' | '7d' | '30d';
201
+ }
202
+ `,
203
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
204
+ readonly series?: readonly ${pascal}DataPoint[];
205
+ readonly timeRange?: '1h' | '24h' | '7d' | '30d';
206
+ }
207
+ `,
208
+ buildController: (name, pascal) => `import { useState } from 'react';
209
+
210
+ export const use${pascal}Controller = () => {
211
+ const [activeIndex, setActiveIndex] = useState<number | null>(null);
212
+ const [timeRange, setTimeRange] = useState<'1h' | '24h' | '7d' | '30d'>('24h');
213
+
214
+ return { activeIndex, timeRange, setActiveIndex, setTimeRange };
215
+ };
216
+ `,
217
+ buildReactBody: (name, pascal) => ` <div className="${name}__toolbar">
218
+ <span>Range: {timeRange}</span>
219
+ </div>
220
+ <div className="${name}__canvas">
221
+ {(series || []).map((pt, idx) => (
222
+ <div key={idx} className="${name}__bar" style={{ height: \`\${pt.value}%\` }} onMouseEnter={() => setActiveIndex(idx)} />
223
+ ))}
224
+ </div>`
225
+ },
226
+ {
227
+ id: 'kanban-board',
228
+ name: 'Kanban & Workspace Board',
229
+ keywords: ['kanban', 'board', 'column', 'lane', 'pipeline'],
230
+ destructure: 'columns = [], cards = [], activeCardId, setActiveCardId, moveCard',
231
+ buildState: (name, pascal) => `export interface ${pascal}Card {
232
+ readonly id: string;
233
+ readonly title: string;
234
+ readonly columnId: string;
235
+ }
236
+
237
+ export interface ${pascal}Column {
238
+ readonly id: string;
239
+ readonly title: string;
240
+ }
241
+ `,
242
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
243
+ readonly columns?: readonly ${pascal}Column[];
244
+ readonly cards?: readonly ${pascal}Card[];
245
+ readonly onCardMove?: (cardId: string, toColumnId: string) => void;
246
+ }
247
+ `,
248
+ buildController: (name, pascal) => `import { useState } from 'react';
249
+ import type { ${pascal}Card } from './types';
250
+
251
+ export const use${pascal}Controller = (options: { cards?: readonly ${pascal}Card[]; onCardMove?: (id: string, colId: string) => void } = {}) => {
252
+ const [cards, setCards] = useState<readonly ${pascal}Card[]>(options.cards || []);
253
+ const [activeCardId, setActiveCardId] = useState<string | null>(null);
254
+
255
+ const moveCard = (cardId: string, toColumnId: string) => {
256
+ const next = cards.map((c) => (c.id === cardId ? { ...c, columnId: toColumnId } : c));
257
+ setCards(next);
258
+ options.onCardMove?.(cardId, toColumnId);
259
+ };
260
+
261
+ return { cards, activeCardId, setActiveCardId, moveCard };
262
+ };
263
+ `,
264
+ buildReactBody: (name, pascal) => ` <div className="${name}__columns">
265
+ {(columns || []).map((col) => (
266
+ <div key={col.id} className="${name}__column">
267
+ <h4>{col.title}</h4>
268
+ {cards.filter((c) => c.columnId === col.id).map((c) => (
269
+ <div key={c.id} className="${name}__card">{c.title}</div>
270
+ ))}
271
+ </div>
272
+ ))}
273
+ </div>`
274
+ }
275
+ ];
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Domain & Entity Archetypes: User Profile, Auth Session, Cart & Billing, Chat & Messaging
3
+ */
4
+
5
+ export const DOMAIN_ARCHETYPES = [
6
+ {
7
+ id: 'user-profile',
8
+ name: 'User Profile & Avatar',
9
+ keywords: ['profile', 'user', 'avatar', 'account', 'author'],
10
+ destructure: 'user, isEditing, status, toggleEdit, updateUser, setStatus',
11
+ buildState: (name, pascal) => `export interface ${pascal}User {
12
+ readonly id: string;
13
+ readonly name: string;
14
+ readonly email: string;
15
+ readonly avatarUrl?: string;
16
+ }
17
+
18
+ export interface ${pascal}State {
19
+ readonly user: ${pascal}User | null;
20
+ readonly isEditing: boolean;
21
+ readonly status: 'online' | 'away' | 'offline';
22
+ }
23
+ `,
24
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
25
+ readonly initialUser?: { id: string; name: string; email: string; avatarUrl?: string };
26
+ readonly onUpdate?: (user: { id: string; name: string; email: string }) => void;
27
+ }
28
+ `,
29
+ buildController: (name, pascal) => `import { useState } from 'react';
30
+ import type { ${pascal}User } from './types';
31
+
32
+ export const use${pascal}Controller = (options: { initialUser?: ${pascal}User; onUpdate?: (u: ${pascal}User) => void } = {}) => {
33
+ const [user, setUser] = useState<${pascal}User | null>(options.initialUser || {
34
+ id: 'u-1',
35
+ name: 'Alex Mercer',
36
+ email: 'alex.mercer@chemicalx.dev'
37
+ });
38
+ const [isEditing, setIsEditing] = useState<boolean>(false);
39
+ const [status, setStatus] = useState<'online' | 'away' | 'offline'>('online');
40
+
41
+ const toggleEdit = () => setIsEditing((prev) => !prev);
42
+ const updateUser = (updated: ${pascal}User) => {
43
+ setUser(updated);
44
+ setIsEditing(false);
45
+ options.onUpdate?.(updated);
46
+ };
47
+
48
+ return { user, isEditing, status, toggleEdit, updateUser, setStatus };
49
+ };
50
+ `,
51
+ buildReactBody: (name, pascal) => ` <div className="${name}__profile">
52
+ <div className="${name}__avatar">
53
+ <span>{user?.name?.[0] || 'U'}</span>
54
+ <span className={\`${name}__status-indicator ${name}__status-indicator--\${status}\`} />
55
+ </div>
56
+ <div className="${name}__details">
57
+ <h4>{user?.name}</h4>
58
+ <p>{user?.email}</p>
59
+ <button type="button" onClick={toggleEdit}>{isEditing ? 'Cancel' : 'Edit Profile'}</button>
60
+ </div>
61
+ </div>`
62
+ },
63
+ {
64
+ id: 'auth-session',
65
+ name: 'Auth & Session Gate',
66
+ keywords: ['auth', 'login', 'session', 'gate', 'permission', 'role'],
67
+ destructure: 'isAuthenticated, userId, role, login, logout',
68
+ buildState: (name, pascal) => `export interface ${pascal}State {
69
+ readonly isAuthenticated: boolean;
70
+ readonly userId: string | null;
71
+ readonly role: 'guest' | 'member' | 'admin';
72
+ }
73
+ `,
74
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
75
+ readonly onLogin?: (userId: string) => void;
76
+ readonly onLogout?: () => void;
77
+ }
78
+ `,
79
+ buildController: (name, pascal) => `import { useState } from 'react';
80
+
81
+ export const use${pascal}Controller = (options: { onLogin?: (id: string) => void; onLogout?: () => void } = {}) => {
82
+ const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
83
+ const [userId, setUserId] = useState<string | null>(null);
84
+ const [role, setRole] = useState<'guest' | 'member' | 'admin'>('guest');
85
+
86
+ const login = (id: string, userRole: 'member' | 'admin' = 'member') => {
87
+ setIsAuthenticated(true);
88
+ setUserId(id);
89
+ setRole(userRole);
90
+ options.onLogin?.(id);
91
+ };
92
+
93
+ const logout = () => {
94
+ setIsAuthenticated(false);
95
+ setUserId(null);
96
+ setRole('guest');
97
+ options.onLogout?.();
98
+ };
99
+
100
+ return { isAuthenticated, userId, role, login, logout };
101
+ };
102
+ `,
103
+ buildReactBody: (name, pascal) => ` <div className="${name}__session">
104
+ {isAuthenticated ? (
105
+ <div>
106
+ <span>Signed in as {userId} ({role})</span>
107
+ <button type="button" onClick={logout}>Sign Out</button>
108
+ </div>
109
+ ) : (
110
+ <button type="button" onClick={() => login('u-demo', 'member')}>Sign In</button>
111
+ )}
112
+ </div>`
113
+ },
114
+ {
115
+ id: 'cart-billing',
116
+ name: 'Cart & Billing',
117
+ keywords: ['cart', 'checkout', 'pricing', 'plan', 'invoice', 'subscription', 'billing'],
118
+ destructure: 'items, subtotal, discountCode, setDiscountCode, updateQuantity, checkout',
119
+ buildState: (name, pascal) => `export interface ${pascal}CartItem {
120
+ readonly id: string;
121
+ readonly name: string;
122
+ readonly price: number;
123
+ readonly quantity: number;
124
+ }
125
+
126
+ export interface ${pascal}State {
127
+ readonly items: readonly ${pascal}CartItem[];
128
+ readonly subtotal: number;
129
+ readonly discountCode: string | null;
130
+ }
131
+ `,
132
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
133
+ readonly initialItems?: readonly { id: string; name: string; price: number; quantity: number }[];
134
+ readonly onCheckout?: (total: number) => void;
135
+ }
136
+ `,
137
+ buildController: (name, pascal) => `import { useState, useMemo } from 'react';
138
+ import type { ${pascal}CartItem } from './types';
139
+
140
+ export const use${pascal}Controller = (options: { onCheckout?: (total: number) => void } = {}) => {
141
+ const [items, setItems] = useState<readonly ${pascal}CartItem[]>([]);
142
+ const [discountCode, setDiscountCode] = useState<string | null>(null);
143
+
144
+ const subtotal = useMemo(() => items.reduce((sum, item) => sum + item.price * item.quantity, 0), [items]);
145
+
146
+ const updateQuantity = (id: string, delta: number) => {
147
+ setItems((prev) =>
148
+ prev
149
+ .map((i) => (i.id === id ? { ...i, quantity: Math.max(0, i.quantity + delta) } : i))
150
+ .filter((i) => i.quantity > 0)
151
+ );
152
+ };
153
+
154
+ const checkout = () => {
155
+ options.onCheckout?.(subtotal);
156
+ };
157
+
158
+ return { items, subtotal, discountCode, setDiscountCode, updateQuantity, checkout };
159
+ };
160
+ `,
161
+ buildReactBody: (name, pascal) => ` <div className="${name}__cart">
162
+ <h4>Order Summary ({items.length} items)</h4>
163
+ <div className="${name}__total">Subtotal: \${(subtotal / 100).toFixed(2)}</div>
164
+ <button type="button" disabled={items.length === 0} onClick={checkout}>Checkout</button>
165
+ </div>`
166
+ },
167
+ {
168
+ id: 'chat-messaging',
169
+ name: 'Chat & Messaging',
170
+ keywords: ['chat', 'message', 'thread', 'inbox', 'comment', 'feed'],
171
+ destructure: 'messages, draftText, setDraftText, send',
172
+ buildState: (name, pascal) => `export interface ${pascal}Message {
173
+ readonly id: string;
174
+ readonly author: string;
175
+ readonly text: string;
176
+ readonly timestamp: number;
177
+ }
178
+
179
+ export interface ${pascal}State {
180
+ readonly messages: readonly ${pascal}Message[];
181
+ readonly draftText: string;
182
+ }
183
+ `,
184
+ buildProps: (name, pascal) => `export interface ${pascal}Props {
185
+ readonly initialMessages?: readonly { id: string; author: string; text: string; timestamp: number }[];
186
+ readonly onSend?: (text: string) => void;
187
+ }
188
+ `,
189
+ buildController: (name, pascal) => `import { useState } from 'react';
190
+ import type { ${pascal}Message } from './types';
191
+
192
+ export const use${pascal}Controller = (options: { onSend?: (text: string) => void } = {}) => {
193
+ const [messages, setMessages] = useState<readonly ${pascal}Message[]>([]);
194
+ const [draftText, setDraftText] = useState<string>('');
195
+
196
+ const send = () => {
197
+ if (!draftText.trim()) return;
198
+ const nextMsg: ${pascal}Message = { id: String(Date.now()), author: 'You', text: draftText.trim(), timestamp: Date.now() };
199
+ setMessages((prev) => [...prev, nextMsg]);
200
+ options.onSend?.(draftText.trim());
201
+ setDraftText('');
202
+ };
203
+
204
+ return { messages, draftText, setDraftText, send };
205
+ };
206
+ `,
207
+ buildReactBody: (name, pascal) => ` <div className="${name}__feed">
208
+ {messages.map((m) => (
209
+ <div key={m.id} className="${name}__message">
210
+ <strong>{m.author}</strong>: <span>{m.text}</span>
211
+ </div>
212
+ ))}
213
+ </div>
214
+ <div className="${name}__input-row">
215
+ <input type="text" value={draftText} onChange={(e) => setDraftText(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && send()} />
216
+ <button type="button" onClick={send}>Send</button>
217
+ </div>`
218
+ }
219
+ ];
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Chemical X Protocol: 22 Canonical Semantic Archetypes Registry
3
+ */
4
+
5
+ import { COLLECTION_ARCHETYPES } from './collection-archetypes.js';
6
+ import { INPUT_ARCHETYPES } from './input-archetypes.js';
7
+ import { LAYOUT_ARCHETYPES } from './layout-archetypes.js';
8
+ import { DOMAIN_ARCHETYPES } from './domain-archetypes.js';
9
+ import { STATUS_ARCHETYPES } from './status-archetypes.js';
10
+
11
+ export const ALL_ARCHETYPES = [
12
+ ...COLLECTION_ARCHETYPES,
13
+ ...INPUT_ARCHETYPES,
14
+ ...LAYOUT_ARCHETYPES,
15
+ ...DOMAIN_ARCHETYPES,
16
+ ...STATUS_ARCHETYPES
17
+ ];
18
+
19
+ export const resolveArchetype = (slug = '') => {
20
+ const normalized = String(slug).toLowerCase().replace(/^(m-|a-|o-|t-|v-|use-)/, '');
21
+ const tokens = normalized.split(/[-_]/).filter(Boolean);
22
+
23
+ for (const archetype of ALL_ARCHETYPES) {
24
+ if (archetype.id === normalized || archetype.keywords.includes(normalized)) {
25
+ return archetype;
26
+ }
27
+ const hasKeyword = archetype.keywords.some((kw) => tokens.includes(kw) || normalized.includes(kw));
28
+ if (hasKeyword) {
29
+ return archetype;
30
+ }
31
+ }
32
+
33
+ // Fallback to State Boundary (Archetype #22 in LAYOUT_ARCHETYPES)
34
+ return LAYOUT_ARCHETYPES.find((a) => a.id === 'state-boundary') || ALL_ARCHETYPES[0];
35
+ };