@texturehq/edges 1.7.2 → 1.7.4

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.
@@ -0,0 +1,1004 @@
1
+ // generate-edges-docs.js
2
+ // Unified AI Context Generator for @texturehq/edges
3
+ //
4
+ // Consolidates and enhances:
5
+ // - generate-components-manifest.js
6
+ // - generate-utilities-manifest.js
7
+ //
8
+ // Generates:
9
+ // - Component, utility, and hook manifests
10
+ // - docs/ai/edges/ markdown documentation
11
+ // - .agent-fragments/edges.md (for merging with other fragments)
12
+ // - .cursor/rules/edges-components.mdc
13
+ // - Distributable context files for consuming apps
14
+
15
+ import { execSync } from "child_process";
16
+ import fs from "fs";
17
+ import path from "path";
18
+
19
+ const ROOT = process.cwd();
20
+ const SRC_DIR = path.join(ROOT, "src");
21
+ const DIST_DIR = path.join(ROOT, "dist");
22
+ const MONO_ROOT = path.join(ROOT, "../..");
23
+ const DOCS_AI_EDGES = path.join(MONO_ROOT, "docs/ai/edges");
24
+
25
+ const PACKAGE_JSON_PATH = path.join(ROOT, "package.json");
26
+ const PACKAGE_VERSION = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, "utf8")).version;
27
+
28
+ // ============================================================================
29
+ // UTILITIES
30
+ // ============================================================================
31
+
32
+ function read(file) {
33
+ return fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
34
+ }
35
+
36
+ function ensureDir(dir) {
37
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
38
+ }
39
+
40
+ function writeFile(filePath, content) {
41
+ ensureDir(path.dirname(filePath));
42
+ fs.writeFileSync(filePath, content, "utf8");
43
+ console.log(`✓ Generated: ${filePath}`);
44
+ }
45
+
46
+ // ============================================================================
47
+ // COMPONENT MANIFEST GENERATION
48
+ // ============================================================================
49
+
50
+ function extractExportsFromIndex(indexContent) {
51
+ const re = /export\s+\{\s*([A-Za-z0-9_,\s]+)\s*\}\s+from\s+"\.\/components\/([A-Za-z0-9_/.-]+)"/g;
52
+ const results = [];
53
+ let m;
54
+ while ((m = re.exec(indexContent))) {
55
+ const exported = m[1]
56
+ .split(",")
57
+ .map((s) => s.trim())
58
+ .filter(Boolean);
59
+ const rel = m[2];
60
+ for (const name of exported) {
61
+ results.push({ name, relPath: rel });
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+
67
+ function findComponentFile(relPath) {
68
+ const base = path.join(SRC_DIR, "components", relPath);
69
+ const componentName = relPath.split("/").pop();
70
+ const candidates = [
71
+ path.join(base, `${componentName}.tsx`),
72
+ path.join(base, `${componentName}.ts`),
73
+ path.join(base, `index.tsx`),
74
+ path.join(base, `index.ts`),
75
+ ];
76
+ for (const f of candidates) {
77
+ if (fs.existsSync(f)) return f;
78
+ }
79
+ return null;
80
+ }
81
+
82
+ function findStorybookFile(relPath) {
83
+ const base = path.join(SRC_DIR, "components", relPath);
84
+ const componentName = relPath.split("/").pop();
85
+ const candidates = [path.join(base, `${componentName}.stories.tsx`), path.join(base, `${componentName}.stories.ts`)];
86
+ for (const f of candidates) {
87
+ if (fs.existsSync(f)) return f;
88
+ }
89
+ return null;
90
+ }
91
+
92
+ function extractStorybookCategory(storyContent) {
93
+ // Extract: title: "Form Controls/Button"
94
+ const titleMatch = storyContent.match(/title:\s*["']([^"']+)["']/);
95
+ if (titleMatch) {
96
+ const fullTitle = titleMatch[1];
97
+ const parts = fullTitle.split("/");
98
+ if (parts.length > 1) {
99
+ return parts[0]; // "Form Controls"
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+
105
+ function extractRelatedComponents(storyContent) {
106
+ // Find other component imports in the story file
107
+ const importMatch = storyContent.match(/import\s*\{([^}]+)\}\s*from\s*["']\.\.\/(\w+)["']/g);
108
+ const related = new Set();
109
+ if (importMatch) {
110
+ for (const imp of importMatch) {
111
+ const m = imp.match(/import\s*\{([^}]+)\}/);
112
+ if (m) {
113
+ const imports = m[1].split(",").map((s) => s.trim());
114
+ for (const name of imports) {
115
+ if (/^[A-Z]/.test(name)) {
116
+ related.add(name);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ return Array.from(related);
123
+ }
124
+
125
+ function getJsdocAbove(content, idx) {
126
+ const upTo = content.slice(0, idx);
127
+ const start = upTo.lastIndexOf("/**");
128
+ const end = upTo.lastIndexOf("*/");
129
+ if (start !== -1 && end !== -1 && end > start) {
130
+ const block = upTo.slice(start + 3, end).trim();
131
+ return block
132
+ .split("\n")
133
+ .map((l) => l.replace(/^\s*\*\s?/, "").trim())
134
+ .join(" ")
135
+ .trim();
136
+ }
137
+ return "";
138
+ }
139
+
140
+ function extractProps(content, componentName) {
141
+ const ifaceRe = new RegExp(
142
+ `export\\s+interface\\s+${componentName}Props\\s*(?:extends\\s+[^{]+)?\\{([\\s\\S]*?)\\}`,
143
+ "m",
144
+ );
145
+ let m = ifaceRe.exec(content);
146
+ if (!m) {
147
+ const anyIfaceRe = /export\s+interface\s+([A-Za-z0-9_]+Props)\s*(?:extends\s+[^{]+)?\{([\s\S]*?)\}/m;
148
+ m = anyIfaceRe.exec(content);
149
+ }
150
+ if (!m) {
151
+ const typeRe = new RegExp(`export\\s+type\\s+${componentName}Props\\s*=\\s*(?:[^{&]+&\s*)?([\\s\\S]*?)\\}`, "m");
152
+ m = typeRe.exec(content);
153
+ }
154
+ const props = [];
155
+ if (m) {
156
+ const body = m[2] || m[1] || "";
157
+ // Parse props more carefully to handle nested types
158
+ let currentProp = "";
159
+ let braceDepth = 0;
160
+ const lines = body.split("\n");
161
+
162
+ for (const line of lines) {
163
+ const trimmed = line.trim();
164
+ if (!trimmed || trimmed.startsWith("//")) continue;
165
+
166
+ // Count braces to track nested objects
167
+ for (const char of trimmed) {
168
+ if (char === "{") braceDepth++;
169
+ if (char === "}") braceDepth--;
170
+ }
171
+
172
+ currentProp += (currentProp ? " " : "") + trimmed;
173
+
174
+ // If we're at depth 0 and the line ends with semicolon or we're complete, process the prop
175
+ if (braceDepth === 0 && (trimmed.endsWith(";") || trimmed.endsWith(","))) {
176
+ const pm = /^(readonly\s+)?([A-Za-z0-9_]+)\??:\s*(.+?)[;,]?\s*$/.exec(currentProp.trim());
177
+ if (pm) {
178
+ const name = pm[2];
179
+ let type = pm[3].trim();
180
+ // Clean up type - remove trailing semicolon/comma
181
+ type = type.replace(/[;,]\s*$/, "");
182
+ // Simplify very long nested types for readability
183
+ if (type.includes("{") && type.length > 100) {
184
+ // For nested object types, show simplified version
185
+ type = type.substring(0, 80) + "...}";
186
+ }
187
+ props.push({ name, type });
188
+ }
189
+ currentProp = "";
190
+ braceDepth = 0;
191
+ }
192
+ }
193
+ }
194
+ return props;
195
+ }
196
+
197
+ function generateComponentsManifest() {
198
+ console.log("\n📦 Generating components manifest...");
199
+
200
+ const indexPath = path.join(SRC_DIR, "index.ts");
201
+ const indexContent = read(indexPath);
202
+ const exports = extractExportsFromIndex(indexContent);
203
+
204
+ const components = [];
205
+ const categories = {};
206
+
207
+ for (const { name, relPath } of exports) {
208
+ if (!/^[A-Z]/.test(name)) continue;
209
+
210
+ const componentFile = findComponentFile(relPath);
211
+ if (!componentFile) continue;
212
+ const content = read(componentFile);
213
+
214
+ // Extract description from JSDoc
215
+ let idx = -1;
216
+ let description = "";
217
+ const patterns = [
218
+ `export function ${name}`,
219
+ `export const ${name}:`,
220
+ `export const ${name} =`,
221
+ `export class ${name}`,
222
+ new RegExp(`export\\s+const\\s+${name}\\s*[:=]`),
223
+ new RegExp(`export\\s+function\\s+${name}\\s*[(<]`),
224
+ ];
225
+
226
+ for (const pattern of patterns) {
227
+ if (typeof pattern === "string") {
228
+ idx = content.indexOf(pattern);
229
+ } else {
230
+ const match = content.search(pattern);
231
+ if (match !== -1) idx = match;
232
+ }
233
+ if (idx !== -1) break;
234
+ }
235
+
236
+ if (idx !== -1) {
237
+ description = getJsdocAbove(content, idx);
238
+ }
239
+
240
+ // Extract props
241
+ const props = extractProps(content, name);
242
+
243
+ // Extract Storybook metadata
244
+ const storyFile = findStorybookFile(relPath);
245
+ let category = "Uncategorized";
246
+ let relatedComponents = [];
247
+
248
+ if (storyFile) {
249
+ const storyContent = read(storyFile);
250
+ const storyCategory = extractStorybookCategory(storyContent);
251
+ if (storyCategory) category = storyCategory;
252
+ relatedComponents = extractRelatedComponents(storyContent);
253
+ }
254
+
255
+ const component = {
256
+ name,
257
+ category,
258
+ description,
259
+ importRoot: "@texturehq/edges",
260
+ importPath: `@texturehq/edges/components/${name}`,
261
+ props,
262
+ relatedComponents,
263
+ storybookPath: category + "/" + name,
264
+ };
265
+
266
+ components.push(component);
267
+
268
+ // Organize by category
269
+ if (!categories[category]) {
270
+ categories[category] = [];
271
+ }
272
+ categories[category].push(name);
273
+ }
274
+
275
+ // Sort components by name
276
+ components.sort((a, b) => a.name.localeCompare(b.name));
277
+
278
+ // Sort category arrays
279
+ for (const cat in categories) {
280
+ categories[cat].sort();
281
+ }
282
+
283
+ const manifest = {
284
+ version: PACKAGE_VERSION,
285
+ generatedAt: new Date().toISOString(),
286
+ components: components,
287
+ categories: categories,
288
+ };
289
+
290
+ ensureDir(DIST_DIR);
291
+ writeFile(path.join(DIST_DIR, "components.manifest.json"), JSON.stringify(manifest, null, 2));
292
+
293
+ console.log(` Found ${components.length} components in ${Object.keys(categories).length} categories`);
294
+ return manifest;
295
+ }
296
+
297
+ // ============================================================================
298
+ // UTILITIES & HOOKS MANIFEST GENERATION
299
+ // ============================================================================
300
+
301
+ function extractJSDoc(content, functionName) {
302
+ const patterns = [
303
+ new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+(?:async\\s+)?function\\s+${functionName}\\b`, "s"),
304
+ new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+const\\s+${functionName}\\s*=`, "s"),
305
+ ];
306
+
307
+ for (const pattern of patterns) {
308
+ const match = content.match(pattern);
309
+ if (match) {
310
+ const jsdocMatch = match[0].match(/\/\*\*([\s\S]*?)\*\//);
311
+ if (jsdocMatch) {
312
+ const cleaned = jsdocMatch[1]
313
+ .split("\n")
314
+ .map((line) => line.replace(/^\s*\*\s?/, "").trim())
315
+ .filter((line) => line && !line.startsWith("@"))
316
+ .join(" ")
317
+ .trim();
318
+ return cleaned;
319
+ }
320
+ }
321
+ }
322
+ return "";
323
+ }
324
+
325
+ function extractExportsFromFile(filePath) {
326
+ const content = read(filePath);
327
+ if (!content) return [];
328
+
329
+ const utilities = [];
330
+
331
+ // Match export function declarations
332
+ const functionPattern = /export\s+(?:async\s+)?function\s+(\w+)/g;
333
+ let match;
334
+ while ((match = functionPattern.exec(content))) {
335
+ const name = match[1];
336
+ utilities.push({
337
+ name,
338
+ type: "function",
339
+ description: extractJSDoc(content, name),
340
+ });
341
+ }
342
+
343
+ // Match export const arrow functions and constants
344
+ const constPattern = /export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=/g;
345
+ while ((match = constPattern.exec(content))) {
346
+ const name = match[1];
347
+ const afterMatch = content.slice(match.index + match[0].length, match.index + match[0].length + 50);
348
+ const isFunction = afterMatch.includes("=>") || afterMatch.includes("function");
349
+
350
+ utilities.push({
351
+ name,
352
+ type: isFunction ? "function" : "constant",
353
+ description: extractJSDoc(content, name),
354
+ });
355
+ }
356
+
357
+ return utilities;
358
+ }
359
+
360
+ function scanDirectory(dir, category) {
361
+ const utilities = [];
362
+
363
+ if (!fs.existsSync(dir)) return utilities;
364
+
365
+ const files = fs.readdirSync(dir);
366
+
367
+ for (const file of files) {
368
+ const filePath = path.join(dir, file);
369
+ const stat = fs.statSync(filePath);
370
+
371
+ if (stat.isDirectory() && file !== "__tests__" && file !== "tests") {
372
+ utilities.push(...scanDirectory(filePath, `${category}/${file}`));
373
+ } else if ((file.endsWith(".ts") || file.endsWith(".tsx")) && !file.endsWith(".d.ts")) {
374
+ if (file.includes(".test.") || file.includes(".spec.") || file.includes(".stories.")) {
375
+ continue;
376
+ }
377
+
378
+ const funcs = extractExportsFromFile(filePath);
379
+ for (const func of funcs) {
380
+ utilities.push({
381
+ ...func,
382
+ category,
383
+ file: path.relative(SRC_DIR, filePath),
384
+ });
385
+ }
386
+ }
387
+ }
388
+
389
+ return utilities;
390
+ }
391
+
392
+ function generateUtilitiesManifest() {
393
+ console.log("\n🔧 Generating utilities manifest...");
394
+
395
+ const manifest = {
396
+ version: PACKAGE_VERSION,
397
+ generatedAt: new Date().toISOString(),
398
+ categories: {},
399
+ };
400
+
401
+ // Scan hooks directory
402
+ const hooksDir = path.join(SRC_DIR, "hooks");
403
+ const hooks = scanDirectory(hooksDir, "hooks");
404
+ if (hooks.length > 0) {
405
+ manifest.categories.hooks = {
406
+ description:
407
+ "React hooks for common functionality like breakpoints, debouncing, local storage, and media queries",
408
+ utilities: hooks.sort((a, b) => a.name.localeCompare(b.name)),
409
+ };
410
+ }
411
+
412
+ // Scan formatting utilities
413
+ const formattingDir = path.join(SRC_DIR, "utils/formatting");
414
+ const formatting = scanDirectory(formattingDir, "formatting");
415
+ if (formatting.length > 0) {
416
+ manifest.categories.formatting = {
417
+ description:
418
+ "Comprehensive formatting utilities for text, numbers, dates, currency, energy, temperature, distance, and more",
419
+ utilities: formatting.sort((a, b) => a.name.localeCompare(b.name)),
420
+ };
421
+ }
422
+
423
+ // Chart utilities
424
+ const chartsDir = path.join(SRC_DIR, "utils/charts");
425
+ const charts = scanDirectory(chartsDir, "charts");
426
+ const chartExportUtils = extractExportsFromFile(path.join(SRC_DIR, "utils/chartExport.ts"));
427
+ const chartUtils = extractExportsFromFile(path.join(SRC_DIR, "utils/charts.ts"));
428
+ const allChartUtils = [
429
+ ...charts,
430
+ ...chartExportUtils.map((f) => ({ ...f, category: "charts", file: "utils/chartExport.ts" })),
431
+ ...chartUtils.map((f) => ({ ...f, category: "charts", file: "utils/charts.ts" })),
432
+ ];
433
+
434
+ if (allChartUtils.length > 0) {
435
+ manifest.categories.charts = {
436
+ description: "Chart utilities for data visualization, scaling, and export functionality",
437
+ utilities: allChartUtils.sort((a, b) => a.name.localeCompare(b.name)),
438
+ };
439
+ }
440
+
441
+ // Color utilities
442
+ const colorUtils = extractExportsFromFile(path.join(SRC_DIR, "utils/colors.ts"));
443
+ if (colorUtils.length > 0) {
444
+ manifest.categories.colors = {
445
+ description:
446
+ "Color management utilities for theme-aware color resolution, contrast calculation, and palette generation",
447
+ utilities: colorUtils
448
+ .map((f) => ({ ...f, category: "colors", file: "utils/colors.ts" }))
449
+ .sort((a, b) => a.name.localeCompare(b.name)),
450
+ };
451
+ }
452
+
453
+ const totalUtilities = Object.values(manifest.categories).reduce((sum, cat) => sum + cat.utilities.length, 0);
454
+
455
+ ensureDir(DIST_DIR);
456
+ writeFile(path.join(DIST_DIR, "utilities.manifest.json"), JSON.stringify(manifest, null, 2));
457
+
458
+ console.log(` Found ${totalUtilities} utilities in ${Object.keys(manifest.categories).length} categories`);
459
+ return manifest;
460
+ }
461
+
462
+ // ============================================================================
463
+ // THEME PARSING
464
+ // ============================================================================
465
+
466
+ function parseThemeColors() {
467
+ console.log("\n🎨 Parsing theme colors...");
468
+
469
+ const distThemePath = path.join(DIST_DIR, "theme.css");
470
+ const sources = [];
471
+
472
+ if (fs.existsSync(distThemePath)) {
473
+ sources.push(distThemePath);
474
+ } else {
475
+ const generatedDir = path.join(SRC_DIR, "generated");
476
+ const fallbackFiles = [
477
+ path.join(generatedDir, "tailwind-tokens-light.css"),
478
+ path.join(generatedDir, "tailwind-tokens-dark.css"),
479
+ path.join(generatedDir, "viz-runtime.css"),
480
+ ];
481
+
482
+ for (const file of fallbackFiles) {
483
+ if (fs.existsSync(file)) {
484
+ sources.push(file);
485
+ }
486
+ }
487
+
488
+ if (sources.length > 0) {
489
+ console.log(" ℹ️ Using generated token CSS as fallback (dist/theme.css missing)");
490
+ }
491
+ }
492
+
493
+ if (sources.length === 0) {
494
+ console.log(" ⚠️ No theme CSS sources found, skipping color extraction");
495
+ return {};
496
+ }
497
+
498
+ const colorVars = {};
499
+ const seen = new Set();
500
+ const colorRegex = /--color-([a-zA-Z0-9-]+):\s*([^;]+);/g;
501
+
502
+ for (const source of sources) {
503
+ const themeContent = read(source);
504
+ let match;
505
+
506
+ while ((match = colorRegex.exec(themeContent))) {
507
+ const varName = match[1];
508
+ const value = match[2].trim();
509
+
510
+ if (!seen.has(varName)) {
511
+ seen.add(varName);
512
+ colorVars[varName] = value;
513
+ }
514
+ }
515
+ }
516
+
517
+ console.log(` Found ${Object.keys(colorVars).length} color variables`);
518
+ return colorVars;
519
+ }
520
+
521
+ // ============================================================================
522
+ // MARKDOWN DOCUMENTATION GENERATION
523
+ // ============================================================================
524
+
525
+ function generateComponentsReference(componentsManifest) {
526
+ console.log("\n📝 Generating components reference...");
527
+
528
+ const { components, categories } = componentsManifest;
529
+
530
+ let content = `# Edges Components Reference
531
+
532
+ > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
533
+
534
+ This document provides a complete reference of all ${components.length} components in the @texturehq/edges design system, organized by functional category.
535
+
536
+ ## Quick Navigation
537
+
538
+ `;
539
+
540
+ // Table of contents by category
541
+ const sortedCategories = Object.keys(categories).sort();
542
+ for (const category of sortedCategories) {
543
+ const count = categories[category].length;
544
+ content += `- [${category}](#${category.toLowerCase().replace(/\s+/g, "-")}) (${count} component${count > 1 ? "s" : ""})\n`;
545
+ }
546
+
547
+ content += `\n---\n\n`;
548
+
549
+ // Generate content by category
550
+ for (const category of sortedCategories) {
551
+ content += `## ${category}\n\n`;
552
+
553
+ const categoryComponents = components.filter((c) => c.category === category);
554
+ categoryComponents.sort((a, b) => a.name.localeCompare(b.name));
555
+
556
+ for (const component of categoryComponents) {
557
+ content += `### ${component.name}\n\n`;
558
+
559
+ if (component.description) {
560
+ content += `${component.description}\n\n`;
561
+ }
562
+
563
+ content += `**Import:**\n\n`;
564
+ content += `\`\`\`typescript\n`;
565
+ content += `import { ${component.name} } from "${component.importRoot}";\n`;
566
+ content += `// or\n`;
567
+ content += `import { ${component.name} } from "${component.importPath}";\n`;
568
+ content += `\`\`\`\n\n`;
569
+
570
+ if (component.props && component.props.length > 0) {
571
+ content += `**Props:**\n\n`;
572
+ content += `| Prop | Type |\n`;
573
+ content += `|------|------|\n`;
574
+ for (const prop of component.props) {
575
+ content += `| \`${prop.name}\` | \`${prop.type}\` |\n`;
576
+ }
577
+ content += `\n`;
578
+ }
579
+
580
+ if (component.relatedComponents && component.relatedComponents.length > 0) {
581
+ content += `**Related:** ${component.relatedComponents.join(", ")}\n\n`;
582
+ }
583
+
584
+ if (component.storybookPath) {
585
+ content += `**Storybook:** \`${component.storybookPath}\`\n\n`;
586
+ }
587
+
588
+ content += `---\n\n`;
589
+ }
590
+ }
591
+
592
+ writeFile(path.join(DOCS_AI_EDGES, "components-reference.md"), content);
593
+ }
594
+
595
+ function generateHooksReference(utilitiesManifest) {
596
+ console.log("\n🪝 Generating hooks reference...");
597
+
598
+ const hooksCategory = utilitiesManifest.categories.hooks;
599
+ if (!hooksCategory) {
600
+ console.log(" No hooks found");
601
+ return;
602
+ }
603
+
604
+ const hooks = hooksCategory.utilities;
605
+
606
+ let content = `# Edges Hooks Reference
607
+
608
+ > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
609
+
610
+ This document provides a complete reference of all ${hooks.length} React hooks in the @texturehq/edges design system.
611
+
612
+ ${hooksCategory.description}
613
+
614
+ ## Available Hooks
615
+
616
+ `;
617
+
618
+ for (const hook of hooks) {
619
+ content += `### ${hook.name}\n\n`;
620
+
621
+ if (hook.description) {
622
+ content += `${hook.description}\n\n`;
623
+ }
624
+
625
+ content += `**Import:**\n\n`;
626
+ content += `\`\`\`typescript\n`;
627
+ content += `import { ${hook.name} } from "@texturehq/edges";\n`;
628
+ content += `\`\`\`\n\n`;
629
+
630
+ content += `**Source:** \`src/${hook.file}\`\n\n`;
631
+ content += `---\n\n`;
632
+ }
633
+
634
+ writeFile(path.join(DOCS_AI_EDGES, "hooks-reference.md"), content);
635
+ }
636
+
637
+ function generateUtilitiesReference(utilitiesManifest) {
638
+ console.log("\n🛠️ Generating utilities reference...");
639
+
640
+ const categories = Object.keys(utilitiesManifest.categories).filter((c) => c !== "hooks");
641
+ if (categories.length === 0) {
642
+ console.log(" No utilities found");
643
+ return;
644
+ }
645
+
646
+ let content = `# Edges Utilities Reference
647
+
648
+ > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
649
+
650
+ This document provides a complete reference of all utility functions in the @texturehq/edges design system.
651
+
652
+ ## Categories
653
+
654
+ `;
655
+
656
+ for (const category of categories) {
657
+ const cat = utilitiesManifest.categories[category];
658
+ content += `- [${category}](#${category}) (${cat.utilities.length} utilities)\n`;
659
+ }
660
+
661
+ content += `\n---\n\n`;
662
+
663
+ for (const category of categories) {
664
+ const cat = utilitiesManifest.categories[category];
665
+ content += `## ${category}\n\n`;
666
+ content += `${cat.description}\n\n`;
667
+
668
+ for (const util of cat.utilities) {
669
+ content += `### ${util.name}\n\n`;
670
+
671
+ if (util.description) {
672
+ content += `${util.description}\n\n`;
673
+ }
674
+
675
+ content += `**Type:** ${util.type}\n\n`;
676
+ content += `**Import:**\n\n`;
677
+ content += `\`\`\`typescript\n`;
678
+ content += `import { ${util.name} } from "@texturehq/edges";\n`;
679
+ content += `\`\`\`\n\n`;
680
+
681
+ content += `**Source:** \`src/${util.file}\`\n\n`;
682
+ content += `---\n\n`;
683
+ }
684
+ }
685
+
686
+ writeFile(path.join(DOCS_AI_EDGES, "utilities-reference.md"), content);
687
+ }
688
+
689
+ function generateThemeSystemDoc(colorVars) {
690
+ console.log("\n🎨 Generating theme system documentation...");
691
+
692
+ let content = `# Edges Theme System
693
+
694
+ > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
695
+
696
+ ## Overview
697
+
698
+ The @texturehq/edges design system uses Tailwind 4 with CSS variables. All design tokens are defined as CSS variables and automatically become available as Tailwind utility classes.
699
+
700
+ ## Setup
701
+
702
+ \`\`\`css
703
+ @import "@texturehq/edges/theme.css";
704
+ \`\`\`
705
+
706
+ ## How It Works
707
+
708
+ CSS variables automatically become Tailwind classes:
709
+ - \`--color-brand-primary\` → \`bg-brand-primary\`, \`text-brand-primary\`, \`border-brand-primary\`
710
+ - \`--spacing-md\` → \`p-md\`, \`m-md\`, \`gap-md\`
711
+ - \`--text-lg\` → \`text-lg\`
712
+ - \`--radius-lg\` → \`rounded-lg\`
713
+
714
+ ## Color System
715
+
716
+ The theme includes ${Object.keys(colorVars).length} color variables organized by purpose:
717
+
718
+ ### Brand Colors
719
+ - \`brand-primary\` - Primary brand color
720
+ - \`brand-light\` - Light brand variant
721
+ - \`brand-dark\` - Dark brand variant
722
+
723
+ ### Text Colors
724
+ - \`text-body\` - Body text
725
+ - \`text-heading\` - Heading text
726
+ - \`text-muted\` - Muted/secondary text
727
+ - \`text-caption\` - Caption/helper text
728
+ - \`text-on-primary\` - Text on primary backgrounds
729
+
730
+ ### Background Colors
731
+ - \`background-body\` - Main page background
732
+ - \`background-surface\` - Card/surface background
733
+ - \`background-muted\` - Muted background
734
+ - \`background-subtle\` - Subtle background
735
+
736
+ ### Border Colors
737
+ - \`border-default\` - Default border
738
+ - \`border-focus\` - Focused border
739
+ - \`border-muted\` - Muted border
740
+ - \`border-subtle\` - Subtle border
741
+
742
+ ### Action Colors
743
+ - \`action-primary\` - Primary actions
744
+ - \`action-secondary\` - Secondary actions
745
+ - \`action-destructive\` - Destructive actions
746
+
747
+ ### Feedback Colors
748
+ - \`feedback-success\` - Success state
749
+ - \`feedback-error\` - Error state
750
+ - \`feedback-warning\` - Warning state
751
+ - \`feedback-info\` - Info state
752
+
753
+ ## Usage Guidelines
754
+
755
+ **✅ Good - Use semantic classes:**
756
+ \`\`\`tsx
757
+ <div className="bg-brand-primary text-text-on-primary p-md rounded-lg shadow-md">
758
+ <h2 className="text-text-heading text-lg font-medium">Title</h2>
759
+ <p className="text-text-body text-base">Content</p>
760
+ </div>
761
+ \`\`\`
762
+
763
+ **❌ Avoid - Arbitrary values:**
764
+ \`\`\`tsx
765
+ <div className="bg-[#444ae1] text-[#ffffff] p-[1rem] rounded-[0.5rem]">
766
+ <h2 className="text-[#111827] text-[1.125rem] font-[500]">Title</h2>
767
+ <p className="text-[#333333] text-[1rem]">Content</p>
768
+ </div>
769
+ \`\`\`
770
+
771
+ ## Spacing Scale
772
+
773
+ - \`xs\`, \`sm\`, \`md\`, \`lg\`, \`xl\`, \`2xl\`, \`3xl\`, \`4xl\`
774
+
775
+ ## Typography Scale
776
+
777
+ - \`xs\`, \`sm\`, \`base\`, \`lg\`, \`xl\`, \`2xl\`, \`3xl\`, \`4xl\`
778
+
779
+ ## Border Radius Scale
780
+
781
+ - \`xs\`, \`sm\`, \`md\`, \`lg\`, \`xl\`, \`2xl\`, \`3xl\`, \`4xl\`
782
+
783
+ ## Dark Mode
784
+
785
+ All colors automatically adapt to dark mode when the \`.theme-dark\` class is present on a parent element.
786
+
787
+ ## Available Variables
788
+
789
+ The theme includes:
790
+ - Complete color system (${Object.keys(colorVars).length} color variables)
791
+ - Spacing scale
792
+ - Typography scale
793
+ - Border radius scale
794
+ - Shadow system
795
+ - Animation definitions
796
+ - Form control specifications
797
+ `;
798
+
799
+ writeFile(path.join(DOCS_AI_EDGES, "theme-system.md"), content);
800
+ }
801
+
802
+ function generateEdgesReadme(componentsManifest, utilitiesManifest) {
803
+ console.log("\n📄 Generating edges README...");
804
+
805
+ const componentCount = componentsManifest.components.length;
806
+ const categoryCount = Object.keys(componentsManifest.categories).length;
807
+ const hookCount = utilitiesManifest.categories.hooks?.utilities.length || 0;
808
+
809
+ let utilityCount = 0;
810
+ for (const cat in utilitiesManifest.categories) {
811
+ if (cat !== "hooks") {
812
+ utilityCount += utilitiesManifest.categories[cat].utilities.length;
813
+ }
814
+ }
815
+
816
+ let content = `# Edges Design System - AI Context
817
+
818
+ > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
819
+
820
+ 👋 **AI Agents: Start here!**
821
+
822
+ Complete documentation for the @texturehq/edges design system.
823
+
824
+ ## What's Inside
825
+
826
+ - **${componentCount} Components** across ${categoryCount} categories
827
+ - **${hookCount} React Hooks** for common functionality
828
+ - **${utilityCount} Utility Functions** for formatting, charts, colors, and more
829
+ - **Complete Theme System** with Tailwind 4 + CSS variables
830
+
831
+ ## Quick Links
832
+
833
+ ### Component Library
834
+ - [Components Reference](components-reference.md) - All ${componentCount} components organized by category
835
+ - [Component Categories](#component-categories) - Quick navigation by function
836
+
837
+ ### Utilities & Hooks
838
+ - [Hooks Reference](hooks-reference.md) - All ${hookCount} React hooks
839
+ - [Utilities Reference](utilities-reference.md) - All ${utilityCount} utility functions
840
+
841
+ ### Design System
842
+ - [Theme System](theme-system.md) - Colors, spacing, typography with Tailwind 4
843
+ - [Patterns](patterns.md) - Common UI patterns and best practices
844
+
845
+ ## Component Categories
846
+
847
+ `;
848
+
849
+ const sortedCategories = Object.keys(componentsManifest.categories).sort();
850
+ for (const category of sortedCategories) {
851
+ const components = componentsManifest.categories[category];
852
+ content += `### ${category}\n\n`;
853
+ content += `${components.join(", ")}\n\n`;
854
+ }
855
+
856
+ content += `## Installation
857
+
858
+ \`\`\`bash
859
+ yarn add @texturehq/edges
860
+ \`\`\`
861
+
862
+ ## Setup
863
+
864
+ \`\`\`css
865
+ @import "@texturehq/edges/theme.css";
866
+ \`\`\`
867
+
868
+ \`\`\`typescript
869
+ import { Button, TextField, DataTable } from "@texturehq/edges";
870
+ \`\`\`
871
+
872
+ ## For Dashboard Development
873
+
874
+ When building UI in \`apps/dashboard\`, reference:
875
+ - **Components**: \`docs/ai/edges/components-reference.md\`
876
+ - **Hooks**: \`docs/ai/edges/hooks-reference.md\`
877
+ - **Utilities**: \`docs/ai/edges/utilities-reference.md\`
878
+ - **Theme**: \`docs/ai/edges/theme-system.md\`
879
+ - **Patterns**: \`docs/ai/edges/patterns.md\`
880
+
881
+ ---
882
+
883
+ **Package Version:** ${componentsManifest.version}
884
+ **Generated:** ${new Date().toISOString()}
885
+ `;
886
+
887
+ writeFile(path.join(DOCS_AI_EDGES, "README.md"), content);
888
+ }
889
+
890
+ // ============================================================================
891
+ // AGENT INSTRUCTION FRAGMENT
892
+ // ============================================================================
893
+
894
+ function generateEdgesFragment(componentsManifest, utilitiesManifest) {
895
+ console.log("\n📄 Generating edges fragment...");
896
+
897
+ const componentCount = componentsManifest.components.length;
898
+ const hookCount = utilitiesManifest.categories.hooks?.utilities.length || 0;
899
+
900
+ let utilityCount = 0;
901
+ for (const cat in utilitiesManifest.categories) {
902
+ if (cat !== "hooks") {
903
+ utilityCount += utilitiesManifest.categories[cat].utilities.length;
904
+ }
905
+ }
906
+
907
+ const content = `<!-- priority: 20 -->
908
+ ## Edges Design System Context
909
+
910
+ The @texturehq/edges design system provides ${componentCount} React components, ${hookCount} hooks, and ${utilityCount} utility functions.
911
+
912
+ **Complete component documentation is in**: \`docs/ai/edges/\`
913
+
914
+ ### Quick Start
915
+
916
+ 1. Read \`docs/ai/edges/README.md\` for an overview and component categories
917
+ 2. Reference \`docs/ai/edges/components-reference.md\` for detailed component API
918
+ 3. Review \`docs/ai/edges/patterns.md\` for common UI patterns and best practices
919
+ 4. Check \`docs/ai/edges/theme-system.md\` for Tailwind 4 color tokens and styling
920
+
921
+ ### Additional Resources
922
+
923
+ - Hooks: \`docs/ai/edges/hooks-reference.md\`
924
+ - Utilities: \`docs/ai/edges/utilities-reference.md\`
925
+
926
+ ### For Dashboard Development
927
+
928
+ **UI (Edges)**:
929
+ - All components, hooks, and utilities from \`@texturehq/edges\`
930
+ - Use semantic Tailwind classes from theme (see theme-system.md)
931
+ - Follow patterns from \`docs/ai/edges/patterns.md\`
932
+
933
+ ### Common Patterns
934
+
935
+ **Import**: \`import { Button, TextField } from "@texturehq/edges"\`
936
+ **Styling**: Use semantic classes like \`bg-brand-primary\`, \`text-text-body\`
937
+ **Forms**: Wrap in \`<Form>\`, use validation with \`errorMessage\`
938
+ **Loading**: Show \`<Skeleton>\` during data fetching
939
+ **Responsive**: Use \`useBreakpoint()\` hook or responsive grid cols
940
+ `;
941
+
942
+ const fragmentsDir = path.join(MONO_ROOT, ".agent-fragments");
943
+ ensureDir(fragmentsDir);
944
+ writeFile(path.join(fragmentsDir, "edges.md"), content);
945
+ }
946
+
947
+ // ============================================================================
948
+ // MAIN
949
+ // ============================================================================
950
+
951
+ async function main() {
952
+ console.log("🚀 Generating Edges AI Context...\n");
953
+
954
+ // Ensure output directories exist
955
+ ensureDir(DOCS_AI_EDGES);
956
+ ensureDir(DIST_DIR);
957
+
958
+ // Generate manifests
959
+ const componentsManifest = generateComponentsManifest();
960
+ const utilitiesManifest = generateUtilitiesManifest();
961
+
962
+ // Parse theme
963
+ const colorVars = parseThemeColors();
964
+
965
+ // Generate markdown documentation
966
+ generateComponentsReference(componentsManifest);
967
+ generateHooksReference(utilitiesManifest);
968
+ generateUtilitiesReference(utilitiesManifest);
969
+ generateThemeSystemDoc(colorVars);
970
+ generateEdgesReadme(componentsManifest, utilitiesManifest);
971
+
972
+ // Generate agent instruction fragment
973
+ generateEdgesFragment(componentsManifest, utilitiesManifest);
974
+
975
+ // Merge all agent instruction fragments
976
+ console.log("\n🔀 Merging agent instruction fragments...");
977
+ try {
978
+ execSync("ts-node scripts/merge-agent-instructions.ts", {
979
+ cwd: MONO_ROOT,
980
+ stdio: "inherit",
981
+ });
982
+ } catch (err) {
983
+ console.log("⚠️ Note: Could not merge agent instructions (merge script may not be available yet)");
984
+ }
985
+
986
+ console.log("\n✅ Edges AI context generation complete!");
987
+ console.log("\nGenerated files:");
988
+ console.log(" - dist/components.manifest.json");
989
+ console.log(" - dist/utilities.manifest.json");
990
+ console.log(" - docs/ai/edges/README.md");
991
+ console.log(" - docs/ai/edges/components-reference.md");
992
+ console.log(" - docs/ai/edges/hooks-reference.md");
993
+ console.log(" - docs/ai/edges/utilities-reference.md");
994
+ console.log(" - docs/ai/edges/theme-system.md");
995
+ console.log(" - .agent-fragments/edges.md");
996
+ console.log(" - .claude/instructions.md (merged)");
997
+ console.log(" - .codex/instructions.md (merged)");
998
+ console.log(" - .github/copilot-instructions.md (merged)");
999
+ }
1000
+
1001
+ main().catch((err) => {
1002
+ console.error("❌ Failed to generate AI context:", err);
1003
+ process.exit(1);
1004
+ });