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,1030 @@
1
+ /*
2
+
3
+ executeAction();
4
+ appendAction();
5
+ undo();
6
+ redo();
7
+ rollbackAction();
8
+ replayAction();
9
+ rollbackSingleOperation();
10
+ replaySingleOperation();
11
+
12
+ */
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import crypto from "crypto";
16
+ import sse from "../../server/sse.js";
17
+ import rollback from "../../execute/history/rollback-handlers.js";
18
+ import { ERROR_CODES } from "../../schemas/index.js";
19
+ import { LoomaError } from "../../server/error.js";
20
+ /**
21
+ * Performs one-step undo.
22
+ *
23
+ * Steps:
24
+ * 1. Read undo.json.
25
+ * 2. Remove the latest operation from undo stack.
26
+ * 3. Execute rollback for that operation.
27
+ * 4. Read redo.json (create if missing).
28
+ * 5. Push the operation onto redo stack.
29
+ * 6. Save both files.
30
+ */
31
+ function undo({ appContext, }) {
32
+ sse.emitInfo("Undoing last instruction...");
33
+ // ----------------------------------------------------------
34
+ // STEP 1:
35
+ // Resolve paths.
36
+ // ----------------------------------------------------------
37
+ const { undoPath, redoPath } = appContext.project;
38
+ // const undoPath = appContext.project.undoPath
39
+ // const redoPath = path.resolve(paths.redo());
40
+ // ----------------------------------------------------------
41
+ // STEP 2:
42
+ // If undo stack doesn't exist, there is nothing to undo.
43
+ // ----------------------------------------------------------
44
+ if (!fs.existsSync(undoPath)) {
45
+ throw new LoomaError(ERROR_CODES.FILE_NOT_FOUND, "Undo history does not exist");
46
+ // return {
47
+ // success: false,
48
+ // message: "Undo history does not exist",
49
+ // };
50
+ }
51
+ // ----------------------------------------------------------
52
+ // STEP 3:
53
+ // Read undo stack.
54
+ // ----------------------------------------------------------
55
+ const undoStack = JSON.parse(fs.readFileSync(undoPath, "utf8"));
56
+ // ----------------------------------------------------------
57
+ // STEP 4:
58
+ // If stack is empty, nothing to undo.
59
+ // ----------------------------------------------------------
60
+ if (undoStack.length === 0) {
61
+ return {
62
+ success: false,
63
+ message: "Nothing to undo, Stack is clean.",
64
+ };
65
+ }
66
+ // ----------------------------------------------------------
67
+ // STEP 5:
68
+ // Remove latest operation.
69
+ // pop() behaves like a stack.
70
+ // ----------------------------------------------------------
71
+ const action = undoStack.pop();
72
+ // here const logId = undoStack.pop()
73
+ // get logs from ./logs/logid.json
74
+ // ----------------------------------------------------------
75
+ // STEP 6:
76
+ // Save updated undo stack.
77
+ // ----------------------------------------------------------
78
+ fs.writeFileSync(undoPath, JSON.stringify(undoStack, null, 2));
79
+ // ----------------------------------------------------------
80
+ // STEP 7:
81
+ // Execute rollback.
82
+ // rollbackOperation() should perform the inverse
83
+ // of the original operation.
84
+ // ----------------------------------------------------------
85
+ for (const operation of action.operations) {
86
+ const taskName = operation.task;
87
+ const rollbackHandler = rollback.undoHandlers[taskName];
88
+ if (!rollbackHandler) {
89
+ throw new LoomaError(ERROR_CODES.FUNCTION_NOT_FOUND, `Rollback handler missing for task: ${taskName}`);
90
+ }
91
+ rollbackHandler(operation, appContext);
92
+ }
93
+ // ----------------------------------------------------------
94
+ // STEP 8:
95
+ // Ensure redo.json exists.
96
+ // ----------------------------------------------------------
97
+ if (!fs.existsSync(redoPath)) {
98
+ fs.mkdirSync(path.dirname(redoPath), {
99
+ recursive: true,
100
+ });
101
+ fs.writeFileSync(redoPath, "[]");
102
+ }
103
+ // ----------------------------------------------------------
104
+ // STEP 9:
105
+ // Read redo stack.
106
+ // ----------------------------------------------------------
107
+ const redoStack = JSON.parse(fs.readFileSync(redoPath, "utf8"));
108
+ // ----------------------------------------------------------
109
+ // STEP 10:
110
+ // Push the undone operation onto redo stack.
111
+ // ----------------------------------------------------------
112
+ redoStack.push(action);
113
+ // ----------------------------------------------------------
114
+ // STEP 11:
115
+ // Persist redo stack.
116
+ // ----------------------------------------------------------
117
+ fs.writeFileSync(redoPath, JSON.stringify(redoStack, null, 2));
118
+ // ----------------------------------------------------------
119
+ // STEP 12:
120
+ // Return success.
121
+ // ----------------------------------------------------------
122
+ return {
123
+ success: true,
124
+ message: `Undo complete for this instruction: ${action.command}`,
125
+ };
126
+ }
127
+ /**
128
+ * Performs one-step redo.
129
+ *
130
+ * Steps:
131
+ * 1. Read redo stack.
132
+ * 2. Remove the latest operation.
133
+ * 3. Re-execute the original task.
134
+ * 4. Push the operation back onto undo stack.
135
+ * 5. Save both files.
136
+ */
137
+ function redo({ appContext, }) {
138
+ sse.emitInfo("Re Appling last instruction...");
139
+ // ----------------------------------------------------------
140
+ // STEP 1:
141
+ // Resolve paths to undo.json and redo.json.
142
+ // ----------------------------------------------------------
143
+ const { undoPath, redoPath } = appContext.project;
144
+ // const undoPath = path.resolve(LOOMA_UNDO_PATH);
145
+ // const redoPath = path.resolve(LOOMA_REDO_PATH);
146
+ // ----------------------------------------------------------
147
+ // STEP 2:
148
+ // If redo stack does not exist, there is nothing to redo.
149
+ // ----------------------------------------------------------
150
+ if (!fs.existsSync(redoPath)) {
151
+ throw new LoomaError(ERROR_CODES.FILE_NOT_FOUND, "Redo history does not exist");
152
+ // return {
153
+ // success: false,
154
+ // message: "Redo history does not exist",
155
+ // };
156
+ }
157
+ // ----------------------------------------------------------
158
+ // STEP 3:
159
+ // Read redo stack.
160
+ // ----------------------------------------------------------
161
+ const redoStack = JSON.parse(fs.readFileSync(redoPath, "utf8"));
162
+ // ----------------------------------------------------------
163
+ // STEP 4:
164
+ // If redo stack is empty, nothing to redo.
165
+ // ----------------------------------------------------------
166
+ if (redoStack.length === 0) {
167
+ return {
168
+ success: false,
169
+ message: "Nothing to redo",
170
+ };
171
+ }
172
+ // ----------------------------------------------------------
173
+ // STEP 5:
174
+ // Remove the latest operation from redo stack.
175
+ // ----------------------------------------------------------
176
+ const action = redoStack.pop();
177
+ // ----------------------------------------------------------
178
+ // STEP 6:
179
+ // Re-execute the original task.
180
+ //
181
+ // IMPORTANT:
182
+ // This execution should NOT append a new operation
183
+ // into undo history because we are merely replaying
184
+ // an existing operation.
185
+ // ----------------------------------------------------------
186
+ // ----------------------------------------------------------
187
+ // STEP 7:
188
+ // Execute redo.
189
+ // ----------------------------------------------------------
190
+ for (const operation of action.operations) {
191
+ const taskName = operation.task;
192
+ const redoHandler = rollback.redoHandlers[taskName];
193
+ if (!redoHandler) {
194
+ throw new LoomaError(ERROR_CODES.FUNCTION_NOT_FOUND, `Redo handler missing for task: ${taskName}`);
195
+ }
196
+ redoHandler(operation, appContext);
197
+ }
198
+ // const result = await executeAction({
199
+ // actions: action.operations,
200
+ // componentContext,
201
+ // });
202
+ // ----------------------------------------------------------
203
+ // STEP 7:
204
+ // If redo failed, restore redo stack and abort.
205
+ // ----------------------------------------------------------
206
+ // if (!result.success) {
207
+ // redoStack.push(action);
208
+ // fs.writeFileSync(redoPath, JSON.stringify(redoStack, null, 2));
209
+ // return {
210
+ // success: false,
211
+ // action,
212
+ // };
213
+ // }
214
+ // ----------------------------------------------------------
215
+ // STEP 8:
216
+ // Persist updated redo stack.
217
+ // ----------------------------------------------------------
218
+ fs.writeFileSync(redoPath, JSON.stringify(redoStack, null, 2));
219
+ // ----------------------------------------------------------
220
+ // STEP 9:
221
+ // Ensure undo stack exists.
222
+ // ----------------------------------------------------------
223
+ if (!fs.existsSync(undoPath)) {
224
+ fs.mkdirSync(path.dirname(undoPath), {
225
+ recursive: true,
226
+ });
227
+ fs.writeFileSync(undoPath, "[]");
228
+ }
229
+ // ----------------------------------------------------------
230
+ // STEP 10:
231
+ // Read undo stack.
232
+ // ----------------------------------------------------------
233
+ const undoStack = JSON.parse(fs.readFileSync(undoPath, "utf8"));
234
+ // ----------------------------------------------------------
235
+ // STEP 11:
236
+ // Push the operation back onto undo stack.
237
+ // ----------------------------------------------------------
238
+ undoStack.push(action);
239
+ // ----------------------------------------------------------
240
+ // STEP 12:
241
+ // Persist undo stack.
242
+ // ----------------------------------------------------------
243
+ fs.writeFileSync(undoPath, JSON.stringify(undoStack, null, 2));
244
+ // ----------------------------------------------------------
245
+ // STEP 13:
246
+ // Return success.
247
+ // ----------------------------------------------------------
248
+ return {
249
+ success: true,
250
+ message: `Re applied this instruction: ${action.command}`,
251
+ };
252
+ }
253
+ /**
254
+ * Creates a new Looma operation log session.
255
+ *
256
+ * ------------------------------------------------------------
257
+ * WHY THIS FUNCTION EXISTS
258
+ * ------------------------------------------------------------
259
+ *
260
+ * Every mutation Looma performs should be traceable
261
+ * and reversible.
262
+ *
263
+ * Instead of storing all mutations in memory,
264
+ * Looma persists them into session log files.
265
+ *
266
+ * Each session gets its own JSONL file:
267
+ *
268
+ * .looma/operations/session_xxx.jsonl
269
+ *
270
+ * JSONL = one JSON object per line.
271
+ *
272
+ * This architecture is:
273
+ * - append friendly
274
+ * - crash resistant
275
+ * - scalable
276
+ * - streamable
277
+ * - easy to rollback
278
+ *
279
+ * ------------------------------------------------------------
280
+ * WHAT THIS FUNCTION DOES
281
+ * ------------------------------------------------------------
282
+ *
283
+ * - ensures .looma directory exists
284
+ * - ensures operations directory exists
285
+ * - creates a new session log file
286
+ * - writes session metadata
287
+ * - returns session information
288
+ *
289
+ * ------------------------------------------------------------
290
+ * RETURNS
291
+ * ------------------------------------------------------------
292
+ *
293
+ * {
294
+ * sessionId,
295
+ * logFilePath,
296
+ * createdAt
297
+ * }
298
+ *
299
+ */
300
+ function createOperationLog(context) {
301
+ // ----------------------------------------------------------
302
+ // STEP 1:
303
+ // Generate unique session id
304
+ // ----------------------------------------------------------
305
+ //
306
+ // We combine:
307
+ // - timestamp
308
+ // - random bytes
309
+ //
310
+ // This avoids collisions between sessions.
311
+ //
312
+ // Example:
313
+ //
314
+ // session_1748450000_a82bc91f
315
+ //
316
+ // ----------------------------------------------------------
317
+ const sessionId = `session_${Date.now()}_${crypto
318
+ .randomBytes(4)
319
+ .toString("hex")}`;
320
+ // ----------------------------------------------------------
321
+ // STEP 2:
322
+ // Resolve .looma directory path
323
+ // ----------------------------------------------------------
324
+ //
325
+ // All Looma internal files should stay isolated
326
+ // from user application code.
327
+ //
328
+ // Example:
329
+ //
330
+ // /my-project/.looma
331
+ //
332
+ // ----------------------------------------------------------
333
+ const logsDirectoryPath = context.project.logsDir;
334
+ // ----------------------------------------------------------
335
+ // STEP 3:
336
+ // Resolve operations directory path
337
+ // ----------------------------------------------------------
338
+ //
339
+ // This directory stores all session logs.
340
+ //
341
+ // Example:
342
+ //
343
+ // /my-project/.looma/operations
344
+ //
345
+ // ----------------------------------------------------------
346
+ const operationsDirectoryPath = path.join(logsDirectoryPath, "operations");
347
+ // ----------------------------------------------------------
348
+ // STEP 4:
349
+ // Ensure .looma directory exists
350
+ // ----------------------------------------------------------
351
+ //
352
+ // mkdirSync with recursive:true safely creates:
353
+ //
354
+ // - parent directories
355
+ // - nested directories
356
+ //
357
+ // and does nothing if already existing.
358
+ //
359
+ // ----------------------------------------------------------
360
+ fs.mkdirSync(logsDirectoryPath, {
361
+ recursive: true,
362
+ });
363
+ // ----------------------------------------------------------
364
+ // STEP 5:
365
+ // Ensure operations directory exists
366
+ // ----------------------------------------------------------
367
+ fs.mkdirSync(operationsDirectoryPath, {
368
+ recursive: true,
369
+ });
370
+ // ----------------------------------------------------------
371
+ // STEP 6:
372
+ // Create session log file path
373
+ // ----------------------------------------------------------
374
+ //
375
+ // Example:
376
+ //
377
+ // session_1748450000_abcd1234.jsonl
378
+ //
379
+ // ----------------------------------------------------------
380
+ const logFilePath = path.join(operationsDirectoryPath, `${sessionId}.jsonl`);
381
+ // ----------------------------------------------------------
382
+ // STEP 7:
383
+ // Create initial session metadata object
384
+ // ----------------------------------------------------------
385
+ //
386
+ // This acts as the first entry in the log.
387
+ //
388
+ // Useful later for:
389
+ // - debugging
390
+ // - recovery
391
+ // - analytics
392
+ // - session restoration
393
+ //
394
+ // ----------------------------------------------------------
395
+ const sessionMetadata = {
396
+ type: "SESSION_START",
397
+ sessionId,
398
+ createdAt: new Date().toISOString(),
399
+ version: 1,
400
+ };
401
+ // ----------------------------------------------------------
402
+ // STEP 8:
403
+ // Write metadata as first JSONL line
404
+ // ----------------------------------------------------------
405
+ //
406
+ // JSONL format:
407
+ //
408
+ // {"type":"SESSION_START",...}
409
+ //
410
+ // Every line is independent JSON.
411
+ //
412
+ // We append newline at end because:
413
+ //
414
+ // - easier streaming
415
+ // - easier appending
416
+ // - easier parsing
417
+ //
418
+ // ----------------------------------------------------------
419
+ fs.writeFileSync(logFilePath, JSON.stringify(sessionMetadata) + "\n", "utf8");
420
+ // ----------------------------------------------------------
421
+ // STEP 9:
422
+ // Return session information
423
+ // ----------------------------------------------------------
424
+ return {
425
+ sessionId,
426
+ logFilePath,
427
+ createdAt: sessionMetadata.createdAt,
428
+ };
429
+ }
430
+ /**
431
+ * Appends a mutation operation into current session log.
432
+ *
433
+ * ------------------------------------------------------------
434
+ * PARAMS
435
+ * ------------------------------------------------------------
436
+ *
437
+ * @param {Object} params
438
+ *
439
+ * @param {string} params.logFilePath
440
+ * Absolute path of current session JSONL log file.
441
+ *
442
+ * Example:
443
+ * /project/.looma/operations/session_xxx.jsonl
444
+ *
445
+ * @param {string} params.taskName
446
+ * Name of executed task.
447
+ *
448
+ * Example:
449
+ * - updateStyles
450
+ * - insertJSX
451
+ * - renameComponent
452
+ *
453
+ * @param {Object} params.target
454
+ * Information about mutation target.
455
+ *
456
+ * Structure depends on task type.
457
+ *
458
+ * Example:
459
+ * {
460
+ * cssPath: "/src/Header.css"
461
+ * }
462
+ *
463
+ * @param {Object} params.before
464
+ * Snapshot BEFORE mutation.
465
+ *
466
+ * Structure depends on task type.
467
+ *
468
+ * Example:
469
+ * {
470
+ * cssCode: ".header { color:red; }"
471
+ * }
472
+ *
473
+ * @param {Object} params.after
474
+ * Snapshot AFTER mutation.
475
+ *
476
+ * Structure depends on task type.
477
+ *
478
+ * Example:
479
+ * {
480
+ * cssCode: ".header { color:blue; }"
481
+ * }
482
+ *
483
+ * @param {Object} params.metadata
484
+ * Additional optional debugging metadata.
485
+ *
486
+ * Example:
487
+ * {
488
+ * plannerTaskId,
489
+ * executionTime,
490
+ * triggeredBy
491
+ * }
492
+ */
493
+ function appendOperation({ logFilePath, taskName, target, before, after, metadata = {}, }) {
494
+ // ----------------------------------------------------------
495
+ // STEP 1:
496
+ // Generate unique operation id
497
+ // ----------------------------------------------------------
498
+ //
499
+ // Each operation should be independently identifiable.
500
+ //
501
+ // Useful later for:
502
+ // - rollback
503
+ // - debugging
504
+ // - tracing
505
+ // - operation references
506
+ //
507
+ // ----------------------------------------------------------
508
+ const operationId = `op_${Date.now()}_${crypto
509
+ .randomBytes(4)
510
+ .toString("hex")}`;
511
+ // ----------------------------------------------------------
512
+ // STEP 2:
513
+ // Create operation object
514
+ // ----------------------------------------------------------
515
+ //
516
+ // This object represents one mutation event.
517
+ //
518
+ // Example:
519
+ //
520
+ // {
521
+ // id,
522
+ // taskName,
523
+ // before,
524
+ // after
525
+ // }
526
+ //
527
+ // ----------------------------------------------------------
528
+ const operation = {
529
+ id: operationId,
530
+ type: "OPERATION",
531
+ timestamp: new Date().toISOString(),
532
+ taskName,
533
+ target,
534
+ before,
535
+ after,
536
+ metadata,
537
+ };
538
+ // ----------------------------------------------------------
539
+ // STEP 3:
540
+ // Convert operation into JSONL line
541
+ // ----------------------------------------------------------
542
+ //
543
+ // JSONL requires:
544
+ //
545
+ // one JSON object per line
546
+ //
547
+ // ----------------------------------------------------------
548
+ const operationLine = JSON.stringify(operation) + "\n";
549
+ // ----------------------------------------------------------
550
+ // STEP 4:
551
+ // Append operation into session file
552
+ // ----------------------------------------------------------
553
+ //
554
+ // appendFileSync adds operation
555
+ // without overwriting previous logs.
556
+ //
557
+ // ----------------------------------------------------------
558
+ console.log("Appending operation: ", taskName, operationId);
559
+ fs.appendFileSync(logFilePath, operationLine, "utf8");
560
+ // ----------------------------------------------------------
561
+ // STEP 5:
562
+ // Return appended operation
563
+ // ----------------------------------------------------------
564
+ return operation;
565
+ }
566
+ /**
567
+ * Rolls back a single Looma operation.
568
+ *
569
+ * ------------------------------------------------------------
570
+ * WHY THIS FUNCTION EXISTS
571
+ * ------------------------------------------------------------
572
+ *
573
+ * AI generated mutations are never guaranteed to be correct.
574
+ *
575
+ * Even with:
576
+ * - planners
577
+ * - validators
578
+ * - AST mutations
579
+ *
580
+ * mistakes can still happen.
581
+ *
582
+ * This function provides safety by restoring
583
+ * the system to its previous state.
584
+ *
585
+ * ------------------------------------------------------------
586
+ * HOW ROLLBACK WORKS
587
+ * ------------------------------------------------------------
588
+ *
589
+ * Every mutation operation stores:
590
+ *
591
+ * {
592
+ * before: ...
593
+ * after: ...
594
+ * }
595
+ *
596
+ * Rollback simply restores:
597
+ *
598
+ * BEFORE STATE
599
+ *
600
+ * ------------------------------------------------------------
601
+ * IMPORTANT ARCHITECTURE DECISION
602
+ * ------------------------------------------------------------
603
+ *
604
+ * Rollback logic is TASK SPECIFIC.
605
+ *
606
+ * Example:
607
+ *
608
+ * updateStyles rollback:
609
+ * restore old cssCode
610
+ *
611
+ * moveFile rollback:
612
+ * move file back
613
+ *
614
+ * renameComponent rollback:
615
+ * restore old component name
616
+ *
617
+ * Therefore:
618
+ *
619
+ * rollbackHandlers map task types
620
+ * to rollback implementations.
621
+ *
622
+ * ------------------------------------------------------------
623
+ * WHAT THIS FUNCTION DOES
624
+ * ------------------------------------------------------------
625
+ *
626
+ * - finds operation
627
+ * - validates rollback support
628
+ * - calls task-specific rollback handler
629
+ * - appends rollback entry into operation log
630
+ *
631
+ * ------------------------------------------------------------
632
+ * PARAMS
633
+ * ------------------------------------------------------------
634
+ *
635
+ * @param {Object} params
636
+ *
637
+ * @param {Object} params.operation
638
+ * Previously logged operation object.
639
+ *
640
+ * Example:
641
+ * {
642
+ * id,
643
+ * taskName,
644
+ * before,
645
+ * after
646
+ * }
647
+ *
648
+ * @param {Object} params.rollbackHandlers
649
+ * Map of rollback handlers by task name.
650
+ *
651
+ * Example:
652
+ * {
653
+ * updateStyles: fn,
654
+ * moveFile: fn,
655
+ * renameComponent: fn
656
+ * }
657
+ *
658
+ * Every task type has different rollback logic.
659
+ *
660
+ * @param {string} params.logFilePath
661
+ * Absolute path of current session JSONL log file.
662
+ *
663
+ * Used for appending rollback history entry.
664
+ *
665
+ * ------------------------------------------------------------
666
+ * RETURNS
667
+ * ------------------------------------------------------------
668
+ *
669
+ * {
670
+ * success: boolean,
671
+ * operationId,
672
+ * taskName
673
+ * }
674
+ *
675
+ */
676
+ function rollbackOperation({ operation, rollbackHandlers, }) {
677
+ // ----------------------------------------------------------
678
+ // STEP 1:
679
+ // Validate operation existence
680
+ // ----------------------------------------------------------
681
+ //
682
+ // Rollback cannot happen without operation data.
683
+ //
684
+ // ----------------------------------------------------------
685
+ if (!operation) {
686
+ throw new LoomaError(ERROR_CODES.PAYLOAD_ERROR, "Operation is required for rollback.");
687
+ }
688
+ // ----------------------------------------------------------
689
+ // STEP 2:
690
+ // Extract task name
691
+ // ----------------------------------------------------------
692
+ //
693
+ // Example:
694
+ //
695
+ // updateStyles
696
+ // moveFile
697
+ // renameComponent
698
+ //
699
+ // ----------------------------------------------------------
700
+ const taskName = operation.task;
701
+ // ----------------------------------------------------------
702
+ // STEP 3:
703
+ // Resolve rollback handler
704
+ // ----------------------------------------------------------
705
+ //
706
+ // Every task has different rollback logic.
707
+ //
708
+ // Example:
709
+ //
710
+ // rollbackHandlers.updateStyles
711
+ //
712
+ // ----------------------------------------------------------
713
+ const rollbackHandler = rollbackHandlers[taskName];
714
+ // ----------------------------------------------------------
715
+ // STEP 4:
716
+ // Ensure rollback handler exists
717
+ // ----------------------------------------------------------
718
+ //
719
+ // Without rollback handler,
720
+ // operation cannot be reverted safely.
721
+ //
722
+ // ----------------------------------------------------------
723
+ if (!rollbackHandler) {
724
+ throw new LoomaError(ERROR_CODES.FUNCTION_NOT_FOUND, `Rollback handler missing for task: ${taskName}`);
725
+ }
726
+ try {
727
+ // ----------------------------------------------------------
728
+ // STEP 5:
729
+ // Execute rollback handler
730
+ // ----------------------------------------------------------
731
+ //
732
+ // Handler restores BEFORE state.
733
+ //
734
+ // Example:
735
+ //
736
+ // restore previous CSS
737
+ // restore deleted file
738
+ // restore previous JSX
739
+ //
740
+ // ----------------------------------------------------------
741
+ rollbackHandler(operation);
742
+ // ----------------------------------------------------------
743
+ // STEP 6:
744
+ // Create rollback log entry
745
+ // ----------------------------------------------------------
746
+ //
747
+ // Rollback itself should also be logged.
748
+ //
749
+ // This is important for:
750
+ //
751
+ // - debugging
752
+ // - history tracking
753
+ // - redo support
754
+ // - future time-travel systems
755
+ //
756
+ // ----------------------------------------------------------
757
+ // const rollbackEntry = {
758
+ // type: "ROLLBACK",
759
+ // rolledBackOperationId: operation.id,
760
+ // taskName,
761
+ // timestamp: new Date().toISOString(),
762
+ // };
763
+ // ----------------------------------------------------------
764
+ // STEP 7:
765
+ // Append rollback entry into session log
766
+ // ----------------------------------------------------------
767
+ //
768
+ // JSONL format:
769
+ //
770
+ // one JSON object per line
771
+ //
772
+ // ----------------------------------------------------------
773
+ // fs.appendFileSync(
774
+ // logFilePath,
775
+ // JSON.stringify(rollbackEntry) + "\n",
776
+ // "utf8"
777
+ // );
778
+ // ----------------------------------------------------------
779
+ // STEP 8:
780
+ // Return rollback result
781
+ // ----------------------------------------------------------
782
+ return {
783
+ success: true,
784
+ taskName,
785
+ };
786
+ }
787
+ catch (error) {
788
+ return {
789
+ success: false,
790
+ taskName,
791
+ error,
792
+ };
793
+ }
794
+ }
795
+ /**
796
+ * Rolls back all operations AFTER a target operation.
797
+ *
798
+ * ------------------------------------------------------------
799
+ * WHY THIS FUNCTION EXISTS
800
+ * ------------------------------------------------------------
801
+ *
802
+ * rollbackOperation() reverts only ONE operation.
803
+ *
804
+ * But sometimes we need to restore the project
805
+ * to an earlier stable checkpoint.
806
+ *
807
+ * Example:
808
+ *
809
+ * op1 -> createComponent
810
+ * op2 -> updateStyles
811
+ * op3 -> insertJSX
812
+ * op4 -> renameComponent
813
+ * op5 -> moveFile
814
+ *
815
+ * rollbackToOperation(op2)
816
+ *
817
+ * Result:
818
+ * - op5 reverted
819
+ * - op4 reverted
820
+ * - op3 reverted
821
+ *
822
+ * Final project state becomes exactly as it was
823
+ * after op2 execution.
824
+ *
825
+ * ------------------------------------------------------------
826
+ * IMPORTANT
827
+ * ------------------------------------------------------------
828
+ *
829
+ * Rollback must happen in REVERSE ORDER.
830
+ *
831
+ * Example:
832
+ *
833
+ * Original order:
834
+ * op1 -> op2 -> op3
835
+ *
836
+ * Rollback order:
837
+ * op3 -> op2 -> op1
838
+ *
839
+ * This is critical because later operations
840
+ * may depend on earlier mutations.
841
+ *
842
+ * ------------------------------------------------------------
843
+ * PARAMS
844
+ * ------------------------------------------------------------
845
+ *
846
+ * @param {Object} params
847
+ *
848
+ * @param {Array<Object>} params.operations
849
+ * Array of all session operations.
850
+ *
851
+ * Example:
852
+ * [
853
+ * operation1,
854
+ * operation2,
855
+ * operation3
856
+ * ]
857
+ *
858
+ * @param {string} params.targetOperationId
859
+ * Rollback checkpoint operation id.
860
+ *
861
+ * All operations AFTER this operation
862
+ * will be reverted.
863
+ *
864
+ * @param {Object} params.rollbackHandlers
865
+ * Map of rollback handlers by task name.
866
+ *
867
+ * Example:
868
+ * {
869
+ * updateStyles: fn,
870
+ * moveFile: fn
871
+ * }
872
+ *
873
+ * @param {string} params.logFilePath
874
+ * Absolute path of current session log file.
875
+ *
876
+ * Used for appending rollback history.
877
+ *
878
+ * ------------------------------------------------------------
879
+ * RETURNS
880
+ * ------------------------------------------------------------
881
+ *
882
+ * {
883
+ * success: boolean,
884
+ * rollbackCount: number,
885
+ * targetOperationId: string,
886
+ * revertedOperationIds: string[]
887
+ * }
888
+ *
889
+ */
890
+ function rollbackToOperation({ operations, targetOperationId, rollbackHandlers, logFilePath, }) {
891
+ // ----------------------------------------------------------
892
+ // STEP 1:
893
+ // Validate operations list
894
+ // ----------------------------------------------------------
895
+ if (!Array.isArray(operations)) {
896
+ throw new LoomaError(ERROR_CODES.PAYLOAD_ERROR, "operations must be an array.");
897
+ }
898
+ // ----------------------------------------------------------
899
+ // STEP 2:
900
+ // Find checkpoint operation index
901
+ // ----------------------------------------------------------
902
+ //
903
+ // Example:
904
+ //
905
+ // [
906
+ // op1,
907
+ // op2,
908
+ // op3,
909
+ // op4
910
+ // ]
911
+ //
912
+ // target = op2
913
+ //
914
+ // index = 1
915
+ //
916
+ // ----------------------------------------------------------
917
+ const checkpointIndex = operations.findIndex((operation) => operation.id === targetOperationId);
918
+ // ----------------------------------------------------------
919
+ // STEP 3:
920
+ // Ensure checkpoint exists
921
+ // ----------------------------------------------------------
922
+ if (checkpointIndex === -1) {
923
+ throw new LoomaError(ERROR_CODES.ROLLBACK_ERROR, `Target operation not found: ${targetOperationId}`);
924
+ }
925
+ // ----------------------------------------------------------
926
+ // STEP 4:
927
+ // Extract operations that need rollback
928
+ // ----------------------------------------------------------
929
+ //
930
+ // We rollback ALL operations AFTER checkpoint.
931
+ //
932
+ // Example:
933
+ //
934
+ // checkpoint = op2
935
+ //
936
+ // rollback:
937
+ // op3
938
+ // op4
939
+ // op5
940
+ //
941
+ // ----------------------------------------------------------
942
+ const operationsToRollback = operations.slice(checkpointIndex + 1);
943
+ // ----------------------------------------------------------
944
+ // STEP 5:
945
+ // Reverse rollback order
946
+ // ----------------------------------------------------------
947
+ //
948
+ // Rollback must happen backwards.
949
+ //
950
+ // Latest mutation gets reverted first.
951
+ //
952
+ // ----------------------------------------------------------
953
+ operationsToRollback.reverse();
954
+ // ----------------------------------------------------------
955
+ // STEP 6:
956
+ // Prepare rollback tracking array
957
+ // ----------------------------------------------------------
958
+ const revertedOperationIds = [];
959
+ // ----------------------------------------------------------
960
+ // STEP 7:
961
+ // Rollback operations one by one
962
+ // ----------------------------------------------------------
963
+ for (const operation of operationsToRollback) {
964
+ // --------------------------------------------------------
965
+ // Resolve rollback handler
966
+ // --------------------------------------------------------
967
+ const rollbackHandler = rollbackHandlers[operation.taskName];
968
+ // --------------------------------------------------------
969
+ // Ensure rollback handler exists
970
+ // --------------------------------------------------------
971
+ if (!rollbackHandler) {
972
+ throw new LoomaError(ERROR_CODES.FUNCTION_NOT_FOUND, `Rollback handler missing for task: ${operation.taskName}`);
973
+ }
974
+ // --------------------------------------------------------
975
+ // Execute rollback
976
+ // --------------------------------------------------------
977
+ rollbackHandler(operation);
978
+ // --------------------------------------------------------
979
+ // Store reverted operation id
980
+ // --------------------------------------------------------
981
+ revertedOperationIds.push(operation.id);
982
+ // --------------------------------------------------------
983
+ // Append rollback entry into session log
984
+ // --------------------------------------------------------
985
+ const rollbackEntry = {
986
+ type: "ROLLBACK",
987
+ rolledBackOperationId: operation.id,
988
+ taskName: operation.taskName,
989
+ timestamp: new Date().toISOString(),
990
+ };
991
+ fs.appendFileSync(logFilePath, JSON.stringify(rollbackEntry) + "\n", "utf8");
992
+ }
993
+ // ----------------------------------------------------------
994
+ // STEP 8:
995
+ // Return rollback result
996
+ // ----------------------------------------------------------
997
+ return {
998
+ success: true,
999
+ rollbackCount: revertedOperationIds.length,
1000
+ targetOperationId,
1001
+ revertedOperationIds,
1002
+ };
1003
+ }
1004
+ function rollbackAction({ operations }) {
1005
+ console.log("rolling back actions");
1006
+ }
1007
+ function getOperations(logFilePath) {
1008
+ const content = fs.readFileSync(logFilePath, "utf8");
1009
+ return content
1010
+ .split("\n")
1011
+ .filter(Boolean)
1012
+ .map((line) => JSON.parse(line));
1013
+ }
1014
+ function getLatestOperation(logFilePath) {
1015
+ const operations = getOperations(logFilePath);
1016
+ const latestOperation = operations.at(-1);
1017
+ // console.log("Latest operation: ", latestOperation);
1018
+ return latestOperation;
1019
+ }
1020
+ export default {
1021
+ createOperationLog,
1022
+ appendOperation,
1023
+ rollbackOperation,
1024
+ rollbackToOperation,
1025
+ getLatestOperation,
1026
+ undo,
1027
+ redo,
1028
+ rollbackAction,
1029
+ };
1030
+ //# sourceMappingURL=history-manager.js.map