farmon 0.1.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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/bin/farmon.js +12 -0
  4. package/dist/bin/farmon.js.map +1 -0
  5. package/dist/execute/agents/index.js +19 -0
  6. package/dist/execute/agents/index.js.map +1 -0
  7. package/dist/execute/agents/instruction-classifier-agent.js +16 -0
  8. package/dist/execute/agents/instruction-classifier-agent.js.map +1 -0
  9. package/dist/execute/agents/mutation-agent.js +272 -0
  10. package/dist/execute/agents/mutation-agent.js.map +1 -0
  11. package/dist/execute/agents/query-agent.js +118 -0
  12. package/dist/execute/agents/query-agent.js.map +1 -0
  13. package/dist/execute/helpers/analyzers.js +8 -0
  14. package/dist/execute/helpers/analyzers.js.map +1 -0
  15. package/dist/execute/helpers/ensurers.js +1053 -0
  16. package/dist/execute/helpers/ensurers.js.map +1 -0
  17. package/dist/execute/helpers/finders.js +1454 -0
  18. package/dist/execute/helpers/finders.js.map +1 -0
  19. package/dist/execute/helpers/general.js +3736 -0
  20. package/dist/execute/helpers/general.js.map +1 -0
  21. package/dist/execute/helpers/import-helpers.js +183 -0
  22. package/dist/execute/helpers/import-helpers.js.map +1 -0
  23. package/dist/execute/helpers/parsers.js +840 -0
  24. package/dist/execute/helpers/parsers.js.map +1 -0
  25. package/dist/execute/helpers/prompt-maker.js +1163 -0
  26. package/dist/execute/helpers/prompt-maker.js.map +1 -0
  27. package/dist/execute/helpers/validators.js +40 -0
  28. package/dist/execute/helpers/validators.js.map +1 -0
  29. package/dist/execute/history/history-manager.js +1030 -0
  30. package/dist/execute/history/history-manager.js.map +1 -0
  31. package/dist/execute/history/rollback-handlers.js +2524 -0
  32. package/dist/execute/history/rollback-handlers.js.map +1 -0
  33. package/dist/execute/index.js +44 -0
  34. package/dist/execute/index.js.map +1 -0
  35. package/dist/execute/llm/call.js +103 -0
  36. package/dist/execute/llm/call.js.map +1 -0
  37. package/dist/execute/tasks/ast.js +3819 -0
  38. package/dist/execute/tasks/ast.js.map +1 -0
  39. package/dist/execute/tasks/generators.js +96 -0
  40. package/dist/execute/tasks/generators.js.map +1 -0
  41. package/dist/execute/tasks/index.js +7 -0
  42. package/dist/execute/tasks/index.js.map +1 -0
  43. package/dist/execute/tasks/mutations.js +8139 -0
  44. package/dist/execute/tasks/mutations.js.map +1 -0
  45. package/dist/execute/tasks/query.js +248 -0
  46. package/dist/execute/tasks/query.js.map +1 -0
  47. package/dist/index.js +6 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/providers/index.js +15 -0
  50. package/dist/providers/index.js.map +1 -0
  51. package/dist/providers/ollama.js +40 -0
  52. package/dist/providers/ollama.js.map +1 -0
  53. package/dist/providers/openai-compatible.js +52 -0
  54. package/dist/providers/openai-compatible.js.map +1 -0
  55. package/dist/runtime/inject.js +250 -0
  56. package/dist/runtime/inject.js.map +1 -0
  57. package/dist/schemas/agent/action.schema.js +935 -0
  58. package/dist/schemas/agent/action.schema.js.map +1 -0
  59. package/dist/schemas/agent/index.js +4 -0
  60. package/dist/schemas/agent/index.js.map +1 -0
  61. package/dist/schemas/agent/llm.schema.js +16 -0
  62. package/dist/schemas/agent/llm.schema.js.map +1 -0
  63. package/dist/schemas/agent/planner.schema.js +17 -0
  64. package/dist/schemas/agent/planner.schema.js.map +1 -0
  65. package/dist/schemas/index.js +7 -0
  66. package/dist/schemas/index.js.map +1 -0
  67. package/dist/schemas/project/context.schema.js +2 -0
  68. package/dist/schemas/project/context.schema.js.map +1 -0
  69. package/dist/schemas/project/index.js +2 -0
  70. package/dist/schemas/project/index.js.map +1 -0
  71. package/dist/schemas/runtime/index.js +4 -0
  72. package/dist/schemas/runtime/index.js.map +1 -0
  73. package/dist/schemas/runtime/injector.schema.js +11 -0
  74. package/dist/schemas/runtime/injector.schema.js.map +1 -0
  75. package/dist/schemas/runtime/runtime.schema.js +73 -0
  76. package/dist/schemas/runtime/runtime.schema.js.map +1 -0
  77. package/dist/schemas/runtime/sse.schema.js +15 -0
  78. package/dist/schemas/runtime/sse.schema.js.map +1 -0
  79. package/dist/schemas/system/index.js +2 -0
  80. package/dist/schemas/system/index.js.map +1 -0
  81. package/dist/schemas/system/logger.schema.js +56 -0
  82. package/dist/schemas/system/logger.schema.js.map +1 -0
  83. package/dist/schemas/task/index.js +9 -0
  84. package/dist/schemas/task/index.js.map +1 -0
  85. package/dist/server/app-context.js +254 -0
  86. package/dist/server/app-context.js.map +1 -0
  87. package/dist/server/config.js +22 -0
  88. package/dist/server/config.js.map +1 -0
  89. package/dist/server/error.js +22 -0
  90. package/dist/server/error.js.map +1 -0
  91. package/dist/server/event-bus.js +60 -0
  92. package/dist/server/event-bus.js.map +1 -0
  93. package/dist/server/logger.js +57 -0
  94. package/dist/server/logger.js.map +1 -0
  95. package/dist/server/run.js +265 -0
  96. package/dist/server/run.js.map +1 -0
  97. package/dist/server/sse.js +143 -0
  98. package/dist/server/sse.js.map +1 -0
  99. package/dist/ui/assets/index-C4ydQSAw.css +2 -0
  100. package/dist/ui/assets/index-Dzo7S5xs.js +85 -0
  101. package/dist/ui/favicon.svg +1 -0
  102. package/dist/ui/icons.svg +24 -0
  103. package/dist/ui/index.html +14 -0
  104. package/dist/workers/prettier.js +11 -0
  105. package/dist/workers/prettier.js.map +1 -0
  106. package/package.json +114 -0
@@ -0,0 +1,3736 @@
1
+ /*
2
+
3
+ Helper tasks: Discovery, parsing,
4
+ normalization, analysis, and other utilities that support the main tasks.
5
+ ------------
6
+ ensureLibrary
7
+ ensureImport
8
+ removeImport
9
+ enrichImport
10
+ optimizeImports
11
+ resolveImportConflicts
12
+ findComponentDirectory
13
+ findNodeByLine
14
+ findComponentByName
15
+ findJSXElement
16
+ ensureComponentStructure
17
+ updateComponentImports
18
+ normalizeComponent
19
+ inferComponentName
20
+ ensureStyleFile
21
+ renameCssClass
22
+ generateClassNames
23
+ syncComponentStyles
24
+ findCssSelector
25
+ resolveCssClassConflicts
26
+ resolveStyleDependencies
27
+
28
+ */
29
+ import fs from "fs";
30
+ import path from "path";
31
+ import traverse from "@babel/traverse";
32
+ import { parse } from "@babel/parser";
33
+ import { generate } from "@babel/generator";
34
+ import t from "@babel/types";
35
+ import postcss from "postcss";
36
+ import crypto from "crypto";
37
+ import { createSyncFn } from "synckit";
38
+ import { ERROR_CODES, TaskRegistry } from "../../schemas/index.js";
39
+ import parsers from "./parsers.js";
40
+ import { LoomaError } from "../../server/error.js";
41
+ import { getInitializationContext } from "../../server/app-context.js";
42
+ // function informUser({ message }) {
43
+ // sse.emitInfo(message);
44
+ // }
45
+ /**
46
+ * Removes a library from package.json.
47
+ *
48
+ * ------------------------------------------------------------
49
+ * WHAT THIS FUNCTION DOES
50
+ * ------------------------------------------------------------
51
+ *
52
+ * This function removes a package from:
53
+ *
54
+ * - dependencies
55
+ * OR
56
+ * - devDependencies
57
+ *
58
+ * If the package does not exist:
59
+ * - nothing changes
60
+ *
61
+ * ------------------------------------------------------------
62
+ * IMPORTANT NOTE
63
+ * ------------------------------------------------------------
64
+ *
65
+ * This function ONLY updates package.json.
66
+ *
67
+ * It DOES NOT:
68
+ * - run npm uninstall
69
+ * - remove node_modules
70
+ * - remove imports from source files
71
+ *
72
+ * Those responsibilities should remain separate.
73
+ *
74
+ *
75
+ * Useful commands:
76
+ * - remove redux: removeLibrary("@reduxjs/toolkit")
77
+ * - remove tailwind: removeLibrary("tailwindcss")
78
+ * - remove charts: removeLibrary("recharts")
79
+ * - remove icons package: removeLibrary("lucide-react")
80
+ * - stop using router: removeLibrary("react-router-dom")
81
+ *
82
+ * ------------------------------------------------------------
83
+ * WHY THIS SEPARATION IS IMPORTANT
84
+ * ------------------------------------------------------------
85
+ *
86
+ * Dependency management and source-code cleanup are different
87
+ * architectural concerns.
88
+ *
89
+ * Example:
90
+ *
91
+ * removeLibrary()
92
+ * -> package.json mutation
93
+ *
94
+ * removeImport()
95
+ * -> source code mutation
96
+ *
97
+ * optimizeImports()
98
+ * -> dead import cleanup
99
+ *
100
+ * ------------------------------------------------------------
101
+ * EXAMPLE
102
+ * ------------------------------------------------------------
103
+ *
104
+ * removeLibrary({
105
+ * projectPath: "/my-app",
106
+ * libraryName: "lodash"
107
+ * });
108
+ *
109
+ * ------------------------------------------------------------
110
+ * PARAMS
111
+ * ------------------------------------------------------------
112
+ *
113
+ * @param {Object} params
114
+ *
115
+ * @param {string} params.projectPath
116
+ * Path to project root directory.
117
+ *
118
+ * @param {string} params.libraryName
119
+ * Name of package to remove.
120
+ *
121
+ * ------------------------------------------------------------
122
+ * RETURNS
123
+ * ------------------------------------------------------------
124
+ *
125
+ * @returns {{
126
+ * modified: boolean,
127
+ * removedFrom: string|null,
128
+ * packageJson: Object
129
+ * }}
130
+ *
131
+ * modified:
132
+ * true -> package removed
133
+ * false -> package not found
134
+ *
135
+ * removedFrom:
136
+ * "dependencies"
137
+ * "devDependencies"
138
+ * null
139
+ *
140
+ */
141
+ // function removeLibrary({ projectPath, libraryName }) {
142
+ // // ----------------------------------------------------------
143
+ // // STEP 1:
144
+ // // Build package.json absolute path
145
+ // // ----------------------------------------------------------
146
+ // const packageJsonPath = path.join(projectPath, "package.json");
147
+ // // ----------------------------------------------------------
148
+ // // STEP 2:
149
+ // // Ensure package.json exists
150
+ // // ----------------------------------------------------------
151
+ // if (!fs.existsSync(packageJsonPath)) {
152
+ // throw new Error(`package.json not found at: ${packageJsonPath}`);
153
+ // }
154
+ // // ----------------------------------------------------------
155
+ // // STEP 3:
156
+ // // Read package.json as text
157
+ // // ----------------------------------------------------------
158
+ // const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
159
+ // // ----------------------------------------------------------
160
+ // // STEP 4:
161
+ // // Convert JSON string into JS object
162
+ // // ----------------------------------------------------------
163
+ // const packageJson = JSON.parse(packageJsonContent);
164
+ // // ----------------------------------------------------------
165
+ // // STEP 5:
166
+ // // Track where library was removed from
167
+ // // ----------------------------------------------------------
168
+ // let removedFrom = null;
169
+ // // ----------------------------------------------------------
170
+ // // STEP 6:
171
+ // // Check dependencies
172
+ // //
173
+ // // Example:
174
+ // //
175
+ // // {
176
+ // // dependencies: {
177
+ // // react: "^19.0.0"
178
+ // // }
179
+ // // }
180
+ // // ----------------------------------------------------------
181
+ // if (packageJson.dependencies && packageJson.dependencies[libraryName]) {
182
+ // // --------------------------------------------------------
183
+ // // Remove package
184
+ // // --------------------------------------------------------
185
+ // delete packageJson.dependencies[libraryName];
186
+ // // --------------------------------------------------------
187
+ // // Track removal source
188
+ // // --------------------------------------------------------
189
+ // removedFrom = "dependencies";
190
+ // }
191
+ // // ----------------------------------------------------------
192
+ // // STEP 7:
193
+ // // Check devDependencies
194
+ // // ----------------------------------------------------------
195
+ // if (packageJson.devDependencies && packageJson.devDependencies[libraryName]) {
196
+ // // --------------------------------------------------------
197
+ // // Remove package
198
+ // // --------------------------------------------------------
199
+ // delete packageJson.devDependencies[libraryName];
200
+ // // --------------------------------------------------------
201
+ // // Track removal source
202
+ // // --------------------------------------------------------
203
+ // removedFrom = "devDependencies";
204
+ // }
205
+ // // ----------------------------------------------------------
206
+ // // STEP 8:
207
+ // // If package was not found:
208
+ // // return unchanged
209
+ // // ----------------------------------------------------------
210
+ // if (!removedFrom) {
211
+ // return {
212
+ // modified: false,
213
+ // removedFrom: null,
214
+ // packageJson,
215
+ // };
216
+ // }
217
+ // // ----------------------------------------------------------
218
+ // // STEP 9:
219
+ // // Sort dependencies alphabetically
220
+ // //
221
+ // // WHY?
222
+ // //
223
+ // // Stable ordering:
224
+ // // - cleaner git diffs
225
+ // // - deterministic formatting
226
+ // // ----------------------------------------------------------
227
+ // if (packageJson.dependencies) {
228
+ // packageJson.dependencies = Object.fromEntries(
229
+ // Object.entries(packageJson.dependencies).sort(([a], [b]) =>
230
+ // a.localeCompare(b)
231
+ // )
232
+ // );
233
+ // }
234
+ // // ----------------------------------------------------------
235
+ // // STEP 10:
236
+ // // Sort devDependencies alphabetically
237
+ // // ----------------------------------------------------------
238
+ // if (packageJson.devDependencies) {
239
+ // packageJson.devDependencies = Object.fromEntries(
240
+ // Object.entries(packageJson.devDependencies).sort(([a], [b]) =>
241
+ // a.localeCompare(b)
242
+ // )
243
+ // );
244
+ // }
245
+ // // ----------------------------------------------------------
246
+ // // STEP 11:
247
+ // // Convert JS object back into formatted JSON string
248
+ // // ----------------------------------------------------------
249
+ // const updatedContent = JSON.stringify(packageJson, null, 2);
250
+ // // ----------------------------------------------------------
251
+ // // STEP 12:
252
+ // // Write updated package.json back to disk
253
+ // // ----------------------------------------------------------
254
+ // fs.writeFileSync(packageJsonPath, `${updatedContent}\n`, "utf-8");
255
+ // // ----------------------------------------------------------
256
+ // // STEP 13:
257
+ // // Return success metadata
258
+ // // ----------------------------------------------------------
259
+ // return {
260
+ // modified: true,
261
+ // removedFrom,
262
+ // packageJson,
263
+ // };
264
+ // }
265
+ /**
266
+ * this function updates the version of a library in package.json. it looks for the library in all dependency sections and updates the version. if the library is not found, it throws an error.
267
+ * @param pkgPath
268
+ * @param param1
269
+ */
270
+ // function updateLibraryVersion({ pkgPath, packageName, newVersion }) {
271
+ // const pkgRaw = fs.readFileSync(pkgPath, "utf-8");
272
+ // const pkg = JSON.parse(pkgRaw);
273
+ // let updated = false;
274
+ // const sections = [
275
+ // "dependencies",
276
+ // "devDependencies",
277
+ // "peerDependencies",
278
+ // "optionalDependencies",
279
+ // ];
280
+ // for (const section of sections) {
281
+ // if (pkg[section] && pkg[section][packageName]) {
282
+ // pkg[section][packageName] = newVersion;
283
+ // updated = true;
284
+ // }
285
+ // }
286
+ // if (!updated) {
287
+ // throw new Error(`Package "${packageName}" not found in dependencies`);
288
+ // }
289
+ // fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
290
+ // }
291
+ /**
292
+ * Resolves import naming conflicts inside a file.
293
+ *
294
+ * ------------------------------------------------------------
295
+ * WHAT THIS FUNCTION DOES
296
+ * ------------------------------------------------------------
297
+ *
298
+ * This function detects conflicting import identifiers
299
+ * and automatically renames imports safely.
300
+ *
301
+ * Example:
302
+ *
303
+ * BEFORE:
304
+ *
305
+ * import Button from "./Button";
306
+ * import Button from "./UI/Button";
307
+ *
308
+ * AFTER:
309
+ *
310
+ * import Button from "./Button";
311
+ * import UIButton from "./UI/Button";
312
+ *
313
+ * ------------------------------------------------------------
314
+ * WHY THIS FUNCTION EXISTS
315
+ * ------------------------------------------------------------
316
+ *
317
+ * In AI-powered frontend systems,
318
+ * imports are often generated dynamically.
319
+ *
320
+ * Example:
321
+ *
322
+ * User says:
323
+ *
324
+ * "add button component"
325
+ *
326
+ * LLM may generate:
327
+ *
328
+ * import Button from "./Button";
329
+ *
330
+ * But:
331
+ *
332
+ * Button may already exist.
333
+ *
334
+ * This creates:
335
+ *
336
+ * duplicate identifier errors.
337
+ *
338
+ * ------------------------------------------------------------
339
+ * WHAT IS IMPORT CONFLICT?
340
+ * ------------------------------------------------------------
341
+ *
342
+ * Conflict occurs when:
343
+ *
344
+ * two imports introduce same variable name.
345
+ *
346
+ * Example:
347
+ *
348
+ * import Card from "./Card";
349
+ * import Card from "./UI/Card";
350
+ *
351
+ * Both define:
352
+ *
353
+ * Card
354
+ *
355
+ * in same scope.
356
+ *
357
+ * Useful in:
358
+ * - ensureImport
359
+ * - includeImport
360
+ * - optimizeImports
361
+ * - createComponent
362
+ * - insertJSX
363
+ * - mergeComponents
364
+ * - codeReplace
365
+ * parseAST
366
+
367
+ resolveImportConflicts
368
+
369
+ generateCode
370
+ * ------------------------------------------------------------
371
+ * IMPORTANT ARCHITECTURAL IDEA
372
+ * ------------------------------------------------------------
373
+ *
374
+ * resolveImportConflicts is:
375
+ *
376
+ * INTERNAL ONLY
377
+ *
378
+ * WHY?
379
+ *
380
+ * Users never directly ask:
381
+ *
382
+ * "resolve import conflicts"
383
+ *
384
+ * Instead:
385
+ *
386
+ * higher-level primitives internally use it.
387
+ *
388
+ * ------------------------------------------------------------
389
+ * WHAT THIS FUNCTION HANDLES
390
+ * ------------------------------------------------------------
391
+ *
392
+ * Current implementation handles:
393
+ *
394
+ * - duplicate default imports
395
+ * - duplicate named imports
396
+ * - duplicate namespace imports
397
+ *
398
+ * ------------------------------------------------------------
399
+ * CONFLICT RESOLUTION STRATEGY
400
+ * ------------------------------------------------------------
401
+ *
402
+ * If identifier already exists:
403
+ *
404
+ * generate unique alias.
405
+ *
406
+ * Example:
407
+ *
408
+ * Button
409
+ * Button1
410
+ * Button2
411
+ * Button3
412
+ *
413
+ * until unique identifier found.
414
+ *
415
+ * ------------------------------------------------------------
416
+ * IMPORTANT NOTE
417
+ * ------------------------------------------------------------
418
+ *
419
+ * This function updates:
420
+ *
421
+ * import declarations only.
422
+ *
423
+ * It does NOT automatically rename usages.
424
+ *
425
+ * WHY?
426
+ *
427
+ * Usage updates belong to:
428
+ *
429
+ * renameVariable
430
+ * or
431
+ * identifier refactor utilities.
432
+ *
433
+ * ------------------------------------------------------------
434
+ * IMPORTANT LIMITATION
435
+ * ------------------------------------------------------------
436
+ *
437
+ * Current implementation:
438
+ *
439
+ * - only checks import declarations
440
+ *
441
+ * NOT:
442
+ *
443
+ * - local variables
444
+ * - function params
445
+ * - component names
446
+ *
447
+ * Future improvements:
448
+ *
449
+ * - scope-aware resolution
450
+ * - full identifier graph
451
+ * - automatic usage rewrites
452
+ * - TS type import support
453
+ *
454
+ * ------------------------------------------------------------
455
+ * PARAMS
456
+ * ------------------------------------------------------------
457
+ *
458
+ * @param {Object} params
459
+ *
460
+ * @param {object} params.ast
461
+ * Babel AST object.
462
+ *
463
+ * ------------------------------------------------------------
464
+ * RETURNS
465
+ * ------------------------------------------------------------
466
+ *
467
+ * @returns {{
468
+ * ast: object,
469
+ * code: string
470
+ * }}
471
+ *
472
+ */
473
+ // function resolveImportConflicts({
474
+ // ast,
475
+ // }: TaskPayload<any>): TaskResponse<TaskReturn<any>> {
476
+ // // ----------------------------------------------------------
477
+ // // STEP 1:
478
+ // // Store already-used identifiers
479
+ // //
480
+ // // Example:
481
+ // //
482
+ // // Button
483
+ // // Header
484
+ // // React
485
+ // // ----------------------------------------------------------
486
+ // const usedIdentifiers = new Set();
487
+ // // ----------------------------------------------------------
488
+ // // STEP 2:
489
+ // // Traverse all import declarations
490
+ // // ----------------------------------------------------------
491
+ // traverse.default(ast, {
492
+ // ImportDeclaration(path) {
493
+ // // ------------------------------------------------------
494
+ // // Iterate all import specifiers
495
+ // //
496
+ // // Examples:
497
+ // //
498
+ // // import React from "react"
499
+ // // import { useState } from "react"
500
+ // // import * as UI from "./ui"
501
+ // // ------------------------------------------------------
502
+ // path.node.specifiers.forEach((specifier) => {
503
+ // // --------------------------------------------------
504
+ // // Extract local identifier name
505
+ // //
506
+ // // Example:
507
+ // //
508
+ // // import Button from "./Button"
509
+ // //
510
+ // // local.name = Button
511
+ // // --------------------------------------------------
512
+ // const localName = specifier.local.name;
513
+ // // --------------------------------------------------
514
+ // // If identifier not used yet:
515
+ // // store it safely
516
+ // // --------------------------------------------------
517
+ // if (!usedIdentifiers.has(localName)) {
518
+ // usedIdentifiers.add(localName);
519
+ // return;
520
+ // }
521
+ // // --------------------------------------------------
522
+ // // CONFLICT DETECTED
523
+ // //
524
+ // // Need unique replacement name.
525
+ // // --------------------------------------------------
526
+ // let counter = 1;
527
+ // // --------------------------------------------------
528
+ // // Start with:
529
+ // //
530
+ // // Button1
531
+ // // --------------------------------------------------
532
+ // let newName = `${localName}${counter}`;
533
+ // // --------------------------------------------------
534
+ // // Keep generating until unique name found
535
+ // // --------------------------------------------------
536
+ // while (usedIdentifiers.has(newName)) {
537
+ // counter++;
538
+ // newName = `${localName}${counter}`;
539
+ // }
540
+ // // --------------------------------------------------
541
+ // // Mark new name as used
542
+ // // --------------------------------------------------
543
+ // usedIdentifiers.add(newName);
544
+ // // --------------------------------------------------
545
+ // // Replace conflicting identifier
546
+ // //
547
+ // // Example:
548
+ // //
549
+ // // Button -> Button1
550
+ // // --------------------------------------------------
551
+ // specifier.local = t.identifier(newName);
552
+ // });
553
+ // },
554
+ // });
555
+ // // ----------------------------------------------------------
556
+ // // STEP 3:
557
+ // // Generate updated source code
558
+ // // ----------------------------------------------------------
559
+ // const output = generate(ast, {
560
+ // retainLines: true,
561
+ // });
562
+ // // ----------------------------------------------------------
563
+ // // STEP 4:
564
+ // // Return updated AST + code
565
+ // // ----------------------------------------------------------
566
+ // return {
567
+ // ast,
568
+ // code: output.code,
569
+ // };
570
+ // }
571
+ /**
572
+ *
573
+ * Updates import statements inside a component file.
574
+ * ------------------------------------------------------------
575
+ * WHAT THIS FUNCTION DOES
576
+ * ------------------------------------------------------------
577
+ *
578
+ * This function safely manages:
579
+ *
580
+ * - adding imports
581
+ * - removing imports
582
+ * - replacing imports
583
+ * - fixing import paths
584
+ * - deduplicating imports
585
+ *
586
+ * inside a component file.
587
+ *
588
+ * Example:
589
+ *
590
+ * BEFORE:
591
+ *
592
+ * import Button from "./Button";
593
+ *
594
+ * AFTER:
595
+ *
596
+ * import Button from "../ui/Button";
597
+ *
598
+ * ------------------------------------------------------------
599
+ * WHY THIS FUNCTION EXISTS
600
+ * ------------------------------------------------------------
601
+ *
602
+ * AI-generated projects frequently suffer from:
603
+ *
604
+ * - duplicate imports
605
+ * - broken relative paths
606
+ * - stale imports
607
+ * - missing imports
608
+ * - inconsistent ordering
609
+ *
610
+ * Runtime component manipulation
611
+ * becomes impossible without:
612
+ *
613
+ * deterministic import management.
614
+ *
615
+ * This function acts as:
616
+ *
617
+ * import synchronization layer.
618
+ *
619
+ * ------------------------------------------------------------
620
+ * IMPORTANT ARCHITECTURAL DECISION
621
+ * ------------------------------------------------------------
622
+ *
623
+ * Looma should NEVER:
624
+ *
625
+ * blindly append imports.
626
+ *
627
+ * Imports should always remain:
628
+ *
629
+ * - normalized
630
+ * - deduplicated
631
+ * - deterministic
632
+ * - architecture-safe
633
+ *
634
+ * ------------------------------------------------------------
635
+ * IMPORTANT NOTE
636
+ * ------------------------------------------------------------
637
+ *
638
+ * updateComponentImports only removes imports it does not update JSX
639
+ *
640
+ *
641
+ * Current implementation:
642
+ *
643
+ * uses string-based parsing.
644
+ *
645
+ * Production version should use:
646
+ *
647
+ * Babel AST import manipulation.
648
+ *
649
+ * because regex/string parsing becomes fragile
650
+ * for:
651
+ *
652
+ * - multiline imports
653
+ * - namespace imports
654
+ * - mixed imports
655
+ * - comments
656
+ * - dynamic imports
657
+ *
658
+ * ------------------------------------------------------------
659
+ * DEPENDENCIES
660
+ * ------------------------------------------------------------
661
+ *
662
+ * This function complements:
663
+ *
664
+ * - createComponent()
665
+ * - updateComponent()
666
+ * - renameComponent()
667
+ * - moveComponent()
668
+ * - extractComponent()
669
+ * - ensureComponentStructure()
670
+ * - ensureImport()
671
+ * - enrichImports()
672
+ * - removeImport()
673
+ *
674
+ * ------------------------------------------------------------
675
+ * WHERE THIS FUNCTION IS USEFUL
676
+ * ------------------------------------------------------------
677
+ *
678
+ * Useful in commands like:
679
+ *
680
+ * - "extract component"
681
+ * - "move component"
682
+ * - "rename component"
683
+ * - "replace button"
684
+ * - "add modal"
685
+ * - "cleanup imports"
686
+ * - "fix broken imports"
687
+ *
688
+ * Usually executed AFTER:
689
+ *
690
+ * - component mutations
691
+ * - file movements
692
+ * - JSX updates
693
+ *
694
+ * ------------------------------------------------------------
695
+ * PARAMS
696
+ * ------------------------------------------------------------
697
+ *
698
+ * @param {Object} params
699
+ *
700
+ * @param {string} params.componentPath
701
+ * JSX component file path.
702
+ *
703
+ * Example:
704
+ *
705
+ * "./src/components/Home/Home.jsx"
706
+ *
707
+ * @param {Array<Object>} params.operations
708
+ * Import operations.
709
+ *
710
+ * Example:
711
+ *
712
+ * [
713
+ * {
714
+ * type: "add",
715
+ * importName: "Button",
716
+ * importPath: "../ui/Button"
717
+ * }
718
+ * ]
719
+ *
720
+ * Supported operation types:
721
+ *
722
+ * - add
723
+ * - remove
724
+ * - replace
725
+ *
726
+ * ------------------------------------------------------------
727
+ * OPERATION STRUCTURE
728
+ * ------------------------------------------------------------
729
+ *
730
+ * ADD:
731
+ *
732
+ * {
733
+ * type: "add",
734
+ * importName,
735
+ * importPath
736
+ * }
737
+ *
738
+ * REMOVE:
739
+ *
740
+ * {
741
+ * type: "remove",
742
+ * importName
743
+ * }
744
+ *
745
+ * REPLACE:
746
+ *
747
+ * {
748
+ * type: "replace",
749
+ * oldImportName,
750
+ * newImportName,
751
+ * newImportPath
752
+ * }
753
+ *
754
+ * ------------------------------------------------------------
755
+ * RETURNS
756
+ * ------------------------------------------------------------
757
+ *
758
+ * @returns {Object}
759
+ *
760
+ * {
761
+ * updated: boolean,
762
+ * operationsApplied: number
763
+ * }
764
+ *
765
+ */
766
+ // function updateComponentImports({
767
+ // componentPath,
768
+ // operations = [],
769
+ // }: TaskPayload<"updateComponentImports">): TaskResponse<
770
+ // TaskReturn<"updateComponentImports">
771
+ // > {
772
+ // // ----------------------------------------------------------
773
+ // // STEP 1:
774
+ // // Resolve absolute component path
775
+ // // ----------------------------------------------------------
776
+ // const absoluteComponentPath = path.resolve(componentPath);
777
+ // // ----------------------------------------------------------
778
+ // // STEP 2:
779
+ // // Validate component existence
780
+ // // ----------------------------------------------------------
781
+ // if (!fs.existsSync(absoluteComponentPath)) {
782
+ // throw new Error(`Component file does not exist: ${absoluteComponentPath}`);
783
+ // }
784
+ // // ----------------------------------------------------------
785
+ // // STEP 3:
786
+ // // Read component source code
787
+ // // ----------------------------------------------------------
788
+ // let componentCode = fs.readFileSync(absoluteComponentPath, "utf8");
789
+ // // ----------------------------------------------------------
790
+ // // STEP 4:
791
+ // // Track successful operations
792
+ // // ----------------------------------------------------------
793
+ // let operationsApplied = 0;
794
+ // // ----------------------------------------------------------
795
+ // // STEP 5:
796
+ // // Process each import operation
797
+ // // ----------------------------------------------------------
798
+ // operations.forEach((operation) => {
799
+ // // ------------------------------------------------------
800
+ // // ADD IMPORT
801
+ // // ------------------------------------------------------
802
+ // if (operation.type === "add") {
803
+ // // ----------------------------------------------------
804
+ // // Build import statement
805
+ // // ----------------------------------------------------
806
+ // const importStatement = `import ${operation.importName} from "${operation.importPath}";`;
807
+ // // ----------------------------------------------------
808
+ // // Prevent duplicate imports
809
+ // // ----------------------------------------------------
810
+ // if (componentCode.includes(importStatement)) {
811
+ // return;
812
+ // }
813
+ // // ----------------------------------------------------
814
+ // // Insert import at top of file
815
+ // // ----------------------------------------------------
816
+ // componentCode = importStatement + "\n" + componentCode;
817
+ // operationsApplied++;
818
+ // }
819
+ // // ------------------------------------------------------
820
+ // // REMOVE IMPORT
821
+ // // ------------------------------------------------------
822
+ // else if (operation.type === "remove") {
823
+ // // ----------------------------------------------------
824
+ // // Build regex for import removal
825
+ // // ----------------------------------------------------
826
+ // const importRegex = new RegExp(
827
+ // `import\\s+${operation.importName}\\s+from\\s+["'][^"']+["'];?\\n?`,
828
+ // "g"
829
+ // );
830
+ // // ----------------------------------------------------
831
+ // // Detect whether import exists
832
+ // // ----------------------------------------------------
833
+ // const hasImport = importRegex.test(componentCode);
834
+ // // ----------------------------------------------------
835
+ // // Remove import
836
+ // // ----------------------------------------------------
837
+ // componentCode = componentCode.replace(importRegex, "");
838
+ // // ----------------------------------------------------
839
+ // // Track successful removal
840
+ // // ----------------------------------------------------
841
+ // if (hasImport) {
842
+ // operationsApplied++;
843
+ // }
844
+ // }
845
+ // // ------------------------------------------------------
846
+ // // REPLACE IMPORT
847
+ // // ------------------------------------------------------
848
+ // else if (operation.type === "replace") {
849
+ // // ----------------------------------------------------
850
+ // // Build regex for old import
851
+ // // ----------------------------------------------------
852
+ // const oldImportRegex = new RegExp(
853
+ // `import\\s+${operation.oldImportName}\\s+from\\s+["'][^"']+["'];?`,
854
+ // "g"
855
+ // );
856
+ // // ----------------------------------------------------
857
+ // // Build new import statement
858
+ // // ----------------------------------------------------
859
+ // const newImportStatement = `import ${operation.newImportName} from "${operation.newImportPath}";`;
860
+ // // ----------------------------------------------------
861
+ // // Detect old import existence
862
+ // // ----------------------------------------------------
863
+ // const hasImport = oldImportRegex.test(componentCode);
864
+ // // ----------------------------------------------------
865
+ // // Replace old import
866
+ // // ----------------------------------------------------
867
+ // componentCode = componentCode.replace(oldImportRegex, newImportStatement);
868
+ // // ----------------------------------------------------
869
+ // // Track successful replacement
870
+ // // ----------------------------------------------------
871
+ // if (hasImport) {
872
+ // operationsApplied++;
873
+ // }
874
+ // }
875
+ // });
876
+ // // ----------------------------------------------------------
877
+ // // STEP 6:
878
+ // // Cleanup excessive blank lines
879
+ // // ----------------------------------------------------------
880
+ // componentCode = componentCode.replace(/\n{3,}/g, "\n\n");
881
+ // // ----------------------------------------------------------
882
+ // // STEP 7:
883
+ // // Write updated component source
884
+ // // ----------------------------------------------------------
885
+ // fs.writeFileSync(absoluteComponentPath, componentCode.trim(), "utf8");
886
+ // // ----------------------------------------------------------
887
+ // // STEP 8:
888
+ // // Return operation summary
889
+ // // ----------------------------------------------------------
890
+ // return {
891
+ // success: operationsApplied > 0,
892
+ // operationsApplied,
893
+ // };
894
+ // }
895
+ /**
896
+ * Infers the most appropriate component name
897
+ * from various inputs.
898
+ *
899
+ * ------------------------------------------------------------
900
+ * WHAT THIS FUNCTION DOES
901
+ * ------------------------------------------------------------
902
+ *
903
+ * This function intelligently derives
904
+ * a React component name from:
905
+ *
906
+ * - user commands
907
+ * - file paths
908
+ * - JSX content
909
+ * - DOM labels
910
+ * - filenames
911
+ * - directory names
912
+ *
913
+ * Example:
914
+ *
915
+ * INPUT:
916
+ *
917
+ * "make navbar red"
918
+ *
919
+ * OUTPUT:
920
+ *
921
+ * "Navbar"
922
+ *
923
+ * ------------------------------------------------------------
924
+ * WHY THIS FUNCTION EXISTS
925
+ * ------------------------------------------------------------
926
+ *
927
+ * Users rarely speak using:
928
+ *
929
+ * exact filesystem names.
930
+ *
931
+ * Example:
932
+ *
933
+ * User says:
934
+ *
935
+ * "update hero section"
936
+ *
937
+ * but filesystem may contain:
938
+ *
939
+ * HeroSection/
940
+ *
941
+ * AI systems require:
942
+ *
943
+ * normalized deterministic names.
944
+ *
945
+ * This function acts as:
946
+ *
947
+ * semantic naming bridge.
948
+ *
949
+ * ------------------------------------------------------------
950
+ * IMPORTANT ARCHITECTURAL DECISION
951
+ * ------------------------------------------------------------
952
+ *
953
+ * Looma should NEVER depend entirely on:
954
+ *
955
+ * exact naming matches.
956
+ *
957
+ * Natural language interaction requires:
958
+ *
959
+ * fuzzy semantic inference.
960
+ *
961
+ * because users naturally say:
962
+ *
963
+ * - navbar
964
+ * - nav bar
965
+ * - navigation
966
+ * - top header
967
+ *
968
+ * while project may contain:
969
+ *
970
+ * NavigationBar
971
+ *
972
+ * ------------------------------------------------------------
973
+ * IMPORTANT NOTE
974
+ * ------------------------------------------------------------
975
+ *
976
+ * Current implementation uses:
977
+ *
978
+ * heuristic inference.
979
+ *
980
+ * Production version should eventually use:
981
+ *
982
+ * - component registry
983
+ * - semantic embeddings
984
+ * - AST metadata
985
+ * - runtime DOM context
986
+ *
987
+ * for better accuracy.
988
+ *
989
+ * ------------------------------------------------------------
990
+ * DEPENDENCIES
991
+ * ------------------------------------------------------------
992
+ *
993
+ * This function complements:
994
+ *
995
+ * - findComponentDirectory()
996
+ * - createComponent()
997
+ * - renameComponent()
998
+ * - normalizeComponent()
999
+ * - extractComponent()
1000
+ *
1001
+ * ------------------------------------------------------------
1002
+ * WHERE THIS FUNCTION IS USEFUL
1003
+ * ------------------------------------------------------------
1004
+ *
1005
+ * Useful in commands like:
1006
+ *
1007
+ * - "make navbar sticky"
1008
+ * - "update hero"
1009
+ * - "extract footer"
1010
+ * - "move login form"
1011
+ * - "delete sidebar"
1012
+ * - "rename top section"
1013
+ *
1014
+ * Usually executed BEFORE:
1015
+ *
1016
+ * - component lookup
1017
+ * - component mutation
1018
+ * - DOM synchronization
1019
+ *
1020
+ * ------------------------------------------------------------
1021
+ * PARAMS
1022
+ * ------------------------------------------------------------
1023
+ *
1024
+ * @param {Object} params
1025
+ *
1026
+ * @param {string} [params.userInput]
1027
+ * Natural language user command.
1028
+ *
1029
+ * Example:
1030
+ *
1031
+ * "update navbar styles"
1032
+ *
1033
+ * @param {string} [params.filePath]
1034
+ * File or directory path.
1035
+ *
1036
+ * Example:
1037
+ *
1038
+ * "./src/components/Navbar/Navbar.jsx"
1039
+ *
1040
+ * @param {string} [params.jsxCode]
1041
+ * JSX source code.
1042
+ *
1043
+ * Example:
1044
+ *
1045
+ * function Navbar() {}
1046
+ *
1047
+ * @param {string[]} [params.stopWords]
1048
+ * Words to ignore during inference.
1049
+ *
1050
+ * ------------------------------------------------------------
1051
+ * RETURNS
1052
+ * ------------------------------------------------------------
1053
+ *
1054
+ * @returns {Object}
1055
+ *
1056
+ * {
1057
+ * componentName,
1058
+ * inferredFrom
1059
+ * }
1060
+ *
1061
+ */
1062
+ // function inferComponentName({
1063
+ // userInput = "",
1064
+ // filePath = "",
1065
+ // jsxCode = "",
1066
+ // stopWords = [
1067
+ // "make",
1068
+ // "update",
1069
+ // "delete",
1070
+ // "move",
1071
+ // "rename",
1072
+ // "extract",
1073
+ // "component",
1074
+ // "section",
1075
+ // "add",
1076
+ // "remove",
1077
+ // "create",
1078
+ // "change",
1079
+ // "modify",
1080
+ // "the",
1081
+ // "a",
1082
+ // "an",
1083
+ // ],
1084
+ // }: TaskPayload<"inferComponentName">): TaskResponse<
1085
+ // TaskReturn<"inferComponentName">
1086
+ // > {
1087
+ // // ----------------------------------------------------------
1088
+ // // STEP 1:
1089
+ // // Helper function to convert strings
1090
+ // // into PascalCase component names
1091
+ // //
1092
+ // // Example:
1093
+ // //
1094
+ // // "hero section"
1095
+ // // →
1096
+ // // "HeroSection"
1097
+ // // ----------------------------------------------------------
1098
+ // function toPascalCase(value) {
1099
+ // return value
1100
+ // .split(/[\s-_]+/)
1101
+ // .filter(Boolean)
1102
+ // .map((word) => {
1103
+ // return word.charAt(0).toUpperCase() + word.slice(1);
1104
+ // })
1105
+ // .join("");
1106
+ // }
1107
+ // // ----------------------------------------------------------
1108
+ // // STEP 2:
1109
+ // // Try inferring from JSX code
1110
+ // //
1111
+ // // Highest confidence source.
1112
+ // // ----------------------------------------------------------
1113
+ // if (jsxCode) {
1114
+ // // --------------------------------------------------------
1115
+ // // Match:
1116
+ // //
1117
+ // // function Header()
1118
+ // // --------------------------------------------------------
1119
+ // const functionMatch = jsxCode.match(/function\s+([A-Z][A-Za-z0-9_]*)\s*\(/);
1120
+ // // --------------------------------------------------------
1121
+ // // Return detected component name
1122
+ // // --------------------------------------------------------
1123
+ // if (functionMatch) {
1124
+ // return {
1125
+ // componentName: functionMatch[1],
1126
+ // inferredFrom: "jsx-function",
1127
+ // };
1128
+ // }
1129
+ // // --------------------------------------------------------
1130
+ // // Match:
1131
+ // //
1132
+ // // const Header = () =>
1133
+ // // --------------------------------------------------------
1134
+ // const arrowMatch = jsxCode.match(/const\s+([A-Z][A-Za-z0-9_]*)\s*=\s*\(/);
1135
+ // // --------------------------------------------------------
1136
+ // // Return detected arrow component
1137
+ // // --------------------------------------------------------
1138
+ // if (arrowMatch) {
1139
+ // return {
1140
+ // success: true,
1141
+ // componentName: arrowMatch[1],
1142
+ // inferredFrom: "jsx-arrow-function",
1143
+ // };
1144
+ // }
1145
+ // }
1146
+ // // ----------------------------------------------------------
1147
+ // // STEP 3:
1148
+ // // Try inferring from file path
1149
+ // // ----------------------------------------------------------
1150
+ // if (filePath) {
1151
+ // // --------------------------------------------------------
1152
+ // // Extract filename/folder name
1153
+ // // --------------------------------------------------------
1154
+ // const parsedPath = path.parse(filePath);
1155
+ // // --------------------------------------------------------
1156
+ // // Prefer directory name if available
1157
+ // // --------------------------------------------------------
1158
+ // const directoryName = path.basename(path.dirname(filePath));
1159
+ // // --------------------------------------------------------
1160
+ // // Use filename if meaningful
1161
+ // // --------------------------------------------------------
1162
+ // const candidateName = parsedPath.name || directoryName;
1163
+ // // --------------------------------------------------------
1164
+ // // Normalize component name
1165
+ // // --------------------------------------------------------
1166
+ // const normalizedName = toPascalCase(candidateName);
1167
+ // // --------------------------------------------------------
1168
+ // // Return inferred name
1169
+ // // --------------------------------------------------------
1170
+ // if (normalizedName) {
1171
+ // return {
1172
+ // success: true,
1173
+ // componentName: normalizedName,
1174
+ // inferredFrom: "file-path",
1175
+ // };
1176
+ // }
1177
+ // }
1178
+ // // ----------------------------------------------------------
1179
+ // // STEP 4:
1180
+ // // Try inferring from user input
1181
+ // // ----------------------------------------------------------
1182
+ // if (userInput) {
1183
+ // // --------------------------------------------------------
1184
+ // // Normalize input
1185
+ // // --------------------------------------------------------
1186
+ // const normalizedInput = userInput
1187
+ // .toLowerCase()
1188
+ // .replace(/[^a-z0-9\s-_]/g, "");
1189
+ // // --------------------------------------------------------
1190
+ // // Split words
1191
+ // // --------------------------------------------------------
1192
+ // const words = normalizedInput.split(/\s+/);
1193
+ // // --------------------------------------------------------
1194
+ // // Remove stop words
1195
+ // // --------------------------------------------------------
1196
+ // const filteredWords = words.filter((word) => {
1197
+ // return word && !stopWords.includes(word);
1198
+ // });
1199
+ // // --------------------------------------------------------
1200
+ // // Convert to PascalCase
1201
+ // // --------------------------------------------------------
1202
+ // const inferredName = toPascalCase(filteredWords.join(" "));
1203
+ // // --------------------------------------------------------
1204
+ // // Return inferred component name
1205
+ // // --------------------------------------------------------
1206
+ // if (inferredName) {
1207
+ // return {
1208
+ // success: true,
1209
+ // componentName: inferredName,
1210
+ // inferredFrom: "user-input",
1211
+ // };
1212
+ // }
1213
+ // }
1214
+ // // ----------------------------------------------------------
1215
+ // // STEP 5:
1216
+ // // Fallback component name
1217
+ // // ----------------------------------------------------------
1218
+ // return {
1219
+ // success: true,
1220
+ // componentName: "UnnamedComponent",
1221
+ // inferredFrom: "fallback",
1222
+ // };
1223
+ // }
1224
+ /**
1225
+ * Generates deterministic CSS class names
1226
+ * for Looma components.
1227
+ *
1228
+ * ------------------------------------------------------------
1229
+ * WHAT THIS FUNCTION DOES
1230
+ * ------------------------------------------------------------
1231
+ *
1232
+ * This function creates predictable,
1233
+ * collision-safe css class names.
1234
+ *
1235
+ * Example:
1236
+ *
1237
+ * INPUT:
1238
+ *
1239
+ * componentName: "Header"
1240
+ * elementName: "title"
1241
+ *
1242
+ * OUTPUT:
1243
+ *
1244
+ * "header__title"
1245
+ *
1246
+ * ------------------------------------------------------------
1247
+ * WHY THIS FUNCTION EXISTS
1248
+ * ------------------------------------------------------------
1249
+ *
1250
+ * AI-generated code can quickly become messy
1251
+ * if class naming is inconsistent.
1252
+ *
1253
+ * Problems without naming strategy:
1254
+ *
1255
+ * - duplicate class names
1256
+ * - style leakage
1257
+ * - unreadable css
1258
+ * - impossible refactors
1259
+ * - component collisions
1260
+ *
1261
+ * This function enforces:
1262
+ *
1263
+ * deterministic naming architecture.
1264
+ *
1265
+ * ------------------------------------------------------------
1266
+ * ARCHITECTURE STRATEGY
1267
+ * ------------------------------------------------------------
1268
+ *
1269
+ * This implementation follows:
1270
+ *
1271
+ * BEM-inspired naming.
1272
+ *
1273
+ * Format:
1274
+ *
1275
+ * component__element--modifier
1276
+ *
1277
+ * Examples:
1278
+ *
1279
+ * header
1280
+ * header__title
1281
+ * header__button
1282
+ * header__button--active
1283
+ *
1284
+ * ------------------------------------------------------------
1285
+ * IMPORTANT DESIGN DECISION
1286
+ * ------------------------------------------------------------
1287
+ *
1288
+ * Class names are:
1289
+ *
1290
+ * - deterministic
1291
+ * - readable
1292
+ * - component scoped
1293
+ * - collision resistant
1294
+ *
1295
+ * We NEVER generate:
1296
+ *
1297
+ * random class names.
1298
+ *
1299
+ * Example of BAD naming:
1300
+ *
1301
+ * x12ab
1302
+ * card991
1303
+ *
1304
+ * ------------------------------------------------------------
1305
+ * WHERE THIS FUNCTION IS USEFUL
1306
+ * ------------------------------------------------------------
1307
+ *
1308
+ * Useful in commands like:
1309
+ *
1310
+ * - "create card component"
1311
+ * - "add button"
1312
+ * - "generate styles"
1313
+ * - "extract section"
1314
+ * - "create navbar"
1315
+ * - "rename styles"
1316
+ * - "normalize css"
1317
+ *
1318
+ * This function is usually called before:
1319
+ *
1320
+ * - insertStyles()
1321
+ * - updateStyles()
1322
+ * - renameCssClass()
1323
+ * - createComponent()
1324
+ *
1325
+ * ------------------------------------------------------------
1326
+ * PARAMS
1327
+ * ------------------------------------------------------------
1328
+ *
1329
+ * @param {Object} params
1330
+ *
1331
+ * @param {string} params.componentName
1332
+ * Component name.
1333
+ *
1334
+ * Example:
1335
+ *
1336
+ * "Header"
1337
+ *
1338
+ * @param {string} [params.elementName]
1339
+ * Optional child element name.
1340
+ *
1341
+ * Example:
1342
+ *
1343
+ * "title"
1344
+ *
1345
+ * @param {string} [params.modifier]
1346
+ * Optional modifier name.
1347
+ *
1348
+ * Example:
1349
+ *
1350
+ * "active"
1351
+ *
1352
+ * @param {boolean} [params.useKebabCase=true]
1353
+ * Whether generated names should use kebab-case.
1354
+ *
1355
+ * ------------------------------------------------------------
1356
+ * RETURNS
1357
+ * ------------------------------------------------------------
1358
+ *
1359
+ * @returns {string}
1360
+ *
1361
+ * Example outputs:
1362
+ *
1363
+ * "header"
1364
+ * "header__title"
1365
+ * "header__button--active"
1366
+ *
1367
+ */
1368
+ // function generateClassNames({
1369
+ // componentName,
1370
+ // elementName,
1371
+ // modifier,
1372
+ // useKebabCase = true,
1373
+ // }) {
1374
+ // // ----------------------------------------------------------
1375
+ // // STEP 1:
1376
+ // // Helper function to normalize names
1377
+ // //
1378
+ // // Converts:
1379
+ // //
1380
+ // // HeaderTitle
1381
+ // // →
1382
+ // // header-title
1383
+ // // ----------------------------------------------------------
1384
+ // function normalize(value) {
1385
+ // // --------------------------------------------------------
1386
+ // // Return empty string for invalid values
1387
+ // // --------------------------------------------------------
1388
+ // if (!value) {
1389
+ // return "";
1390
+ // }
1391
+ // // --------------------------------------------------------
1392
+ // // Convert camelCase/PascalCase to kebab-case
1393
+ // // --------------------------------------------------------
1394
+ // let normalized = value.replace(/([a-z])([A-Z])/g, "$1-$2");
1395
+ // // --------------------------------------------------------
1396
+ // // Replace spaces/underscores
1397
+ // // --------------------------------------------------------
1398
+ // normalized = normalized.replace(/[\s_]+/g, "-");
1399
+ // // --------------------------------------------------------
1400
+ // // Lowercase final result
1401
+ // // --------------------------------------------------------
1402
+ // normalized = normalized.toLowerCase();
1403
+ // // --------------------------------------------------------
1404
+ // // Return normalized value
1405
+ // // --------------------------------------------------------
1406
+ // return normalized;
1407
+ // }
1408
+ // // ----------------------------------------------------------
1409
+ // // STEP 2:
1410
+ // // Normalize component name
1411
+ // // ----------------------------------------------------------
1412
+ // const normalizedComponentName = useKebabCase
1413
+ // ? normalize(componentName)
1414
+ // : componentName;
1415
+ // // ----------------------------------------------------------
1416
+ // // STEP 3:
1417
+ // // Start class name with component scope
1418
+ // //
1419
+ // // Example:
1420
+ // //
1421
+ // // header
1422
+ // // ----------------------------------------------------------
1423
+ // let className = normalizedComponentName;
1424
+ // // ----------------------------------------------------------
1425
+ // // STEP 4:
1426
+ // // Add element segment
1427
+ // //
1428
+ // // Example:
1429
+ // //
1430
+ // // header__title
1431
+ // // ----------------------------------------------------------
1432
+ // if (elementName) {
1433
+ // const normalizedElementName = useKebabCase
1434
+ // ? normalize(elementName)
1435
+ // : elementName;
1436
+ // className += `__${normalizedElementName}`;
1437
+ // }
1438
+ // // ----------------------------------------------------------
1439
+ // // STEP 5:
1440
+ // // Add modifier segment
1441
+ // //
1442
+ // // Example:
1443
+ // //
1444
+ // // header__title--active
1445
+ // // ----------------------------------------------------------
1446
+ // if (modifier) {
1447
+ // const normalizedModifier = useKebabCase ? normalize(modifier) : modifier;
1448
+ // className += `--${normalizedModifier}`;
1449
+ // }
1450
+ // // ----------------------------------------------------------
1451
+ // // STEP 6:
1452
+ // // Return generated class name
1453
+ // // ----------------------------------------------------------
1454
+ // return { className };
1455
+ // }
1456
+ /**
1457
+ * Synchronizes JSX class usage
1458
+ * with component CSS styles.
1459
+ *
1460
+ * ------------------------------------------------------------
1461
+ * WHAT THIS FUNCTION DOES
1462
+ * ------------------------------------------------------------
1463
+ *
1464
+ * This function ensures that:
1465
+ *
1466
+ * - JSX classNames
1467
+ * - CSS selectors
1468
+ *
1469
+ * stay synchronized.
1470
+ *
1471
+ * It performs:
1472
+ *
1473
+ * 1. Detect class names used in JSX
1474
+ * 2. Detect class names defined in CSS
1475
+ * 3. Find missing CSS classes
1476
+ * 4. Auto-generate missing CSS blocks
1477
+ * 5. Find orphan CSS classes
1478
+ *
1479
+ * ------------------------------------------------------------
1480
+ * WHY THIS FUNCTION EXISTS
1481
+ * ------------------------------------------------------------
1482
+ *
1483
+ * AI-generated UI code can easily create:
1484
+ *
1485
+ * - missing styles
1486
+ * - orphan selectors
1487
+ * - inconsistent naming
1488
+ * - broken class references
1489
+ *
1490
+ * Example:
1491
+ *
1492
+ * JSX:
1493
+ *
1494
+ * <button className="header__button" />
1495
+ *
1496
+ * CSS:
1497
+ *
1498
+ * // missing .header__button
1499
+ *
1500
+ * This causes:
1501
+ *
1502
+ * - unstyled UI
1503
+ * - inconsistent rendering
1504
+ * - architecture drift
1505
+ *
1506
+ * This function prevents that.
1507
+ *
1508
+ * ------------------------------------------------------------
1509
+ * IMPORTANT DESIGN DECISION
1510
+ * ------------------------------------------------------------
1511
+ *
1512
+ * This function focuses ONLY on:
1513
+ *
1514
+ * synchronization.
1515
+ *
1516
+ * It does NOT:
1517
+ *
1518
+ * - generate advanced styles
1519
+ * - optimize css
1520
+ * - merge selectors
1521
+ * - infer design systems
1522
+ *
1523
+ * It only ensures:
1524
+ *
1525
+ * JSX ↔ CSS consistency.
1526
+ *
1527
+ * ------------------------------------------------------------
1528
+ * DEPENDENCIES
1529
+ * ------------------------------------------------------------
1530
+ *
1531
+ * This function assumes:
1532
+ *
1533
+ * - ensureStyleFile()
1534
+ * - insertStyles()
1535
+ * - updateStyles()
1536
+ * - removeStyles()
1537
+ * - renameCssClass()
1538
+ * - generateClassNames()
1539
+ *
1540
+ * already exist.
1541
+ *
1542
+ * ------------------------------------------------------------
1543
+ * WHERE THIS FUNCTION IS USEFUL
1544
+ * ------------------------------------------------------------
1545
+ *
1546
+ * Useful in commands like:
1547
+ *
1548
+ * - "create navbar"
1549
+ * - "generate styles"
1550
+ * - "extract component"
1551
+ * - "cleanup component"
1552
+ * - "normalize css"
1553
+ * - "sync styles"
1554
+ * - "fix missing styles"
1555
+ *
1556
+ * Usually executed:
1557
+ *
1558
+ * AFTER JSX mutations.
1559
+ *
1560
+ * ------------------------------------------------------------
1561
+ * IMPORTANT LIMITATION
1562
+ * ------------------------------------------------------------
1563
+ *
1564
+ * Current implementation supports:
1565
+ *
1566
+ * - className="..."
1567
+ * - class="..."
1568
+ *
1569
+ * It does NOT fully support:
1570
+ *
1571
+ * - clsx()
1572
+ * - classnames()
1573
+ * - template literals
1574
+ * - dynamic class generation
1575
+ *
1576
+ * Those require AST-level JSX analysis.
1577
+ *
1578
+ * ------------------------------------------------------------
1579
+ * PARAMS
1580
+ * ------------------------------------------------------------
1581
+ *
1582
+ * @param {Object} params
1583
+ *
1584
+ * @param {string} params.componentPath
1585
+ * JSX component file path.
1586
+ *
1587
+ * @param {string} params.cssPath
1588
+ * CSS file path.
1589
+ *
1590
+ * @param {boolean} [params.removeOrphanStyles=false]
1591
+ * Whether unused css selectors should be removed.
1592
+ *
1593
+ * ------------------------------------------------------------
1594
+ * RETURNS
1595
+ * ------------------------------------------------------------
1596
+ *
1597
+ * @returns {Object}
1598
+ *
1599
+ * {
1600
+ * missingClasses: string[], // when className is present in JSX but missing in CSS
1601
+ * orphanClasses: string[], // when class is defined in CSS but not used in JSX
1602
+ * insertedClasses: string[], // when this function auto-generates missing CSS blocks
1603
+ * removedClasses: string[] // when this function removes orphan CSS blocks (if enabled)
1604
+ * }
1605
+ *
1606
+ */
1607
+ // function syncComponentStyles({
1608
+ // componentPath,
1609
+ // cssPath,
1610
+ // removeOrphanStyles = false,
1611
+ // }) {
1612
+ // // ----------------------------------------------------------
1613
+ // // STEP 1:
1614
+ // // Validate component file existence
1615
+ // // ----------------------------------------------------------
1616
+ // if (!fs.existsSync(componentPath)) {
1617
+ // throw new Error(`Component file does not exist: ${componentPath}`);
1618
+ // }
1619
+ // // ----------------------------------------------------------
1620
+ // // STEP 2:
1621
+ // // Validate css file existence
1622
+ // // ----------------------------------------------------------
1623
+ // if (!fs.existsSync(cssPath)) {
1624
+ // throw new Error(`CSS file does not exist: ${cssPath}`);
1625
+ // }
1626
+ // // ----------------------------------------------------------
1627
+ // // STEP 3:
1628
+ // // Read JSX/component source code
1629
+ // // ----------------------------------------------------------
1630
+ // const componentCode = fs.readFileSync(componentPath, "utf8");
1631
+ // // ----------------------------------------------------------
1632
+ // // STEP 4:
1633
+ // // Read CSS source code
1634
+ // // ----------------------------------------------------------
1635
+ // const cssCode = fs.readFileSync(cssPath, "utf8");
1636
+ // // ----------------------------------------------------------
1637
+ // // STEP 5:
1638
+ // // Extract class names from JSX
1639
+ // //
1640
+ // // Handles:
1641
+ // //
1642
+ // // className="..."
1643
+ // // class="..."
1644
+ // // ----------------------------------------------------------
1645
+ // const jsxClassRegex = /class(Name)?=["'`]([^"'`]+)["'`]/g;
1646
+ // // ----------------------------------------------------------
1647
+ // // Store unique JSX classes
1648
+ // // ----------------------------------------------------------
1649
+ // const jsxClasses = new Set();
1650
+ // // ----------------------------------------------------------
1651
+ // // Iterate JSX matches
1652
+ // // ----------------------------------------------------------
1653
+ // let jsxMatch;
1654
+ // while ((jsxMatch = jsxClassRegex.exec(componentCode)) !== null) {
1655
+ // // --------------------------------------------------------
1656
+ // // Extract full class string
1657
+ // //
1658
+ // // Example:
1659
+ // //
1660
+ // // "header button active"
1661
+ // // --------------------------------------------------------
1662
+ // const classString = jsxMatch[2];
1663
+ // // --------------------------------------------------------
1664
+ // // Split multiple classes
1665
+ // // --------------------------------------------------------
1666
+ // classString.split(/\s+/).forEach((className) => {
1667
+ // // ----------------------------------------------------
1668
+ // // Ignore empty values
1669
+ // // ----------------------------------------------------
1670
+ // if (!className.trim()) {
1671
+ // return;
1672
+ // }
1673
+ // jsxClasses.add(className.trim());
1674
+ // });
1675
+ // }
1676
+ // // ----------------------------------------------------------
1677
+ // // STEP 6:
1678
+ // // Extract css selectors
1679
+ // //
1680
+ // // Example:
1681
+ // //
1682
+ // // .header__button
1683
+ // // ----------------------------------------------------------
1684
+ // const cssSelectorRegex = /\.([a-zA-Z0-9_-]+)/g;
1685
+ // // ----------------------------------------------------------
1686
+ // // Store unique CSS classes
1687
+ // // ----------------------------------------------------------
1688
+ // const cssClasses = new Set();
1689
+ // let cssMatch;
1690
+ // while ((cssMatch = cssSelectorRegex.exec(cssCode)) !== null) {
1691
+ // cssClasses.add(cssMatch[1]);
1692
+ // }
1693
+ // // ----------------------------------------------------------
1694
+ // // STEP 7:
1695
+ // // Detect missing classes
1696
+ // //
1697
+ // // Present in JSX
1698
+ // // Missing in CSS
1699
+ // // ----------------------------------------------------------
1700
+ // const missingClasses = [];
1701
+ // jsxClasses.forEach((className) => {
1702
+ // if (!cssClasses.has(className)) {
1703
+ // missingClasses.push(className);
1704
+ // }
1705
+ // });
1706
+ // // ----------------------------------------------------------
1707
+ // // STEP 8:
1708
+ // // Detect orphan classes
1709
+ // //
1710
+ // // Present in CSS
1711
+ // // Missing in JSX
1712
+ // // ----------------------------------------------------------
1713
+ // const orphanClasses = [];
1714
+ // cssClasses.forEach((className) => {
1715
+ // if (!jsxClasses.has(className)) {
1716
+ // orphanClasses.push(className);
1717
+ // }
1718
+ // });
1719
+ // // ----------------------------------------------------------
1720
+ // // STEP 9:
1721
+ // // Insert missing css blocks
1722
+ // // ----------------------------------------------------------
1723
+ // const insertedClasses = [];
1724
+ // if (missingClasses.length > 0) {
1725
+ // // --------------------------------------------------------
1726
+ // // Generate css blocks
1727
+ // // --------------------------------------------------------
1728
+ // const generatedStyles = missingClasses
1729
+ // .map(
1730
+ // (className) => `
1731
+ // .${className} {
1732
+ // }
1733
+ // `
1734
+ // )
1735
+ // .join("\n");
1736
+ // // --------------------------------------------------------
1737
+ // // Append generated styles
1738
+ // // --------------------------------------------------------
1739
+ // fs.appendFileSync(cssPath, `\n\n${generatedStyles}`, "utf8");
1740
+ // insertedClasses.push(...missingClasses);
1741
+ // }
1742
+ // // ----------------------------------------------------------
1743
+ // // STEP 10:
1744
+ // // Remove orphan styles if enabled
1745
+ // // ----------------------------------------------------------
1746
+ // const removedClasses = [];
1747
+ // if (removeOrphanStyles && orphanClasses.length > 0) {
1748
+ // let updatedCssCode = cssCode;
1749
+ // orphanClasses.forEach((className) => {
1750
+ // // ----------------------------------------------------
1751
+ // // Match entire css block
1752
+ // // ----------------------------------------------------
1753
+ // const blockRegex = new RegExp(`\\.${className}\\s*\\{[\\s\\S]*?\\}`, "g");
1754
+ // // ----------------------------------------------------
1755
+ // // Remove css block
1756
+ // // ----------------------------------------------------
1757
+ // updatedCssCode = updatedCssCode.replace(blockRegex, "");
1758
+ // removedClasses.push(className);
1759
+ // });
1760
+ // // --------------------------------------------------------
1761
+ // // Cleanup excessive spacing
1762
+ // // --------------------------------------------------------
1763
+ // updatedCssCode = updatedCssCode.replace(/\n{3,}/g, "\n\n");
1764
+ // // --------------------------------------------------------
1765
+ // // Write cleaned css
1766
+ // // --------------------------------------------------------
1767
+ // fs.writeFileSync(cssPath, updatedCssCode.trim(), "utf8");
1768
+ // }
1769
+ // // ----------------------------------------------------------
1770
+ // // STEP 11:
1771
+ // // Return synchronization summary
1772
+ // // ----------------------------------------------------------
1773
+ // return {
1774
+ // missingClasses,
1775
+ // orphanClasses,
1776
+ // insertedClasses,
1777
+ // removedClasses,
1778
+ // };
1779
+ // }
1780
+ /**
1781
+ * Resolves CSS class naming conflicts
1782
+ * inside component CSS and JSX files.
1783
+ *
1784
+ * ------------------------------------------------------------
1785
+ * WHAT THIS FUNCTION DOES
1786
+ * ------------------------------------------------------------
1787
+ *
1788
+ * This function detects duplicate or conflicting
1789
+ * css class names and renames them safely.
1790
+ *
1791
+ * It synchronizes changes across:
1792
+ *
1793
+ * - CSS file
1794
+ * - JSX component file
1795
+ *
1796
+ * Example:
1797
+ *
1798
+ * BEFORE:
1799
+ *
1800
+ * Header.css
1801
+ *
1802
+ * .button {
1803
+ * color: red;
1804
+ * }
1805
+ *
1806
+ * Card.css
1807
+ *
1808
+ * .button {
1809
+ * color: blue;
1810
+ * }
1811
+ *
1812
+ * AFTER:
1813
+ *
1814
+ * Header.css
1815
+ *
1816
+ * .header__button {
1817
+ * color: red;
1818
+ * }
1819
+ *
1820
+ * JSX:
1821
+ *
1822
+ * className="header__button"
1823
+ *
1824
+ * ------------------------------------------------------------
1825
+ * WHY THIS FUNCTION EXISTS
1826
+ * ------------------------------------------------------------
1827
+ *
1828
+ * AI-generated code frequently creates:
1829
+ *
1830
+ * - generic class names
1831
+ * - duplicated selectors
1832
+ * - style leakage
1833
+ * - component collisions
1834
+ *
1835
+ * Example bad class names:
1836
+ *
1837
+ * .container
1838
+ * .wrapper
1839
+ * .button
1840
+ * .title
1841
+ *
1842
+ * At scale these become:
1843
+ *
1844
+ * architecture disasters.
1845
+ *
1846
+ * This function enforces:
1847
+ *
1848
+ * component-scoped CSS naming.
1849
+ *
1850
+ * ------------------------------------------------------------
1851
+ * IMPORTANT ARCHITECTURAL DECISION
1852
+ * ------------------------------------------------------------
1853
+ *
1854
+ * Looma should NEVER trust
1855
+ * raw AI-generated class names.
1856
+ *
1857
+ * Every generated class should become:
1858
+ *
1859
+ * deterministic
1860
+ * scoped
1861
+ * collision-safe
1862
+ *
1863
+ * Example:
1864
+ *
1865
+ * card__button
1866
+ * header__title
1867
+ * modal__footer
1868
+ *
1869
+ * ------------------------------------------------------------
1870
+ * DEPENDENCIES
1871
+ * ------------------------------------------------------------
1872
+ *
1873
+ * This function assumes:
1874
+ *
1875
+ * - parseCSS()
1876
+ * - findCssSelector()
1877
+ * - renameCssClass()
1878
+ * - generateClassNames()
1879
+ * - updateStyles()
1880
+ *
1881
+ * already exist.
1882
+ *
1883
+ * ------------------------------------------------------------
1884
+ * WHERE THIS FUNCTION IS USEFUL
1885
+ * ------------------------------------------------------------
1886
+ *
1887
+ * Useful in commands like:
1888
+ *
1889
+ * - "create component"
1890
+ * - "extract component"
1891
+ * - "cleanup css"
1892
+ * - "normalize styles"
1893
+ * - "fix css conflicts"
1894
+ * - "merge components"
1895
+ * - "generate UI"
1896
+ *
1897
+ * Usually executed AFTER:
1898
+ *
1899
+ * - component generation
1900
+ * - JSX insertion
1901
+ * - style generation
1902
+ *
1903
+ * ------------------------------------------------------------
1904
+ * IMPORTANT NOTE
1905
+ * ------------------------------------------------------------
1906
+ *
1907
+ * Current implementation supports:
1908
+ *
1909
+ * - static class names
1910
+ * - basic selectors
1911
+ *
1912
+ * It does NOT fully support:
1913
+ *
1914
+ * - clsx()
1915
+ * - CSS modules
1916
+ * - Tailwind
1917
+ * - styled-components
1918
+ * - dynamic class generation
1919
+ *
1920
+ * Those require AST-level JSX analysis.
1921
+ *
1922
+ * ------------------------------------------------------------
1923
+ * REQUIRED PACKAGE
1924
+ * ------------------------------------------------------------
1925
+ *
1926
+ * npm install postcss
1927
+ *
1928
+ * ------------------------------------------------------------
1929
+ * PARAMS
1930
+ * ------------------------------------------------------------
1931
+ *
1932
+ * @param {Object} params
1933
+ *
1934
+ * @param {string} params.componentName
1935
+ * Component name.
1936
+ *
1937
+ * Example:
1938
+ *
1939
+ * "Header"
1940
+ *
1941
+ * @param {string} params.cssPath
1942
+ * CSS file path.
1943
+ *
1944
+ * @param {string} params.componentPath
1945
+ * JSX component file path.
1946
+ *
1947
+ * @param {string[]} [params.genericClassNames]
1948
+ * Generic class names considered unsafe.
1949
+ *
1950
+ * Example:
1951
+ *
1952
+ * [
1953
+ * "button",
1954
+ * "container",
1955
+ * "wrapper"
1956
+ * ]
1957
+ *
1958
+ * ------------------------------------------------------------
1959
+ * RETURNS
1960
+ * ------------------------------------------------------------
1961
+ *
1962
+ * @returns {Object}
1963
+ *
1964
+ * {
1965
+ * renamedClasses: [
1966
+ * {
1967
+ * oldName,
1968
+ * newName
1969
+ * }
1970
+ * ]
1971
+ * }
1972
+ *
1973
+ */
1974
+ // function resolveCssClassConflicts({
1975
+ // componentName,
1976
+ // cssPath,
1977
+ // componentPath,
1978
+ // genericClassNames = [
1979
+ // "container",
1980
+ // "wrapper",
1981
+ // "button",
1982
+ // "title",
1983
+ // "card",
1984
+ // "box",
1985
+ // "text",
1986
+ // "item",
1987
+ // "content",
1988
+ // ],
1989
+ // }) {
1990
+ // // ----------------------------------------------------------
1991
+ // // STEP 1:
1992
+ // // Validate CSS file existence
1993
+ // // ----------------------------------------------------------
1994
+ // if (!fs.existsSync(cssPath)) {
1995
+ // throw new Error(`CSS file does not exist: ${cssPath}`);
1996
+ // }
1997
+ // // ----------------------------------------------------------
1998
+ // // STEP 2:
1999
+ // // Validate component file existence
2000
+ // // ----------------------------------------------------------
2001
+ // if (!fs.existsSync(componentPath)) {
2002
+ // throw new Error(`Component file does not exist: ${componentPath}`);
2003
+ // }
2004
+ // // ----------------------------------------------------------
2005
+ // // STEP 3:
2006
+ // // Read CSS source code
2007
+ // // ----------------------------------------------------------
2008
+ // const cssCode = fs.readFileSync(cssPath, "utf8");
2009
+ // // ----------------------------------------------------------
2010
+ // // STEP 4:
2011
+ // // Parse CSS into AST
2012
+ // // ----------------------------------------------------------
2013
+ // const ast = postcss.parse(cssCode);
2014
+ // // ----------------------------------------------------------
2015
+ // // STEP 5:
2016
+ // // Store renamed classes
2017
+ // // ----------------------------------------------------------
2018
+ // const renamedClasses = [];
2019
+ // // ----------------------------------------------------------
2020
+ // // STEP 6:
2021
+ // // Track already processed classes
2022
+ // //
2023
+ // // Prevent duplicate renaming.
2024
+ // // ----------------------------------------------------------
2025
+ // const processedClasses = new Set();
2026
+ // // ----------------------------------------------------------
2027
+ // // STEP 7:
2028
+ // // Traverse all CSS rules
2029
+ // // ----------------------------------------------------------
2030
+ // ast.walkRules((rule) => {
2031
+ // // --------------------------------------------------------
2032
+ // // Extract selector
2033
+ // //
2034
+ // // Example:
2035
+ // //
2036
+ // // ".button"
2037
+ // // --------------------------------------------------------
2038
+ // const selector = rule.selector;
2039
+ // // --------------------------------------------------------
2040
+ // // Extract class name
2041
+ // //
2042
+ // // Example:
2043
+ // //
2044
+ // // button
2045
+ // // --------------------------------------------------------
2046
+ // const classMatch = selector.match(/\.([a-zA-Z0-9_-]+)/);
2047
+ // // --------------------------------------------------------
2048
+ // // Skip selectors without class
2049
+ // // --------------------------------------------------------
2050
+ // if (!classMatch) {
2051
+ // return;
2052
+ // }
2053
+ // // --------------------------------------------------------
2054
+ // // Extract class name
2055
+ // // --------------------------------------------------------
2056
+ // const oldClassName = classMatch[1];
2057
+ // // --------------------------------------------------------
2058
+ // // Skip already processed classes
2059
+ // // --------------------------------------------------------
2060
+ // if (processedClasses.has(oldClassName)) {
2061
+ // return;
2062
+ // }
2063
+ // // --------------------------------------------------------
2064
+ // // Mark class as processed
2065
+ // // --------------------------------------------------------
2066
+ // processedClasses.add(oldClassName);
2067
+ // // --------------------------------------------------------
2068
+ // // Check whether class is generic
2069
+ // // --------------------------------------------------------
2070
+ // const isGeneric = genericClassNames.includes(oldClassName);
2071
+ // // --------------------------------------------------------
2072
+ // // Skip safe classes
2073
+ // // --------------------------------------------------------
2074
+ // if (!isGeneric) {
2075
+ // return;
2076
+ // }
2077
+ // // --------------------------------------------------------
2078
+ // // Generate deterministic scoped class
2079
+ // //
2080
+ // // Example:
2081
+ // //
2082
+ // // button
2083
+ // // →
2084
+ // // header__button
2085
+ // // --------------------------------------------------------
2086
+ // const newClassName = `${componentName.toLowerCase()}__${oldClassName}`;
2087
+ // // --------------------------------------------------------
2088
+ // // Rename class in CSS
2089
+ // // --------------------------------------------------------
2090
+ // const updatedSelector = selector.replace(
2091
+ // `.${oldClassName}`,
2092
+ // `.${newClassName}`
2093
+ // );
2094
+ // rule.selector = updatedSelector;
2095
+ // // --------------------------------------------------------
2096
+ // // Rename class in JSX/component file
2097
+ // // --------------------------------------------------------
2098
+ // const componentCode = fs.readFileSync(componentPath, "utf8");
2099
+ // // --------------------------------------------------------
2100
+ // // Replace JSX class usage
2101
+ // // --------------------------------------------------------
2102
+ // const updatedComponentCode = componentCode.replace(
2103
+ // new RegExp(`\\b${oldClassName}\\b`, "g"),
2104
+ // newClassName
2105
+ // );
2106
+ // // --------------------------------------------------------
2107
+ // // Write updated JSX/component file
2108
+ // // --------------------------------------------------------
2109
+ // fs.writeFileSync(componentPath, updatedComponentCode, "utf8");
2110
+ // // --------------------------------------------------------
2111
+ // // Store rename result
2112
+ // // --------------------------------------------------------
2113
+ // renamedClasses.push({
2114
+ // oldName: oldClassName,
2115
+ // newName: newClassName,
2116
+ // });
2117
+ // });
2118
+ // // ----------------------------------------------------------
2119
+ // // STEP 8:
2120
+ // // Write updated CSS back to file
2121
+ // // ----------------------------------------------------------
2122
+ // fs.writeFileSync(cssPath, ast.toString(), "utf8");
2123
+ // // ----------------------------------------------------------
2124
+ // // STEP 9:
2125
+ // // Return rename summary
2126
+ // // ----------------------------------------------------------
2127
+ // return {
2128
+ // renamedClasses,
2129
+ // };
2130
+ // }
2131
+ /**
2132
+ * Resolves style dependencies used by a component.
2133
+ *
2134
+ * ------------------------------------------------------------
2135
+ * WHAT THIS FUNCTION DOES
2136
+ * ------------------------------------------------------------
2137
+ *
2138
+ * This function analyzes:
2139
+ *
2140
+ * - CSS imports
2141
+ * - font imports
2142
+ * - variable usage
2143
+ * - animation usage
2144
+ * - media query dependencies
2145
+ * - shared utility styles
2146
+ * - root CSS variables
2147
+ *
2148
+ * and ensures required style dependencies
2149
+ * are properly connected.
2150
+ *
2151
+ * Example:
2152
+ *
2153
+ * BEFORE:
2154
+ *
2155
+ * Button.css
2156
+ *
2157
+ * .button {
2158
+ * color: var(--primary-color);
2159
+ * }
2160
+ *
2161
+ * But:
2162
+ *
2163
+ * variables.css is NOT imported.
2164
+ *
2165
+ * AFTER:
2166
+ *
2167
+ * variables.css gets imported automatically.
2168
+ *
2169
+ * ------------------------------------------------------------
2170
+ * WHY THIS FUNCTION EXISTS
2171
+ * ------------------------------------------------------------
2172
+ *
2173
+ * AI-generated CSS often creates:
2174
+ *
2175
+ * - missing variables
2176
+ * - broken animations
2177
+ * - undefined fonts
2178
+ * - disconnected utility classes
2179
+ * - missing shared styles
2180
+ *
2181
+ * which causes:
2182
+ *
2183
+ * broken runtime UI.
2184
+ *
2185
+ * This function acts as:
2186
+ *
2187
+ * style dependency synchronization layer.
2188
+ *
2189
+ * ------------------------------------------------------------
2190
+ * IMPORTANT ARCHITECTURAL DECISION
2191
+ * ------------------------------------------------------------
2192
+ *
2193
+ * Looma should NEVER allow:
2194
+ *
2195
+ * orphaned style references.
2196
+ *
2197
+ * Every style dependency should be:
2198
+ *
2199
+ * - traceable
2200
+ * - deterministic
2201
+ * - auto-repairable
2202
+ *
2203
+ * because runtime editing requires:
2204
+ *
2205
+ * predictable styling architecture.
2206
+ *
2207
+ * ------------------------------------------------------------
2208
+ * IMPORTANT NOTE
2209
+ * ------------------------------------------------------------
2210
+ *
2211
+ * Current implementation supports:
2212
+ *
2213
+ * - CSS variable detection
2214
+ * - @import detection
2215
+ * - animation detection
2216
+ *
2217
+ * Production version should later support:
2218
+ *
2219
+ * - Tailwind dependency graphs
2220
+ * - CSS modules
2221
+ * - CSS-in-JS
2222
+ * - design token systems
2223
+ * - theme inheritance
2224
+ *
2225
+ * ------------------------------------------------------------
2226
+ * DEPENDENCIES
2227
+ * ------------------------------------------------------------
2228
+ *
2229
+ * This function complements:
2230
+ *
2231
+ * - parseCSS()
2232
+ * - findCssSelector()
2233
+ * - ensureStyleFile()
2234
+ * - insertStyles()
2235
+ * - updateStyles()
2236
+ * - removeStyles()
2237
+ * - renameCssClass()
2238
+ * - resolveCssClassConflicts()
2239
+ *
2240
+ * ------------------------------------------------------------
2241
+ * WHERE THIS FUNCTION IS USEFUL
2242
+ * ------------------------------------------------------------
2243
+ *
2244
+ * Useful in commands like:
2245
+ *
2246
+ * - "create component"
2247
+ * - "generate styles"
2248
+ * - "extract component"
2249
+ * - "move component"
2250
+ * - "repair styling"
2251
+ * - "fix css variables"
2252
+ * - "cleanup styles"
2253
+ *
2254
+ * Usually executed AFTER:
2255
+ *
2256
+ * - style generation
2257
+ * - css insertion
2258
+ * - component extraction
2259
+ *
2260
+ * ------------------------------------------------------------
2261
+ * REQUIRED PACKAGE
2262
+ * ------------------------------------------------------------
2263
+ *
2264
+ * npm install postcss
2265
+ *
2266
+ * ------------------------------------------------------------
2267
+ * PARAMS
2268
+ * ------------------------------------------------------------
2269
+ *
2270
+ * @param {Object} params
2271
+ *
2272
+ * @param {string} params.cssPath
2273
+ * Component css file path.
2274
+ *
2275
+ * Example:
2276
+ *
2277
+ * "./src/components/Button/Button.css"
2278
+ *
2279
+ * @param {string[]} [params.availableDependencyFiles]
2280
+ * List of available shared style files.
2281
+ *
2282
+ * Example:
2283
+ *
2284
+ * [
2285
+ * "./src/styles/variables.css",
2286
+ * "./src/styles/animations.css"
2287
+ * ]
2288
+ *
2289
+ * @param {boolean} [params.autoImport=true]
2290
+ * Whether missing dependencies should
2291
+ * be imported automatically.
2292
+ *
2293
+ * ------------------------------------------------------------
2294
+ * RETURNS
2295
+ * ------------------------------------------------------------
2296
+ *
2297
+ * @returns {Object}
2298
+ *
2299
+ * {
2300
+ * resolvedDependencies: string[],
2301
+ * missingDependencies: string[],
2302
+ * insertedImports: string[]
2303
+ * }
2304
+ *
2305
+ */
2306
+ function resolveStyleDependencies({ cssPath, availableDependencyFiles = [], autoImport = true, }) {
2307
+ // ----------------------------------------------------------
2308
+ // STEP 1:
2309
+ // Resolve absolute css path
2310
+ // ----------------------------------------------------------
2311
+ const absoluteCssPath = path.resolve(cssPath);
2312
+ // ----------------------------------------------------------
2313
+ // STEP 2:
2314
+ // Validate css file existence
2315
+ // ----------------------------------------------------------
2316
+ if (!fs.existsSync(absoluteCssPath)) {
2317
+ throw new LoomaError(ERROR_CODES.FILE_NOT_FOUND, `CSS file does not exist: ${absoluteCssPath}`);
2318
+ }
2319
+ // ----------------------------------------------------------
2320
+ // STEP 3:
2321
+ // Read css source code
2322
+ // ----------------------------------------------------------
2323
+ let cssCode = fs.readFileSync(absoluteCssPath, "utf8");
2324
+ // ----------------------------------------------------------
2325
+ // STEP 4:
2326
+ // Parse CSS into AST
2327
+ // ----------------------------------------------------------
2328
+ const ast = postcss.parse(cssCode);
2329
+ // ----------------------------------------------------------
2330
+ // STEP 5:
2331
+ // Track dependencies
2332
+ // ----------------------------------------------------------
2333
+ const resolvedDependencies = [];
2334
+ const missingDependencies = [];
2335
+ const insertedImports = [];
2336
+ // ----------------------------------------------------------
2337
+ // STEP 6:
2338
+ // Detect CSS variable usage
2339
+ //
2340
+ // Example:
2341
+ //
2342
+ // var(--primary-color)
2343
+ // ----------------------------------------------------------
2344
+ const variableMatches = cssCode.match(/var\(--([a-zA-Z0-9-_]+)\)/g) || [];
2345
+ // ----------------------------------------------------------
2346
+ // STEP 7:
2347
+ // If variables are used,
2348
+ // ensure variables.css exists
2349
+ // ----------------------------------------------------------
2350
+ if (variableMatches.length > 0) {
2351
+ // --------------------------------------------------------
2352
+ // Try finding variables.css
2353
+ // --------------------------------------------------------
2354
+ const variablesFile = availableDependencyFiles.find((file) => {
2355
+ return file.includes("variables.css");
2356
+ });
2357
+ // --------------------------------------------------------
2358
+ // If found, resolve dependency
2359
+ // --------------------------------------------------------
2360
+ if (variablesFile) {
2361
+ resolvedDependencies.push(variablesFile);
2362
+ // ------------------------------------------------------
2363
+ // Auto import variables.css
2364
+ // ------------------------------------------------------
2365
+ if (autoImport && !cssCode.includes(variablesFile)) {
2366
+ // ----------------------------------------------------
2367
+ // Build relative import path
2368
+ // ----------------------------------------------------
2369
+ const relativeImportPath = path.relative(path.dirname(absoluteCssPath), variablesFile);
2370
+ // ----------------------------------------------------
2371
+ // Normalize slashes
2372
+ // ----------------------------------------------------
2373
+ const normalizedImportPath = relativeImportPath.replace(/\\/g, "/");
2374
+ // ----------------------------------------------------
2375
+ // Build import statement
2376
+ // ----------------------------------------------------
2377
+ const importStatement = `@import "${normalizedImportPath}";\n`;
2378
+ // ----------------------------------------------------
2379
+ // Insert import at top
2380
+ // ----------------------------------------------------
2381
+ cssCode = importStatement + cssCode;
2382
+ insertedImports.push(normalizedImportPath);
2383
+ }
2384
+ }
2385
+ // --------------------------------------------------------
2386
+ // Track missing dependency
2387
+ // --------------------------------------------------------
2388
+ else {
2389
+ missingDependencies.push("variables.css");
2390
+ }
2391
+ }
2392
+ // ----------------------------------------------------------
2393
+ // STEP 8:
2394
+ // Detect animation usage
2395
+ //
2396
+ // Example:
2397
+ //
2398
+ // animation: fadeIn
2399
+ // ----------------------------------------------------------
2400
+ const animationMatches = cssCode.match(/animation\s*:\s*([a-zA-Z0-9_-]+)/g) || [];
2401
+ // ----------------------------------------------------------
2402
+ // STEP 9:
2403
+ // Resolve animations.css dependency
2404
+ // ----------------------------------------------------------
2405
+ if (animationMatches.length > 0) {
2406
+ // --------------------------------------------------------
2407
+ // Find animations.css
2408
+ // --------------------------------------------------------
2409
+ const animationsFile = availableDependencyFiles.find((file) => {
2410
+ return file.includes("animations.css");
2411
+ });
2412
+ // --------------------------------------------------------
2413
+ // If found, resolve dependency
2414
+ // --------------------------------------------------------
2415
+ if (animationsFile) {
2416
+ resolvedDependencies.push(animationsFile);
2417
+ // ------------------------------------------------------
2418
+ // Auto import if enabled
2419
+ // ------------------------------------------------------
2420
+ if (autoImport && !cssCode.includes(animationsFile)) {
2421
+ // ----------------------------------------------------
2422
+ // Build relative path
2423
+ // ----------------------------------------------------
2424
+ const relativeImportPath = path.relative(path.dirname(absoluteCssPath), animationsFile);
2425
+ // ----------------------------------------------------
2426
+ // Normalize path separators
2427
+ // ----------------------------------------------------
2428
+ const normalizedImportPath = relativeImportPath.replace(/\\/g, "/");
2429
+ // ----------------------------------------------------
2430
+ // Build import statement
2431
+ // ----------------------------------------------------
2432
+ const importStatement = `@import "${normalizedImportPath}";\n`;
2433
+ // ----------------------------------------------------
2434
+ // Insert import
2435
+ // ----------------------------------------------------
2436
+ cssCode = importStatement + cssCode;
2437
+ insertedImports.push(normalizedImportPath);
2438
+ }
2439
+ }
2440
+ // --------------------------------------------------------
2441
+ // Track missing dependency
2442
+ // --------------------------------------------------------
2443
+ else {
2444
+ missingDependencies.push("animations.css");
2445
+ }
2446
+ }
2447
+ // ----------------------------------------------------------
2448
+ // STEP 10:
2449
+ // Detect existing @imports
2450
+ // ----------------------------------------------------------
2451
+ ast.walkAtRules("import", (rule) => {
2452
+ // ------------------------------------------------------
2453
+ // Extract imported file
2454
+ // ------------------------------------------------------
2455
+ const importMatch = rule.params.match(/["'](.+?)["']/);
2456
+ // ------------------------------------------------------
2457
+ // Skip invalid imports
2458
+ // ------------------------------------------------------
2459
+ if (!importMatch) {
2460
+ return;
2461
+ }
2462
+ // ------------------------------------------------------
2463
+ // Extract import path
2464
+ // ------------------------------------------------------
2465
+ const importPath = importMatch[1];
2466
+ // ------------------------------------------------------
2467
+ // Register dependency
2468
+ // ------------------------------------------------------
2469
+ resolvedDependencies.push(importPath);
2470
+ });
2471
+ // ----------------------------------------------------------
2472
+ // STEP 11:
2473
+ // Remove duplicate dependencies
2474
+ // ----------------------------------------------------------
2475
+ const uniqueResolvedDependencies = [...new Set(resolvedDependencies)];
2476
+ const uniqueInsertedImports = [...new Set(insertedImports)];
2477
+ const uniqueMissingDependencies = [...new Set(missingDependencies)];
2478
+ // ----------------------------------------------------------
2479
+ // STEP 12:
2480
+ // Write updated css back to file
2481
+ // ----------------------------------------------------------
2482
+ fs.writeFileSync(absoluteCssPath, cssCode, "utf8");
2483
+ // ----------------------------------------------------------
2484
+ // STEP 13:
2485
+ // Return dependency resolution summary
2486
+ // ----------------------------------------------------------
2487
+ return {
2488
+ success: true,
2489
+ resolvedDependencies: uniqueResolvedDependencies,
2490
+ missingDependencies: uniqueMissingDependencies,
2491
+ insertedImports: uniqueInsertedImports,
2492
+ };
2493
+ }
2494
+ /**
2495
+ * Inserts generic code into a file.
2496
+ *
2497
+ * Useful for:
2498
+ * - inserting hooks
2499
+ * - inserting utilities
2500
+ * - inserting exports
2501
+ * - inserting config
2502
+ * - inserting arbitrary JS/TS code
2503
+ */
2504
+ // function insertCode({ filePath, codeToInsert, insertAt = "end" }) {
2505
+ // // ----------------------------------------------------------
2506
+ // // STEP 1:
2507
+ // // Ensure file exists
2508
+ // // ----------------------------------------------------------
2509
+ // if (!fs.existsSync(filePath)) {
2510
+ // throw new Error(`File does not exist: ${filePath}`);
2511
+ // }
2512
+ // // ----------------------------------------------------------
2513
+ // // STEP 2:
2514
+ // // Read existing file content
2515
+ // // ----------------------------------------------------------
2516
+ // const existingCode = fs.readFileSync(filePath, "utf8");
2517
+ // // ----------------------------------------------------------
2518
+ // // STEP 3:
2519
+ // // Decide insertion strategy
2520
+ // // ----------------------------------------------------------
2521
+ // let updatedCode = existingCode;
2522
+ // // ----------------------------------------------------------
2523
+ // // Insert at beginning
2524
+ // // ----------------------------------------------------------
2525
+ // if (insertAt === "start") {
2526
+ // updatedCode = `${codeToInsert}\n\n${existingCode}`;
2527
+ // }
2528
+ // // ----------------------------------------------------------
2529
+ // // Insert at end
2530
+ // // ----------------------------------------------------------
2531
+ // else if (insertAt === "end") {
2532
+ // updatedCode = `${existingCode}\n\n${codeToInsert}`;
2533
+ // }
2534
+ // // ----------------------------------------------------------
2535
+ // // Unsupported insertion strategy
2536
+ // // ----------------------------------------------------------
2537
+ // else {
2538
+ // throw new Error(`Unsupported insertAt value: ${insertAt}`);
2539
+ // }
2540
+ // // ----------------------------------------------------------
2541
+ // // STEP 4:
2542
+ // // Write updated code back to file
2543
+ // // ----------------------------------------------------------
2544
+ // fs.writeFileSync(filePath, updatedCode, "utf8");
2545
+ // // ----------------------------------------------------------
2546
+ // // STEP 5:
2547
+ // // Return result
2548
+ // // ----------------------------------------------------------
2549
+ // return {
2550
+ // inserted: true,
2551
+ // filePath,
2552
+ // insertAt,
2553
+ // };
2554
+ // }
2555
+ /**
2556
+ * Generates source code from Babel AST.
2557
+ */
2558
+ function generateCodeFromAST({ ast, options = {} }) {
2559
+ return generate(ast, {
2560
+ retainLines: false,
2561
+ compact: false,
2562
+ concise: false,
2563
+ comments: true,
2564
+ jsescOption: {
2565
+ minimal: true,
2566
+ },
2567
+ ...options,
2568
+ }).code;
2569
+ }
2570
+ function matchesSelector({ openingElement, selector }) {
2571
+ // ----------------------------------------------------------
2572
+ // Ignore unsupported JSX names
2573
+ // ----------------------------------------------------------
2574
+ if (!t.isJSXIdentifier(openingElement.name)) {
2575
+ return false;
2576
+ }
2577
+ const { tagName, classes, attributes } = parsers.parseSelector(selector);
2578
+ // ----------------------------------------------------------
2579
+ // Match tag
2580
+ // ----------------------------------------------------------
2581
+ if (openingElement.name.name !== tagName) {
2582
+ return false;
2583
+ }
2584
+ // ----------------------------------------------------------
2585
+ // Build attribute map
2586
+ // ----------------------------------------------------------
2587
+ const attributeMap = {};
2588
+ openingElement.attributes.forEach((attr) => {
2589
+ if (!t.isJSXAttribute(attr) || !t.isJSXIdentifier(attr.name)) {
2590
+ return;
2591
+ }
2592
+ // ------------------------------------------------------
2593
+ // String literal:
2594
+ //
2595
+ // href="#"
2596
+ // ------------------------------------------------------
2597
+ if (t.isStringLiteral(attr.value)) {
2598
+ attributeMap[attr.name.name] = attr.value.value;
2599
+ }
2600
+ // ------------------------------------------------------
2601
+ // JSX expression with string literal:
2602
+ //
2603
+ // src={"/logo.png"}
2604
+ // ------------------------------------------------------
2605
+ else if (t.isJSXExpressionContainer(attr.value) &&
2606
+ t.isStringLiteral(attr.value.expression)) {
2607
+ attributeMap[attr.name.name] = attr.value.expression.value;
2608
+ }
2609
+ });
2610
+ // ----------------------------------------------------------
2611
+ // Match class names
2612
+ // ----------------------------------------------------------
2613
+ if (classes.length > 0) {
2614
+ const classNames = (attributeMap.className || "")
2615
+ .split(/\s+/)
2616
+ .filter(Boolean);
2617
+ const hasAllClasses = classes.every((className) => classNames.includes(className));
2618
+ if (!hasAllClasses) {
2619
+ return false;
2620
+ }
2621
+ }
2622
+ // ----------------------------------------------------------
2623
+ // Match attributes
2624
+ // ----------------------------------------------------------
2625
+ for (const [key, value] of Object.entries(attributes)) {
2626
+ if (attributeMap[key] !== value) {
2627
+ return false;
2628
+ }
2629
+ }
2630
+ return true;
2631
+ }
2632
+ function analyzeComponent() { }
2633
+ // Architecture I Would Prefer
2634
+ // Parsers
2635
+ // parseAST
2636
+ // parseCSS
2637
+ // parseJSCode
2638
+ // generateCodeFromAST
2639
+ // Finders
2640
+ // findNodeByLine
2641
+ // findComponentByName
2642
+ // findJSXElement
2643
+ // findImportDeclaration
2644
+ // findCssSelector
2645
+ // findComponentDirectory
2646
+ // Ensurers
2647
+ // ensureLibrary
2648
+ // ensureStyleFile
2649
+ // ensureComponentStructure
2650
+ // normalizeComponent
2651
+ // Import Helpers
2652
+ // buildImportDeclaration
2653
+ // getImportSpecifiers
2654
+ // removeImportSpecifier
2655
+ // mergeImportDeclarations
2656
+ // isImportUsed
2657
+ // Analyzers (instead of many parseX functions)
2658
+ // analyzeComponent
2659
+ // analyzeDependencies
2660
+ // analyzeTypes
2661
+ // analyzeHooks
2662
+ // analyzeExports
2663
+ // const mutationTasks = getExportedFunctionNames("./lib/tasks/mutations.ts");
2664
+ // const astTasks = getExportedFunctionNames("./lib/tasks/ast.ts");
2665
+ /**
2666
+ * Parses a file and returns an array of exported function names.
2667
+ * @param {string} filePath - Path to the index.ts file.
2668
+ * @returns {string[]} Array of function name strings.
2669
+ */
2670
+ async function getExportedFunctionNames(type) {
2671
+ let taskModule;
2672
+ if (type === "ast") {
2673
+ taskModule = await import("../tasks/ast.js");
2674
+ }
2675
+ if (type === "mutation") {
2676
+ taskModule = await import("../tasks/mutations.js");
2677
+ }
2678
+ if (type === "generators") {
2679
+ taskModule = await import("../tasks/generators.js");
2680
+ }
2681
+ if (type === "query") {
2682
+ taskModule = await import("../tasks/query.js");
2683
+ }
2684
+ let tasksList = Object.keys(taskModule.default);
2685
+ return tasksList;
2686
+ }
2687
+ /**
2688
+ *
2689
+ * @returns
2690
+ */
2691
+ async function generateTasksDocs() {
2692
+ // let astTasksList = await getExportedFunctionNames("ast");
2693
+ // // const astModule = await import("../lib/tasks/ast.ts");
2694
+ // // let astTasksList = Object.keys(astModule.default);
2695
+ // let astTasks = [];
2696
+ let mutationTasksList = await getExportedFunctionNames("mutation");
2697
+ // const mutationsModule = await import("../lib/tasks/mutations.ts");
2698
+ // let mutationTasksList = Object.keys(mutationsModule.default);
2699
+ let mutationTasks = [];
2700
+ let generatorTasksList = await getExportedFunctionNames("generators");
2701
+ // const generatorModule = await import("../lib/tasks/generators.ts");
2702
+ // let generatorTasksList = Object.keys(generatorModule.default);
2703
+ let generatorTasks = [];
2704
+ let queryTasksList = await getExportedFunctionNames("query");
2705
+ // const queryModule = await import("../lib/tasks/query.ts");
2706
+ // let queryTasksList = Object.keys(queryModule.default);
2707
+ let queryTasks = [];
2708
+ for (let [taskName, task] of Object.entries(TaskRegistry)) {
2709
+ const payloadFields = "shape" in task.payload ? Object.keys(task.payload.shape) : [];
2710
+ const returnFields = "shape" in task.return ? Object.keys(task.return.shape) : [];
2711
+ const payloadString = payloadFields.length > 0 ? payloadFields.join(",\n ") : "";
2712
+ const returnString = returnFields.length > 0 ? returnFields.join(",\n ") : "";
2713
+ const taskDesc = `${taskName}({
2714
+ ${payloadString}
2715
+ }) returns {
2716
+ ${returnString}
2717
+ } - ${"description" in task ? task.description : "No description available"}`;
2718
+ // if (astTasksList.includes(taskName)) astTasks.push(taskDesc);
2719
+ if (mutationTasksList.includes(taskName))
2720
+ mutationTasks.push(taskDesc);
2721
+ if (generatorTasksList.includes(taskName))
2722
+ generatorTasks.push(taskDesc);
2723
+ if (queryTasksList.includes(taskName))
2724
+ queryTasks.push(taskDesc);
2725
+ }
2726
+ return {
2727
+ // astTasks: astTasks.join("\n\n"),
2728
+ mutationTasks: mutationTasks.join("\n\n"),
2729
+ generatorTasks: generatorTasks.join("\n\n"),
2730
+ queryTasks: queryTasks.join("\n\n"),
2731
+ };
2732
+ }
2733
+ // Point synckit to your worker file
2734
+ // const formatSync = createSyncFn(
2735
+ // path.join(getInitializationContext().workersDir, "prettier.js"),
2736
+ // );
2737
+ // const formatCodeSync = createSyncFn(paths.worker("prettier"));
2738
+ function formatCode(code) {
2739
+ try {
2740
+ return createSyncFn(path.join(getInitializationContext().workersDir, "prettier.js"))(code);
2741
+ }
2742
+ catch (error) {
2743
+ throw new LoomaError(ERROR_CODES.VALIDATION_FAILED, `Syntax validation failed:\n${error.message}`);
2744
+ }
2745
+ }
2746
+ function formatObjectCode(obj) {
2747
+ const result = { ...obj };
2748
+ const keysToFormat = ["code", "css", "component"];
2749
+ for (const key of keysToFormat) {
2750
+ if (typeof result[key] === "string") {
2751
+ result[key] = formatCode(result[key]);
2752
+ }
2753
+ }
2754
+ return result;
2755
+ }
2756
+ /**
2757
+ * Resolves all task references inside a payload.
2758
+ *
2759
+ * Example:
2760
+ *
2761
+ * payload:
2762
+ * {
2763
+ * code: {
2764
+ * $ref: {
2765
+ * source: "task_1",
2766
+ * path: "generatedCode"
2767
+ * }
2768
+ * }
2769
+ * }
2770
+ *
2771
+ * taskOutputs:
2772
+ * {
2773
+ * task_1: {
2774
+ * generatedCode: "<div>Hello</div>"
2775
+ * }
2776
+ * }
2777
+ *
2778
+ * result:
2779
+ * {
2780
+ * code: "<div>Hello</div>"
2781
+ * }
2782
+ */
2783
+ function resolveTaskReferences({ value, taskOutputs, }) {
2784
+ // ----------------------------------------------------------
2785
+ // STEP 1:
2786
+ // Handle arrays.
2787
+ //
2788
+ // If the current value is an array,
2789
+ // recursively resolve every item.
2790
+ // ----------------------------------------------------------
2791
+ if (Array.isArray(value)) {
2792
+ return value.map((item) => resolveTaskReferences({ value: item, taskOutputs }));
2793
+ }
2794
+ // ----------------------------------------------------------
2795
+ // STEP 2:
2796
+ // Handle objects.
2797
+ //
2798
+ // Objects can either be:
2799
+ //
2800
+ // 1. A reference object
2801
+ // 2. A normal object containing nested references
2802
+ // ----------------------------------------------------------
2803
+ if (value && typeof value === "object") {
2804
+ // --------------------------------------------------------
2805
+ // STEP 3:
2806
+ // Check if this object is a reference.
2807
+ //
2808
+ // Example:
2809
+ //
2810
+ // {
2811
+ // $ref: {
2812
+ // source: "task_1",
2813
+ // path: "generatedCode"
2814
+ // }
2815
+ // }
2816
+ // --------------------------------------------------------
2817
+ if ("$ref" in value && value.$ref?.source) {
2818
+ const { source, path } = value.$ref;
2819
+ // ------------------------------------------------------
2820
+ // STEP 4:
2821
+ // Find task output.
2822
+ // ------------------------------------------------------
2823
+ const taskOutput = taskOutputs[source];
2824
+ if (!taskOutput) {
2825
+ throw new LoomaError(ERROR_CODES.TASK_ERROR, `Task output not found: ${source}`, {
2826
+ taskOutputs,
2827
+ source,
2828
+ });
2829
+ }
2830
+ // ------------------------------------------------------
2831
+ // STEP 5:
2832
+ // Resolve nested path.
2833
+ //
2834
+ // Example:
2835
+ //
2836
+ // path:
2837
+ // "generatedCode"
2838
+ //
2839
+ // path:
2840
+ // "component.jsx"
2841
+ // ------------------------------------------------------
2842
+ const pathParts = path.split(".");
2843
+ let currentValue = taskOutput;
2844
+ for (const part of pathParts) {
2845
+ if (currentValue == null || !(part in currentValue)) {
2846
+ throw new LoomaError(ERROR_CODES.TASK_ERROR, `Unable to resolve path "${path}" from task "${source}"`, {
2847
+ task: resolveTaskReferences.name,
2848
+ });
2849
+ }
2850
+ currentValue = currentValue[part];
2851
+ }
2852
+ // ------------------------------------------------------
2853
+ // STEP 6:
2854
+ // Return resolved value.
2855
+ // ------------------------------------------------------
2856
+ return currentValue;
2857
+ }
2858
+ // --------------------------------------------------------
2859
+ // STEP 7:
2860
+ // Resolve every property of a normal object.
2861
+ // --------------------------------------------------------
2862
+ return Object.fromEntries(Object.entries(value).map(([key, val]) => [
2863
+ key,
2864
+ resolveTaskReferences({ value: val, taskOutputs }),
2865
+ ]));
2866
+ }
2867
+ // ----------------------------------------------------------
2868
+ // STEP 8:
2869
+ // Primitive values need no processing.
2870
+ //
2871
+ // Examples:
2872
+ // string
2873
+ // number
2874
+ // boolean
2875
+ // null
2876
+ // undefined
2877
+ // ----------------------------------------------------------
2878
+ return value;
2879
+ }
2880
+ // function detectFileType(filename) {
2881
+ // const ext = path.extname(filename);
2882
+ // if (ext === ".jsx" || ext === ".tsx") return "jsx";
2883
+ // if (ext === ".js" || ext === ".ts") return "js";
2884
+ // if (ext === ".css") return "css";
2885
+ // if (ext === ".svg") return "svg";
2886
+ // if (filename === "package.json") return "json";
2887
+ // return "file";
2888
+ // }
2889
+ // const projectDir = path.resolve("../../");
2890
+ // const map = generateMap(projectDir);
2891
+ // fs.writeFileSync("../file-map.json", JSON.stringify(map, null, 2));
2892
+ // console.log("Generated file-map.json");
2893
+ // const SRC_DIR = path.join(__dirname, 'looma-test', 'src'); // where your frontend code lives
2894
+ // console.log(__dirname)
2895
+ // console.log(SRC_DIR)
2896
+ // console.log(SRC_DIR)
2897
+ // function readDirectoryStructure(dir, prefix = "") {
2898
+ // let structure = "";
2899
+ // const items = fs.readdirSync(dir);
2900
+ // for (const item of items) {
2901
+ // const fullPath = path.join(dir, item);
2902
+ // const relativePath = path.join(prefix, item);
2903
+ // const stat = fs.statSync(fullPath);
2904
+ // if (stat.isDirectory()) {
2905
+ // structure += `Directory: ${relativePath}\n`;
2906
+ // structure += readDirectoryStructure(fullPath, relativePath);
2907
+ // } else {
2908
+ // structure += `File: ${relativePath}\n`;
2909
+ // }
2910
+ // }
2911
+ // return structure;
2912
+ // }
2913
+ // function getFileTree(dir, depth = 0) {
2914
+ // let result = "";
2915
+ // const items = fs.readdirSync(dir);
2916
+ // for (const item of items) {
2917
+ // const path = `${dir}/${item}`;
2918
+ // const stats = fs.statSync(path);
2919
+ // result += " ".repeat(depth) + item + "\n";
2920
+ // if (stats.isDirectory()) result += getFileTree(path, depth + 1);
2921
+ // }
2922
+ // return result;
2923
+ // }
2924
+ // console.log(readDirectoryStructure(PROJECT_SRC))
2925
+ //save it to a local text file
2926
+ // function generatePrompt({ command, component }) {
2927
+ // const projectDetails = "It is a react project";
2928
+ // const llmRole = "You are a front end developer";
2929
+ // const componentText = "Consider the following code";
2930
+ // const outputText =
2931
+ // "Your response should be just the code, nothing else, no explaination";
2932
+ // const designPreferences = "";
2933
+ // const text = `Write a very simple React functional component named "Header" using javascript. Do not include explanations or markdown. Do not include import react or export statment. Output only the component code. `;
2934
+ // }
2935
+ // function backupFile(filePath) {
2936
+ // const content = fs.readFileSync(filePath, "utf8");
2937
+ // const timestamp = new Date().toISOString().replace(/[:.]/g, "_");
2938
+ // const fileName = path.basename(filePath);
2939
+ // const backupPath = path.join("llm_backups", `${fileName}_${timestamp}.bak`);
2940
+ // fs.writeFileSync(backupPath, content);
2941
+ // }
2942
+ // function restoreLastBackup(filePath) {
2943
+ // const fileName = path.basename(filePath);
2944
+ // const backups = fs
2945
+ // .readdirSync("llm_backups")
2946
+ // .filter((f) => f.startsWith(fileName))
2947
+ // .sort()
2948
+ // .reverse();
2949
+ // if (backups.length === 0) return;
2950
+ // const lastBackup = path.join("llm_backups", backups[0]);
2951
+ // const backupContent = fs.readFileSync(lastBackup, "utf8");
2952
+ // fs.writeFileSync(filePath, backupContent);
2953
+ // }
2954
+ // // file: scanStructure.js
2955
+ // // const fs from 'fs');
2956
+ // // const path from 'path');
2957
+ // import fs from "fs";
2958
+ // import path from "path";
2959
+ // function walk(dir, fileList = []) {
2960
+ // const files = fs.readdirSync(dir);
2961
+ // for (const file of files) {
2962
+ // const filepath = path.join(dir, file);
2963
+ // const stat = fs.statSync(filepath);
2964
+ // if (stat.isDirectory()) {
2965
+ // walk(filepath, fileList);
2966
+ // } else {
2967
+ // const relativePath = path.relative(process.cwd(), filepath);
2968
+ // const content = fs.readFileSync(filepath, 'utf-8');
2969
+ // fileList.push({ file: relativePath, content });
2970
+ // }
2971
+ // }
2972
+ // return fileList;
2973
+ // }
2974
+ // const files = walk('./src');
2975
+ // const summary = files.map(f => `--- ${f.file} ---\n${f.content}`).join('\n\n');
2976
+ // fs.writeFileSync('structure.txt', summary);
2977
+ /**
2978
+ * Recursively crawls component directories
2979
+ * and builds Looma component registry.
2980
+ *
2981
+ * ------------------------------------------------------------
2982
+ * WHY RECURSIVE CRAWLING IS IMPORTANT
2983
+ * ------------------------------------------------------------
2984
+ *
2985
+ * Modern React applications almost always
2986
+ * organize components in nested structures.
2987
+ *
2988
+ * Example:
2989
+ *
2990
+ * src/components/
2991
+ * Dashboard/
2992
+ * Header/
2993
+ * Sidebar/
2994
+ *
2995
+ * Flat-only crawling becomes insufficient
2996
+ * for scalable applications.
2997
+ *
2998
+ * This function recursively traverses
2999
+ * all nested component directories.
3000
+ *
3001
+ * ------------------------------------------------------------
3002
+ * WHAT THIS FUNCTION DOES
3003
+ * ------------------------------------------------------------
3004
+ *
3005
+ * - crawls nested component directories
3006
+ * - finds JSX/TSX component files
3007
+ * - detects CSS files
3008
+ * - generates component registry entries
3009
+ * - supports scalable project structures
3010
+ *
3011
+ * ------------------------------------------------------------
3012
+ * PARAMS
3013
+ * ------------------------------------------------------------
3014
+ *
3015
+ * @param {Object} params
3016
+ *
3017
+ * @param {string} params.componentsPath
3018
+ * Root components directory.
3019
+ *
3020
+ * Example:
3021
+ *
3022
+ * src/components
3023
+ *
3024
+ * @param {Object} params.registry
3025
+ * Existing registry object.
3026
+ *
3027
+ * ------------------------------------------------------------
3028
+ * RETURNS
3029
+ * ------------------------------------------------------------
3030
+ *
3031
+ * @returns {Object}
3032
+ *
3033
+ * Component registry object.
3034
+ *
3035
+ */
3036
+ function createComponentRegistry({ componentsPath, projectRoot, registry = {}, }) {
3037
+ /**
3038
+ * ----------------------------------------------------------
3039
+ * INTERNAL RECURSIVE DIRECTORY WALKER
3040
+ * ----------------------------------------------------------
3041
+ */
3042
+ function walkDirectory(currentDirectory) {
3043
+ // --------------------------------------------------------
3044
+ // Read all files/folders inside current directory
3045
+ // --------------------------------------------------------
3046
+ const entries = fs.readdirSync(currentDirectory, {
3047
+ withFileTypes: true,
3048
+ });
3049
+ // --------------------------------------------------------
3050
+ // Process every directory entry
3051
+ // --------------------------------------------------------
3052
+ for (const entry of entries) {
3053
+ // ------------------------------------------------------
3054
+ // Build absolute path
3055
+ // ------------------------------------------------------
3056
+ const absoluteEntryPath = path.join(currentDirectory, entry.name);
3057
+ // ------------------------------------------------------
3058
+ // Build project-relative path
3059
+ // ------------------------------------------------------
3060
+ const relativeEntryPath = path.relative(projectRoot, absoluteEntryPath);
3061
+ // ------------------------------------------------------
3062
+ // RECURSIVE DIRECTORY HANDLING
3063
+ // ------------------------------------------------------
3064
+ if (entry.isDirectory()) {
3065
+ // ----------------------------------------------------
3066
+ // Continue recursive crawl
3067
+ // ----------------------------------------------------
3068
+ walkDirectory(absoluteEntryPath);
3069
+ // ----------------------------------------------------
3070
+ // Move to next entry
3071
+ // ----------------------------------------------------
3072
+ continue;
3073
+ }
3074
+ // ------------------------------------------------------
3075
+ // Skip non JSX/TSX files
3076
+ // ------------------------------------------------------
3077
+ const isComponentFile = entry.name.endsWith(".jsx") || entry.name.endsWith(".tsx");
3078
+ if (!isComponentFile) {
3079
+ continue;
3080
+ }
3081
+ // ------------------------------------------------------
3082
+ // Extract component name
3083
+ // ------------------------------------------------------
3084
+ const componentName = path.basename(entry.name, path.extname(entry.name));
3085
+ // ------------------------------------------------------
3086
+ // Build component id
3087
+ // ------------------------------------------------------
3088
+ const componentId = `cmp_${componentName.toLowerCase()}`;
3089
+ // --------------------------------------------------------
3090
+ // Read component source code
3091
+ // --------------------------------------------------------
3092
+ const code = fs.readFileSync(path.join(currentDirectory, `${componentName}.jsx`), "utf8");
3093
+ // --------------------------------------------------------
3094
+ // Parse AST
3095
+ // --------------------------------------------------------
3096
+ const ast = parse(code, {
3097
+ sourceType: "module",
3098
+ plugins: ["jsx"],
3099
+ });
3100
+ // --------------------------------------------------------
3101
+ // Default metadata values
3102
+ // --------------------------------------------------------
3103
+ let exported = false;
3104
+ let props = [];
3105
+ let rootElement = null;
3106
+ const childComponents = [];
3107
+ // --------------------------------------------------------
3108
+ // Traverse AST
3109
+ // --------------------------------------------------------
3110
+ traverse.default(ast, {
3111
+ // ------------------------------------------------------
3112
+ // Detect exported component
3113
+ // ------------------------------------------------------
3114
+ ExportDefaultDeclaration(path) {
3115
+ exported = true;
3116
+ },
3117
+ // ------------------------------------------------------
3118
+ // Detect component declaration
3119
+ // ------------------------------------------------------
3120
+ FunctionDeclaration(path) {
3121
+ // ----------------------------------------------------
3122
+ // Ignore unrelated functions
3123
+ // ----------------------------------------------------
3124
+ if (path.node.id.name !== componentName) {
3125
+ return;
3126
+ }
3127
+ // ----------------------------------------------------
3128
+ // Extract props
3129
+ // ----------------------------------------------------
3130
+ const firstParam = path.node.params[0];
3131
+ // ----------------------------------------------------
3132
+ // props object destructuring
3133
+ //
3134
+ // function Header({
3135
+ // title,
3136
+ // logo
3137
+ // })
3138
+ // ----------------------------------------------------
3139
+ if (t.isObjectPattern(firstParam)) {
3140
+ firstParam.properties.forEach((property) => {
3141
+ if (t.isObjectProperty(property)) {
3142
+ if (t.isIdentifier(property.key)) {
3143
+ props.push(property.key.name);
3144
+ }
3145
+ }
3146
+ });
3147
+ }
3148
+ // ----------------------------------------------------
3149
+ // plain props object
3150
+ //
3151
+ // function Header(props)
3152
+ // ----------------------------------------------------
3153
+ if (t.isIdentifier(firstParam)) {
3154
+ props.push(firstParam.name);
3155
+ }
3156
+ },
3157
+ // ------------------------------------------------------
3158
+ // Detect JSX root element
3159
+ // ------------------------------------------------------
3160
+ ReturnStatement(path) {
3161
+ // ----------------------------------------------------
3162
+ // Only inspect JSX returns
3163
+ // ----------------------------------------------------
3164
+ if (!t.isJSXElement(path.node.argument)) {
3165
+ return;
3166
+ }
3167
+ const openingElement = path.node.argument.openingElement;
3168
+ // ----------------------------------------------------
3169
+ // Extract root element name
3170
+ //
3171
+ // Example:
3172
+ //
3173
+ // <header>
3174
+ // ----------------------------------------------------
3175
+ if (t.isJSXIdentifier(openingElement.name)) {
3176
+ rootElement = openingElement.name.name;
3177
+ }
3178
+ },
3179
+ // ------------------------------------------------------
3180
+ // Detect child component usage
3181
+ // ------------------------------------------------------
3182
+ JSXOpeningElement(path) {
3183
+ // ----------------------------------------------------
3184
+ // Ignore html tags
3185
+ // ----------------------------------------------------
3186
+ if (!t.isJSXIdentifier(path.node.name)) {
3187
+ return;
3188
+ }
3189
+ const tagName = path.node.name.name;
3190
+ // ----------------------------------------------------
3191
+ // React component names start uppercase
3192
+ // ----------------------------------------------------
3193
+ const isReactComponent = /^[A-Z]/.test(tagName);
3194
+ if (!isReactComponent) {
3195
+ return;
3196
+ }
3197
+ // ----------------------------------------------------
3198
+ // Ignore self-reference
3199
+ // ----------------------------------------------------
3200
+ if (tagName === componentName) {
3201
+ return;
3202
+ }
3203
+ // ----------------------------------------------------
3204
+ // Prevent duplicates
3205
+ // ----------------------------------------------------
3206
+ if (!childComponents.includes(tagName)) {
3207
+ childComponents.push(tagName);
3208
+ }
3209
+ },
3210
+ });
3211
+ // ------------------------------------------------------
3212
+ // Detect CSS file
3213
+ // ------------------------------------------------------
3214
+ const cssCandidates = [
3215
+ `${componentName}.css`,
3216
+ `${componentName}.module.css`,
3217
+ ];
3218
+ // ------------------------------------------------------
3219
+ // Try finding matching CSS file
3220
+ // ------------------------------------------------------
3221
+ let cssPath = null;
3222
+ for (const cssFileName of cssCandidates) {
3223
+ // ----------------------------------------------------
3224
+ // Build candidate CSS path
3225
+ // ----------------------------------------------------
3226
+ const absoluteCssPath = path.join(currentDirectory, cssFileName);
3227
+ // ----------------------------------------------------
3228
+ // Check if CSS file exists
3229
+ // ----------------------------------------------------
3230
+ if (fs.existsSync(absoluteCssPath)) {
3231
+ // --------------------------------------------------
3232
+ // Store project-relative css path
3233
+ // --------------------------------------------------
3234
+ cssPath = path.relative(projectRoot, absoluteCssPath);
3235
+ break;
3236
+ }
3237
+ }
3238
+ // ------------------------------------------------------
3239
+ // Create registry entry
3240
+ // ------------------------------------------------------
3241
+ registry[componentId] = {
3242
+ componentId,
3243
+ componentName,
3244
+ filePath: relativeEntryPath,
3245
+ cssPath: fs.existsSync(cssPath) ? cssPath : null,
3246
+ importPath: relativeEntryPath,
3247
+ parentComponent: null,
3248
+ childComponents,
3249
+ exported,
3250
+ props,
3251
+ rootElement,
3252
+ lastUpdated: Date.now(),
3253
+ };
3254
+ // ----------------------------------------------------------
3255
+ // STEP 5:
3256
+ // Resolve parent-child relationships
3257
+ // ----------------------------------------------------------
3258
+ Object.values(registry).forEach((component) => {
3259
+ component.childComponents.forEach((childName) => {
3260
+ // --------------------------------------------------
3261
+ // Find matching child component
3262
+ // --------------------------------------------------
3263
+ const childComponent = Object.values(registry).find((entry) => entry.componentName === childName);
3264
+ if (!childComponent) {
3265
+ return;
3266
+ }
3267
+ // --------------------------------------------------
3268
+ // Assign parent relationship
3269
+ // --------------------------------------------------
3270
+ childComponent.parentComponent = component.componentName;
3271
+ });
3272
+ });
3273
+ }
3274
+ }
3275
+ // ----------------------------------------------------------
3276
+ // Start recursive crawling
3277
+ // ----------------------------------------------------------
3278
+ walkDirectory(componentsPath);
3279
+ // ----------------------------------------------------------
3280
+ // Return final registry
3281
+ // ----------------------------------------------------------
3282
+ return { ...registry };
3283
+ }
3284
+ /**
3285
+ * Reads package.json and returns:
3286
+ *
3287
+ * - dependencies
3288
+ * - devDependencies
3289
+ *
3290
+ * ------------------------------------------------------------
3291
+ * WHY THIS FUNCTION EXISTS
3292
+ * ------------------------------------------------------------
3293
+ *
3294
+ * Looma's planner must understand:
3295
+ *
3296
+ * - available UI libraries
3297
+ * - routing libraries
3298
+ * - css systems
3299
+ * - animation libraries
3300
+ * - state management tools
3301
+ *
3302
+ * before generating code.
3303
+ *
3304
+ * Example:
3305
+ *
3306
+ * If project already has:
3307
+ *
3308
+ * - tailwindcss
3309
+ * - framer-motion
3310
+ * - react-router-dom
3311
+ *
3312
+ * then Looma should REUSE them
3313
+ * instead of generating custom solutions.
3314
+ *
3315
+ * ------------------------------------------------------------
3316
+ * WHERE THIS FUNCTION IS USEFUL
3317
+ * ------------------------------------------------------------
3318
+ *
3319
+ * Useful before:
3320
+ *
3321
+ * - planning mutations
3322
+ * - generating components
3323
+ * - generating styles
3324
+ * - deciding architecture
3325
+ * - choosing UI patterns
3326
+ *
3327
+ * ------------------------------------------------------------
3328
+ * PARAMS
3329
+ * ------------------------------------------------------------
3330
+ *
3331
+ * @param {Object} params
3332
+ *
3333
+ * @param {string} params.projectRoot
3334
+ * Root directory of project.
3335
+ *
3336
+ * Example:
3337
+ *
3338
+ * "/Users/sarv/project"
3339
+ *
3340
+ * ------------------------------------------------------------
3341
+ * RETURNS
3342
+ * ------------------------------------------------------------
3343
+ *
3344
+ * @returns {Object}
3345
+ *
3346
+ * {
3347
+ * dependencies,
3348
+ * devDependencies,
3349
+ * allPackages
3350
+ * }
3351
+ *
3352
+ */
3353
+ function getProjectDependencies({ projectRoot, }) {
3354
+ // ----------------------------------------------------------
3355
+ // STEP 1:
3356
+ // Build absolute package.json path
3357
+ // ----------------------------------------------------------
3358
+ const packageJsonPath = path.join(path.resolve(projectRoot), "package.json");
3359
+ // ----------------------------------------------------------
3360
+ // STEP 2:
3361
+ // Validate package.json existence
3362
+ // ----------------------------------------------------------
3363
+ if (!fs.existsSync(packageJsonPath)) {
3364
+ throw new LoomaError(ERROR_CODES.FILE_NOT_FOUND, `package.json not found at: ${packageJsonPath}`);
3365
+ }
3366
+ // ----------------------------------------------------------
3367
+ // STEP 3:
3368
+ // Read package.json file
3369
+ // ----------------------------------------------------------
3370
+ const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
3371
+ // ----------------------------------------------------------
3372
+ // STEP 4:
3373
+ // Parse JSON safely
3374
+ // ----------------------------------------------------------
3375
+ let packageJson;
3376
+ try {
3377
+ packageJson = JSON.parse(packageJsonContent);
3378
+ }
3379
+ catch (error) {
3380
+ throw new LoomaError(ERROR_CODES.INTERNAL_ERROR, `Invalid package.json format`);
3381
+ }
3382
+ // ----------------------------------------------------------
3383
+ // STEP 5:
3384
+ // Extract dependencies
3385
+ // ----------------------------------------------------------
3386
+ const dependencies = packageJson.dependencies || {};
3387
+ // ----------------------------------------------------------
3388
+ // STEP 6:
3389
+ // Extract devDependencies
3390
+ // ----------------------------------------------------------
3391
+ const devDependencies = packageJson.devDependencies || {};
3392
+ // ----------------------------------------------------------
3393
+ // STEP 7:
3394
+ // Merge all packages
3395
+ // ----------------------------------------------------------
3396
+ const allPackages = {
3397
+ ...dependencies,
3398
+ ...devDependencies,
3399
+ };
3400
+ // ----------------------------------------------------------
3401
+ // STEP 8:
3402
+ // Return package information
3403
+ // ----------------------------------------------------------
3404
+ return {
3405
+ dependencies,
3406
+ devDependencies,
3407
+ allPackages,
3408
+ };
3409
+ }
3410
+ function runtimeUiSnapshot() {
3411
+ return {
3412
+ currentRoute: "/dashboard",
3413
+ visibleComponents: [
3414
+ {
3415
+ id: "cmp_42",
3416
+ name: "Sidebar",
3417
+ bounds: {},
3418
+ visible: true,
3419
+ },
3420
+ ],
3421
+ selectedComponent: {
3422
+ id: "cmp_42",
3423
+ },
3424
+ appState: {
3425
+ theme: "dark",
3426
+ auth: true,
3427
+ },
3428
+ viewport: {
3429
+ width: 1440,
3430
+ height: 900,
3431
+ },
3432
+ };
3433
+ }
3434
+ function getOperationById() { }
3435
+ // function generateTasksDocs(filePath) {
3436
+ // const code = fs.readFileSync(path.resolve(filePath), "utf8");
3437
+ // // console.log(code);
3438
+ // const ast = parse(code, {
3439
+ // sourceType: "module",
3440
+ // plugins: ["typescript", "jsx"],
3441
+ // });
3442
+ // const exportedNames = new Set();
3443
+ // const functionDetails = {};
3444
+ // // Helper to extract the "Description: ..." text from a node's leading comments
3445
+ // function extractDescription(node) {
3446
+ // if (!node.leadingComments) return "";
3447
+ // for (const comment of node.leadingComments) {
3448
+ // // Look for "Description:" followed by any text inside the block comment
3449
+ // const match = comment.value.match(/DescriptionForPrompt:\s*(.*)/i);
3450
+ // if (match && match[1]) {
3451
+ // return match[1].trim(); // Returns just the description string
3452
+ // }
3453
+ // }
3454
+ // return "";
3455
+ // }
3456
+ // function extractJSDocReturns(node) {
3457
+ // if (!node.leadingComments) return "";
3458
+ // for (const comment of node.leadingComments) {
3459
+ // // This regex looks for @returns followed by anything wrapped in double curly braces {{ ... }}
3460
+ // // The 's' flag at the end allows the regex to match across multiple lines
3461
+ // const match = comment.value.match(/@returns\s*\{\{(.*?)\}\}/s);
3462
+ // if (match && match[1]) {
3463
+ // // 1. Split the captured block into individual lines
3464
+ // // 2. Remove leading spaces and JSDoc asterisks (*) from each line
3465
+ // // 3. Filter out empty lines and join them back together
3466
+ // const cleanedLines = match[1]
3467
+ // .split("\n")
3468
+ // .map((line) => line.replace(/^\s*\*\s*/, "").trim())
3469
+ // .filter((line) => line.length > 0);
3470
+ // // Reconstruct the object string format
3471
+ // return `{\n ${cleanedLines.join("\n ")}\n}`;
3472
+ // }
3473
+ // }
3474
+ // return "";
3475
+ // }
3476
+ // // Helper to convert Babel parameter nodes back into a string
3477
+ // function getParamsString(paramsNodes) {
3478
+ // return paramsNodes.map((param) => generate.default(param).code).join(", ");
3479
+ // }
3480
+ // // 1. First Pass: Find what is exported via module.exports
3481
+ // traverse.default(ast, {
3482
+ // AssignmentExpression(path) {
3483
+ // const { left, right } = path.node;
3484
+ // const isModuleExports =
3485
+ // left.type === "MemberExpression" &&
3486
+ // t.isIdentifier(left.object) &&
3487
+ // left.object.name === "module" &&
3488
+ // t.isIdentifier(left.property) &&
3489
+ // left.property.name === "exports";
3490
+ // if (isModuleExports && right.type === "ObjectExpression") {
3491
+ // for (const prop of right.properties) {
3492
+ // if (
3493
+ // prop.type === "ObjectProperty" &&
3494
+ // t.isIdentifier(prop.key) &&
3495
+ // prop.key.name
3496
+ // ) {
3497
+ // exportedNames.add(prop.key.name);
3498
+ // }
3499
+ // }
3500
+ // }
3501
+ // },
3502
+ // });
3503
+ // // 2. Second Pass: Extract parameters AND descriptions
3504
+ // traverse.default(ast, {
3505
+ // // Matches: function fun1() {}
3506
+ // FunctionDeclaration(path) {
3507
+ // const name = path.node.id?.name;
3508
+ // if (exportedNames.has(name)) {
3509
+ // let actualReturn = "void";
3510
+ // // Safe inner traversal using the current path context
3511
+ // path.traverse({
3512
+ // ReturnStatement(innerPath) {
3513
+ // if (innerPath.getFunctionParent().node !== path.node) return; // Skip sub-functions
3514
+ // actualReturn = innerPath.node.argument
3515
+ // ? generate.default(innerPath.node.argument).code
3516
+ // : "undefined";
3517
+ // },
3518
+ // });
3519
+ // functionDetails[name] = {
3520
+ // params: getParamsString(path.node.params),
3521
+ // returnsDoc: extractJSDocReturns(path.node) || "void",
3522
+ // actualReturn: actualReturn.replace(/\s+/g, " "),
3523
+ // description: extractDescription(path.node),
3524
+ // };
3525
+ // }
3526
+ // },
3527
+ // // Matches: const fun1 = () => {}
3528
+ // // Note: Comments are attached to the VariableDeclaration statement container
3529
+ // VariableDeclarator(path) {
3530
+ // const name = t.isIdentifier(path.node.id) ? path.node.id.name : null;
3531
+ // if (exportedNames.has(name)) {
3532
+ // const init = path.node.init;
3533
+ // if (
3534
+ // init &&
3535
+ // ["ArrowFunctionExpression", "FunctionExpression"].includes(init.type)
3536
+ // ) {
3537
+ // // For variables, leadingComments live on the parent VariableDeclaration node
3538
+ // const parentNode = path.parentPath.node;
3539
+ // let actualReturn = "void";
3540
+ // // Arrow functions can have an implicit return expression instead of a block statement body
3541
+ // if (
3542
+ // (t.isFunctionExpression(init) ||
3543
+ // t.isArrowFunctionExpression(init)) &&
3544
+ // init.body.type !== "BlockStatement"
3545
+ // ) {
3546
+ // // Implicit arrow return: () => ({ status: 'ok' })
3547
+ // actualReturn = generate.default(init.body).code;
3548
+ // } else {
3549
+ // // Block statement arrow return: () => { return { status: 'ok' } }
3550
+ // path.traverse({
3551
+ // ReturnStatement(innerPath) {
3552
+ // if (innerPath.getFunctionParent().node !== init) return; // Skip sub-functions
3553
+ // actualReturn = innerPath.node.argument
3554
+ // ? generate.default(innerPath.node.argument).code
3555
+ // : "undefined";
3556
+ // },
3557
+ // });
3558
+ // }
3559
+ // const complexReturnDoc = extractJSDocReturns(path.parentPath.node);
3560
+ // functionDetails[name] = {
3561
+ // params:
3562
+ // t.isFunctionExpression(init) || t.isArrowFunctionExpression(init)
3563
+ // ? getParamsString(init.params)
3564
+ // : "",
3565
+ // returnsDoc: complexReturnDoc || "void",
3566
+ // actualReturn: actualReturn.replace(/\s+/g, " "),
3567
+ // description: extractDescription(parentNode),
3568
+ // };
3569
+ // }
3570
+ // }
3571
+ // },
3572
+ // });
3573
+ // // 3. Format the documentation string
3574
+ // const formattedLines = Array.from(exportedNames).map((name: any) => {
3575
+ // const details = functionDetails[name];
3576
+ // if (!details) return `${name}()`;
3577
+ // const paramStr = details.params;
3578
+ // const descStr = details.description ? ` - ${details.description}` : "";
3579
+ // return `${name}(${paramStr}) returns ${details.returnsDoc}${descStr}`;
3580
+ // // return `
3581
+ // // ${name}: {
3582
+ // // payload: ${paramStr};
3583
+ // // result: ${details.actualReturn};
3584
+ // // };
3585
+ // // `;
3586
+ // });
3587
+ // const functionsListString = formattedLines.join("\n");
3588
+ // return functionsListString;
3589
+ // }
3590
+ function appendAction({ command, operations, undoPath }) {
3591
+ // Generate a unique action id.
3592
+ const actionId = `ac_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
3593
+ // Construct the action object.
3594
+ const action = {
3595
+ id: actionId,
3596
+ command,
3597
+ operations,
3598
+ timestamp: new Date().toISOString(),
3599
+ };
3600
+ // Read the current contents of undo.json.
3601
+ const actions = readActionStack(undoPath);
3602
+ // Append the new action to the stack.
3603
+ actions.push(action);
3604
+ // Persist the updated stack.
3605
+ writeActionStack(undoPath, actions);
3606
+ console.log("Logged action:", actionId);
3607
+ }
3608
+ /**
3609
+ * Reads an action stack (undo.json or redo.json).
3610
+ *
3611
+ * - Creates the file if it does not exist.
3612
+ * - Initializes it with an empty array if needed.
3613
+ * - Returns the parsed array of actions.
3614
+ *
3615
+ * @param {string} filePath
3616
+ * @returns {Array}
3617
+ */
3618
+ function readActionStack(filePath) {
3619
+ // Create the file if it doesn't exist.
3620
+ if (!fs.existsSync(filePath)) {
3621
+ fs.writeFileSync(filePath, "[]", "utf8");
3622
+ }
3623
+ // Read the contents of the file.
3624
+ const fileContents = fs.readFileSync(filePath, "utf8").trim();
3625
+ // Treat an empty file as an empty stack.
3626
+ if (fileContents.length === 0) {
3627
+ return [];
3628
+ }
3629
+ // Parse and return the action stack.
3630
+ return JSON.parse(fileContents);
3631
+ }
3632
+ /**
3633
+ * Writes an action stack (undo.json or redo.json).
3634
+ *
3635
+ * @param {string} filePath
3636
+ * @param {Array} actions
3637
+ */
3638
+ function writeActionStack(filePath, actions) {
3639
+ // Persist the updated stack in a readable format.
3640
+ fs.writeFileSync(filePath, JSON.stringify(actions, null, 2), "utf8");
3641
+ }
3642
+ // function appendLLMLog(log) {
3643
+ // console.log(path.resolve("./looma-logs/llm-logs.jsonl"));
3644
+ // fs.appendFileSync(
3645
+ // "./looma-logs/llm-logs.jsonl",
3646
+ // JSON.stringify(log) + "\n",
3647
+ // "utf8",
3648
+ // );
3649
+ // }
3650
+ async function listFiles({ directory, recursive, }) {
3651
+ const files = [];
3652
+ async function walk(dir) {
3653
+ const entries = await fs.promises.readdir(dir, {
3654
+ withFileTypes: true,
3655
+ });
3656
+ for (const entry of entries) {
3657
+ const fullPath = path.join(dir, entry.name);
3658
+ if (entry.isDirectory()) {
3659
+ if (recursive) {
3660
+ await walk(fullPath);
3661
+ }
3662
+ }
3663
+ else {
3664
+ files.push(fullPath);
3665
+ }
3666
+ }
3667
+ }
3668
+ await walk(directory);
3669
+ return { files };
3670
+ }
3671
+ function readFile(filePath) {
3672
+ return fs.readFileSync(filePath, "utf8");
3673
+ }
3674
+ function writeFile({ filePath, content, }) {
3675
+ const formatted = formatCode(content);
3676
+ fs.writeFileSync(filePath, formatted, "utf8");
3677
+ }
3678
+ export default {
3679
+ // insertCode,
3680
+ // expose only when planner becomes mature enough to use it directly
3681
+ // ensureImport,
3682
+ // removeImport,
3683
+ // enrichImport,
3684
+ // optimizeImports,
3685
+ // updateComponentImports,
3686
+ // findNodeByLine,
3687
+ // findComponentByName,
3688
+ // findJSXElement,
3689
+ // resolveImportConflicts,
3690
+ // findImportDeclaration,
3691
+ // buildImportDeclaration,
3692
+ // getImportSpecifiers,
3693
+ // removeImportSpecifier,
3694
+ // mergeImportDeclarations,
3695
+ // isImportUsed,
3696
+ // do not expose
3697
+ // ensureLibrary,
3698
+ // ensureComponentStructure,
3699
+ // normalizeComponent,
3700
+ // findComponentDirectory,
3701
+ // inferComponentName,
3702
+ matchesSelector,
3703
+ // ensureStyleFile,
3704
+ // findCssSelector,
3705
+ // resolveCssClassConflicts,
3706
+ resolveStyleDependencies,
3707
+ listFiles,
3708
+ // parseAST,
3709
+ // parseCSS,
3710
+ // parseRoutes,
3711
+ // parseComponentDependencies,
3712
+ // parseProps,
3713
+ // // parseTypescriptTypes,
3714
+ // parseExports,
3715
+ // parseJSCode,
3716
+ // parseHooksUsage,
3717
+ // // parseStateUsage,
3718
+ // // parseEventHandlers,
3719
+ // // parseAPICalls,
3720
+ // // parseDOMHierarchy,
3721
+ generateCodeFromAST,
3722
+ createComponentRegistry,
3723
+ getProjectDependencies,
3724
+ runtimeUiSnapshot,
3725
+ getExportedFunctionNames,
3726
+ generateTasksDocs,
3727
+ resolveTaskReferences,
3728
+ appendAction,
3729
+ // mutationTasks,
3730
+ // astTasks,
3731
+ formatCode,
3732
+ formatObjectCode,
3733
+ readFile,
3734
+ writeFile,
3735
+ };
3736
+ //# sourceMappingURL=general.js.map