@oisheek_c/codeforge 1.0.1

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 (141) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +7 -0
  3. package/apps/cli/bin/codeforge.cmd +2 -0
  4. package/apps/cli/src/bootstrap.js +115 -0
  5. package/apps/cli/src/cli.js +189 -0
  6. package/apps/cli/src/dev/git.js +17 -0
  7. package/apps/cli/src/dev/index.js +68 -0
  8. package/apps/cli/src/dev/provider.js +48 -0
  9. package/apps/cli/src/dev/scanner.js +14 -0
  10. package/apps/cli/src/index.js +31 -0
  11. package/bin/codeforge.js +2 -0
  12. package/package.json +63 -0
  13. package/packages/agent/contextBuilder.js +234 -0
  14. package/packages/agent/executor.js +1729 -0
  15. package/packages/agent/fallback.js +129 -0
  16. package/packages/agent/index.js +26 -0
  17. package/packages/agent/intent.js +73 -0
  18. package/packages/agent/modelSelector.js +469 -0
  19. package/packages/agent/planner.js +379 -0
  20. package/packages/agent/rag.js +412 -0
  21. package/packages/agent/ragSelector.js +570 -0
  22. package/packages/agent/router.js +121 -0
  23. package/packages/agent/thinking.js +54 -0
  24. package/packages/agent/toolCalls.js +258 -0
  25. package/packages/config/config.js +391 -0
  26. package/packages/config/defaults.js +72 -0
  27. package/packages/config/index.js +2 -0
  28. package/packages/core/approvalPolicy.js +140 -0
  29. package/packages/core/context.js +0 -0
  30. package/packages/core/message.js +0 -0
  31. package/packages/core/plan.js +0 -0
  32. package/packages/core/reasoning.js +0 -0
  33. package/packages/core/result.js +0 -0
  34. package/packages/core/retrieval.js +0 -0
  35. package/packages/core/tool.js +536 -0
  36. package/packages/git/branch.js +7 -0
  37. package/packages/git/commit.js +16 -0
  38. package/packages/git/diff.js +15 -0
  39. package/packages/git/git.js +35 -0
  40. package/packages/git/index.js +6 -0
  41. package/packages/git/remote.js +9 -0
  42. package/packages/git/status.js +11 -0
  43. package/packages/memory/index.js +0 -0
  44. package/packages/memory/project.js +0 -0
  45. package/packages/memory/session.js +0 -0
  46. package/packages/prompts/builder.js +43 -0
  47. package/packages/prompts/coder.js +38 -0
  48. package/packages/prompts/index.js +5 -0
  49. package/packages/prompts/planner.js +49 -0
  50. package/packages/prompts/reviewer.js +38 -0
  51. package/packages/prompts/system.js +94 -0
  52. package/packages/providers/index.js +31 -0
  53. package/packages/providers/openrouter.js +380 -0
  54. package/packages/reasoning/budget.js +0 -0
  55. package/packages/reasoning/chain.js +0 -0
  56. package/packages/reasoning/coder.js +0 -0
  57. package/packages/reasoning/evaluator.js +0 -0
  58. package/packages/reasoning/index.js +8 -0
  59. package/packages/reasoning/planner.js +0 -0
  60. package/packages/reasoning/reflector.js +0 -0
  61. package/packages/reasoning/reviewer.js +0 -0
  62. package/packages/reasoning/router.js +0 -0
  63. package/packages/retrieval/budget.js +92 -0
  64. package/packages/retrieval/cache.js +84 -0
  65. package/packages/retrieval/embeddings.js +147 -0
  66. package/packages/retrieval/graph.js +151 -0
  67. package/packages/retrieval/imports.js +74 -0
  68. package/packages/retrieval/index.js +9 -0
  69. package/packages/retrieval/parser/extractors/calls.js +43 -0
  70. package/packages/retrieval/parser/extractors/comments.js +49 -0
  71. package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
  72. package/packages/retrieval/parser/extractors/exports.js +71 -0
  73. package/packages/retrieval/parser/extractors/imports.js +40 -0
  74. package/packages/retrieval/parser/extractors/index.js +12 -0
  75. package/packages/retrieval/parser/extractors/metrics.js +78 -0
  76. package/packages/retrieval/parser/extractors/symbols.js +51 -0
  77. package/packages/retrieval/parser/extractors/todos.js +53 -0
  78. package/packages/retrieval/parser/helpers.js +138 -0
  79. package/packages/retrieval/parser/index.js +10 -0
  80. package/packages/retrieval/parser/languages/c.js +14 -0
  81. package/packages/retrieval/parser/languages/cpp.js +21 -0
  82. package/packages/retrieval/parser/languages/csharp.js +14 -0
  83. package/packages/retrieval/parser/languages/go.js +14 -0
  84. package/packages/retrieval/parser/languages/index.js +12 -0
  85. package/packages/retrieval/parser/languages/java.js +14 -0
  86. package/packages/retrieval/parser/languages/javascript.js +14 -0
  87. package/packages/retrieval/parser/languages/php.js +14 -0
  88. package/packages/retrieval/parser/languages/python.js +14 -0
  89. package/packages/retrieval/parser/languages/ruby.js +14 -0
  90. package/packages/retrieval/parser/languages/rust.js +14 -0
  91. package/packages/retrieval/parser/languages/swift.js +14 -0
  92. package/packages/retrieval/parser/languages/typescript.js +14 -0
  93. package/packages/retrieval/parser/languages.js +218 -0
  94. package/packages/retrieval/parser/parseFile.js +79 -0
  95. package/packages/retrieval/parser/parseRepository.js +78 -0
  96. package/packages/retrieval/parser/queries/c.js +148 -0
  97. package/packages/retrieval/parser/queries/cpp.js +198 -0
  98. package/packages/retrieval/parser/queries/csharp.js +151 -0
  99. package/packages/retrieval/parser/queries/go.js +124 -0
  100. package/packages/retrieval/parser/queries/index.js +35 -0
  101. package/packages/retrieval/parser/queries/java.js +128 -0
  102. package/packages/retrieval/parser/queries/javascript.js +181 -0
  103. package/packages/retrieval/parser/queries/php.js +140 -0
  104. package/packages/retrieval/parser/queries/python.js +110 -0
  105. package/packages/retrieval/parser/queries/ruby.js +105 -0
  106. package/packages/retrieval/parser/queries/rust.js +175 -0
  107. package/packages/retrieval/parser/queries/swift.js +134 -0
  108. package/packages/retrieval/parser/queries/typescript.js +202 -0
  109. package/packages/retrieval/parser/registry.js +107 -0
  110. package/packages/retrieval/parser/traverse.js +82 -0
  111. package/packages/retrieval/parser/types.js +239 -0
  112. package/packages/retrieval/parser.js +78 -0
  113. package/packages/retrieval/ranker.js +73 -0
  114. package/packages/retrieval/retriever.js +910 -0
  115. package/packages/retrieval/symbols.js +145 -0
  116. package/packages/scanner/detect.js +69 -0
  117. package/packages/scanner/ignore.js +12 -0
  118. package/packages/scanner/index.js +2 -0
  119. package/packages/scanner/root.js +21 -0
  120. package/packages/scanner/scanner.js +35 -0
  121. package/packages/scanner/tree.js +45 -0
  122. package/packages/terminal/activity.js +514 -0
  123. package/packages/terminal/banner.js +34 -0
  124. package/packages/terminal/colors.js +16 -0
  125. package/packages/terminal/dashboard.js +94 -0
  126. package/packages/terminal/index.js +19 -0
  127. package/packages/terminal/logger.js +43 -0
  128. package/packages/terminal/prompt.js +11 -0
  129. package/packages/terminal/renderer.js +1209 -0
  130. package/packages/terminal/spinner.js +9 -0
  131. package/packages/tools/editFile.js +261 -0
  132. package/packages/tools/filesystem.js +82 -0
  133. package/packages/tools/index.js +24 -0
  134. package/packages/tools/readFile.js +101 -0
  135. package/packages/tools/registry.js +227 -0
  136. package/packages/tools/searchFiles.js +322 -0
  137. package/packages/tools/shell.js +352 -0
  138. package/packages/tools/writeFile.js +212 -0
  139. package/packages/utils/constants.js +0 -0
  140. package/packages/utils/helpers.js +0 -0
  141. package/packages/utils/logger.js +0 -0
@@ -0,0 +1,1209 @@
1
+ import readline from "node:readline";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ colors,
6
+ } from "./colors.js";
7
+
8
+ const SPINNER_FRAMES =
9
+ Object.freeze([
10
+ "◐",
11
+ "◓",
12
+ "◑",
13
+ "◒",
14
+ ]);
15
+
16
+ const FRAME_INTERVAL_MS =
17
+ 120;
18
+
19
+ const MAX_COMPLETED_LINES =
20
+ 6;
21
+
22
+ function formatDuration(ms) {
23
+ if (!Number.isFinite(ms)) {
24
+ return "";
25
+ }
26
+
27
+ if (ms < 1000) {
28
+ return `${Math.round(ms)}ms`;
29
+ }
30
+
31
+ return `${(
32
+ ms / 1000
33
+ ).toFixed(1)}s`;
34
+ }
35
+
36
+ function formatNumber(
37
+ value
38
+ ) {
39
+ if (!Number.isFinite(value)) {
40
+ return "0";
41
+ }
42
+
43
+ return value.toLocaleString();
44
+ }
45
+
46
+ function formatCost(
47
+ value
48
+ ) {
49
+ if (!Number.isFinite(value)) {
50
+ return null;
51
+ }
52
+
53
+ if (value === 0) {
54
+ return "$0";
55
+ }
56
+
57
+ if (value < 0.000001) {
58
+ return `$${value.toFixed(
59
+ 8
60
+ )}`;
61
+ }
62
+
63
+ if (value < 0.01) {
64
+ return `$${value.toFixed(
65
+ 6
66
+ )}`;
67
+ }
68
+
69
+ return `$${value.toFixed(
70
+ 4
71
+ )}`;
72
+ }
73
+
74
+ function stripAnsi(
75
+ value
76
+ ) {
77
+ return String(
78
+ value ?? ""
79
+ ).replace(
80
+ // eslint-disable-next-line no-control-regex
81
+ /\x1B\[[0-?]*[ -/]*[@-~]/g,
82
+ ""
83
+ );
84
+ }
85
+
86
+ function truncate(
87
+ value,
88
+ maxLength
89
+ ) {
90
+ const text =
91
+ String(
92
+ value ?? ""
93
+ );
94
+
95
+ if (
96
+ stripAnsi(text).length <=
97
+ maxLength
98
+ ) {
99
+ return text;
100
+ }
101
+
102
+ return (
103
+ text.slice(
104
+ 0,
105
+ Math.max(
106
+ 1,
107
+ maxLength - 1
108
+ )
109
+ ) +
110
+ "…"
111
+ );
112
+ }
113
+
114
+ function terminalWidth() {
115
+ return Math.max(
116
+ 40,
117
+ process.stdout.columns ??
118
+ 80
119
+ );
120
+ }
121
+
122
+ function safeLine(
123
+ value
124
+ ) {
125
+ return truncate(
126
+ value,
127
+ terminalWidth() - 1
128
+ );
129
+ }
130
+
131
+ function relativePath(
132
+ filePath,
133
+ projectRoot
134
+ ) {
135
+ if (!filePath) {
136
+ return "";
137
+ }
138
+
139
+ if (!projectRoot) {
140
+ return filePath;
141
+ }
142
+
143
+ const relative =
144
+ path.relative(
145
+ projectRoot,
146
+ filePath
147
+ );
148
+
149
+ if (
150
+ !relative ||
151
+ relative.startsWith(
152
+ ".."
153
+ ) ||
154
+ path.isAbsolute(
155
+ relative
156
+ )
157
+ ) {
158
+ return filePath;
159
+ }
160
+
161
+ return relative
162
+ .split(path.sep)
163
+ .join("/");
164
+ }
165
+
166
+ function statusSymbol(
167
+ status
168
+ ) {
169
+ if (
170
+ status ===
171
+ "success"
172
+ ) {
173
+ return colors.success(
174
+ "✓"
175
+ );
176
+ }
177
+
178
+ if (
179
+ status ===
180
+ "error"
181
+ ) {
182
+ return colors.error(
183
+ "!"
184
+ );
185
+ }
186
+
187
+ if (
188
+ status ===
189
+ "skipped"
190
+ ) {
191
+ return colors.muted(
192
+ "–"
193
+ );
194
+ }
195
+
196
+ return colors.muted(
197
+ "·"
198
+ );
199
+ }
200
+
201
+ function completedLine(
202
+ item
203
+ ) {
204
+ const duration =
205
+ Number.isFinite(
206
+ item.durationMs
207
+ )
208
+ ? colors.muted(
209
+ ` · ${formatDuration(
210
+ item.durationMs
211
+ )}`
212
+ )
213
+ : "";
214
+
215
+ let detail = "";
216
+
217
+ /*
218
+ * Intent and plan details are useful.
219
+ * The route/model detail is shown in
220
+ * the live metrics area, so avoid
221
+ * producing an extremely long stage line.
222
+ */
223
+ if (
224
+ item.detail &&
225
+ [
226
+ "intent",
227
+ "plan",
228
+ "retrieve",
229
+ "fallback",
230
+ "tool",
231
+ "tool_round",
232
+ ].includes(
233
+ item.stage
234
+ )
235
+ ) {
236
+ detail =
237
+ colors.muted(
238
+ ` · ${item.detail}`
239
+ );
240
+ }
241
+
242
+ return safeLine(
243
+ ` ${statusSymbol(
244
+ item.status
245
+ )} ${item.label}${detail}${duration}`
246
+ );
247
+ }
248
+
249
+ function getActiveElapsed(
250
+ state
251
+ ) {
252
+ if (
253
+ !Number.isFinite(
254
+ state.activeStartedAt
255
+ )
256
+ ) {
257
+ return 0;
258
+ }
259
+
260
+ return (
261
+ Date.now() -
262
+ state.activeStartedAt
263
+ );
264
+ }
265
+
266
+ function activeLine(
267
+ state,
268
+ frameIndex
269
+ ) {
270
+ if (!state.activeStage) {
271
+ return null;
272
+ }
273
+
274
+ const spinner =
275
+ colors.primary(
276
+ SPINNER_FRAMES[
277
+ frameIndex %
278
+ SPINNER_FRAMES.length
279
+ ]
280
+ );
281
+
282
+ const elapsed =
283
+ colors.muted(
284
+ formatDuration(
285
+ getActiveElapsed(state)
286
+ )
287
+ );
288
+
289
+ const label =
290
+ state.activeLabel ||
291
+ "Working";
292
+
293
+ return safeLine(
294
+ ` ${spinner} ${colors.bold(label)} ${elapsed}`
295
+ );
296
+ }
297
+ function repositoryLine(
298
+ state
299
+ ) {
300
+ const results =
301
+ state.retrievedResults ??
302
+ [];
303
+
304
+ if (
305
+ results.length === 0
306
+ ) {
307
+ return null;
308
+ }
309
+
310
+ const first =
311
+ results[0];
312
+
313
+ const firstPath =
314
+ relativePath(
315
+ first.path,
316
+ state.projectRoot
317
+ );
318
+
319
+ const extra =
320
+ results.length > 1
321
+ ? ` +${results.length - 1}`
322
+ : "";
323
+
324
+ return safeLine(
325
+ ` ${colors.muted(
326
+ "Repository"
327
+ )} ${firstPath}${extra}`
328
+ );
329
+ }
330
+
331
+ function contextLine(
332
+ state
333
+ ) {
334
+ const metrics =
335
+ state.contextMetrics;
336
+
337
+ if (
338
+ !Array.isArray(metrics) ||
339
+ metrics.length === 0
340
+ ) {
341
+ return null;
342
+ }
343
+
344
+ const latest =
345
+ metrics[
346
+ metrics.length - 1
347
+ ];
348
+
349
+ const total =
350
+ latest
351
+ ?.estimatedTokens
352
+ ?.total;
353
+
354
+ if (
355
+ !Number.isFinite(total)
356
+ ) {
357
+ return null;
358
+ }
359
+
360
+ return safeLine(
361
+ ` ${colors.muted(
362
+ "Context"
363
+ )} ~${formatNumber(
364
+ total
365
+ )} tokens`
366
+ );
367
+ }
368
+
369
+ function modelLine(
370
+ state
371
+ ) {
372
+ if (
373
+ !state.model &&
374
+ !state.provider
375
+ ) {
376
+ return null;
377
+ }
378
+
379
+ const route = [
380
+ state.provider,
381
+ state.model,
382
+ ]
383
+ .filter(Boolean)
384
+ .join(" · ");
385
+
386
+ return safeLine(
387
+ ` ${colors.muted(
388
+ "Model"
389
+ )} ${route}`
390
+ );
391
+ }
392
+
393
+ function attemptLine(
394
+ state
395
+ ) {
396
+ if (
397
+ !Number.isFinite(
398
+ state.attempt
399
+ ) &&
400
+ !Number.isFinite(
401
+ state.attempts
402
+ )
403
+ ) {
404
+ return null;
405
+ }
406
+
407
+ const current =
408
+ Number.isFinite(
409
+ state.attempts
410
+ )
411
+ ? state.attempts
412
+ : state.attempt;
413
+
414
+ const maximum =
415
+ Number.isFinite(
416
+ state.maxAttempts
417
+ )
418
+ ? `/${state.maxAttempts}`
419
+ : "";
420
+
421
+ return (
422
+ ` ${colors.muted(
423
+ "Attempt"
424
+ )} ${current}${maximum}`
425
+ );
426
+ }
427
+
428
+ function usageLine(
429
+ state
430
+ ) {
431
+ const usage =
432
+ state.usage ?? {};
433
+
434
+ if (
435
+ !usage.totalTokens &&
436
+ usage.cost == null
437
+ ) {
438
+ return null;
439
+ }
440
+
441
+ const parts = [];
442
+
443
+ if (
444
+ Number.isFinite(
445
+ usage.totalTokens
446
+ ) &&
447
+ usage.totalTokens > 0
448
+ ) {
449
+ parts.push(
450
+ `${formatNumber(
451
+ usage.totalTokens
452
+ )} tokens`
453
+ );
454
+ }
455
+
456
+ const cost =
457
+ formatCost(
458
+ usage.cost
459
+ );
460
+
461
+ if (cost) {
462
+ parts.push(cost);
463
+ }
464
+
465
+ if (
466
+ parts.length === 0
467
+ ) {
468
+ return null;
469
+ }
470
+
471
+ return safeLine(
472
+ ` ${colors.muted(
473
+ "Usage"
474
+ )} ${parts.join(
475
+ " · "
476
+ )}`
477
+ );
478
+ }
479
+
480
+ function buildLines(
481
+ state,
482
+ frameIndex
483
+ ) {
484
+ const lines = [];
485
+
486
+ lines.push(
487
+ safeLine(
488
+ `${colors.bold(
489
+ colors.primary("✦ CodeForge")
490
+ )} ${colors.muted("· Agent Activity")}`
491
+ )
492
+ );
493
+
494
+ lines.push(
495
+ safeLine(
496
+ `${colors.muted("─".repeat(
497
+ Math.min(terminalWidth() - 1, 64)
498
+ ))}`
499
+ )
500
+ );
501
+
502
+ lines.push("");
503
+
504
+ const completed =
505
+ state.completedStages
506
+ .filter(
507
+ (item) =>
508
+ item.stage !==
509
+ "complete"
510
+ )
511
+ .slice(
512
+ -MAX_COMPLETED_LINES
513
+ );
514
+
515
+ for (
516
+ const item of completed
517
+ ) {
518
+ lines.push(
519
+ completedLine(
520
+ item
521
+ )
522
+ );
523
+ }
524
+
525
+ const active =
526
+ activeLine(
527
+ state,
528
+ frameIndex
529
+ );
530
+
531
+ if (active) {
532
+ lines.push(
533
+ active
534
+ );
535
+ }
536
+
537
+ const metrics = [
538
+ repositoryLine(
539
+ state
540
+ ),
541
+ contextLine(
542
+ state
543
+ ),
544
+ modelLine(
545
+ state
546
+ ),
547
+ attemptLine(
548
+ state
549
+ ),
550
+ usageLine(
551
+ state
552
+ ),
553
+ ].filter(Boolean);
554
+
555
+ if (
556
+ metrics.length > 0
557
+ ) {
558
+ lines.push("");
559
+
560
+ for (
561
+ const line of metrics
562
+ ) {
563
+ lines.push(line);
564
+ }
565
+ }
566
+
567
+ return lines;
568
+ }
569
+
570
+ function clearRenderedLines(
571
+ count
572
+ ) {
573
+ if (
574
+ count <= 0 ||
575
+ !process.stdout.isTTY
576
+ ) {
577
+ return;
578
+ }
579
+
580
+ /*
581
+ * draw() leaves the cursor one line below
582
+ * the rendered dashboard because the frame
583
+ * ends with a newline.
584
+ *
585
+ * Move back to the beginning of the block.
586
+ */
587
+ readline.moveCursor(
588
+ process.stdout,
589
+ 0,
590
+ -count
591
+ );
592
+
593
+ /*
594
+ * Clear the dashboard and everything below
595
+ * it. The next draw starts from exactly the
596
+ * same terminal position.
597
+ */
598
+ readline.clearScreenDown(
599
+ process.stdout
600
+ );
601
+
602
+ readline.cursorTo(
603
+ process.stdout,
604
+ 0
605
+ );
606
+ }
607
+
608
+ function finalRepositoryLines(
609
+ state
610
+ ) {
611
+ const results =
612
+ state.retrievedResults ?? [];
613
+
614
+ if (results.length === 0) {
615
+ return [];
616
+ }
617
+
618
+ const lines = [
619
+ colors.bold(
620
+ "Repository Context"
621
+ ),
622
+ ];
623
+
624
+ for (
625
+ let index = 0;
626
+ index < results.length;
627
+ index += 1
628
+ ) {
629
+ const result =
630
+ results[index];
631
+
632
+ const filePath =
633
+ relativePath(
634
+ result.path,
635
+ state.projectRoot
636
+ );
637
+
638
+ const metadata = [];
639
+
640
+ if (result.reason) {
641
+ metadata.push(
642
+ result.reason
643
+ );
644
+ }
645
+
646
+ if (
647
+ Number.isFinite(
648
+ result.score
649
+ )
650
+ ) {
651
+ metadata.push(
652
+ `score ${result.score}`
653
+ );
654
+ }
655
+
656
+ lines.push(
657
+ safeLine(
658
+ `${String(
659
+ index + 1
660
+ ).padStart(
661
+ 2,
662
+ "0"
663
+ )} ${filePath}${
664
+ metadata.length > 0
665
+ ? ` ${colors.muted(
666
+ metadata.join(
667
+ " · "
668
+ )
669
+ )}`
670
+ : ""
671
+ }`
672
+ )
673
+ );
674
+ }
675
+
676
+ return lines;
677
+ }
678
+
679
+ function finalContextLines(
680
+ state
681
+ ) {
682
+ const metrics =
683
+ state.contextMetrics;
684
+
685
+ if (
686
+ !Array.isArray(metrics) ||
687
+ metrics.length === 0
688
+ ) {
689
+ return [];
690
+ }
691
+
692
+ const lines = [
693
+ colors.bold(
694
+ "Context Usage"
695
+ ),
696
+ ];
697
+
698
+ for (
699
+ const metric of metrics
700
+ ) {
701
+ const estimated =
702
+ metric?.estimatedTokens ??
703
+ {};
704
+
705
+ const round =
706
+ Number.isFinite(
707
+ metric?.toolRound
708
+ )
709
+ ? metric.toolRound
710
+ : 0;
711
+
712
+ lines.push(
713
+ `Round ${round}`
714
+ );
715
+
716
+ const entries = [
717
+ [
718
+ "System",
719
+ estimated.system,
720
+ ],
721
+ [
722
+ "User/RAG",
723
+ estimated.user,
724
+ ],
725
+ [
726
+ "Assistant",
727
+ estimated.assistant,
728
+ ],
729
+ [
730
+ "Tool Results",
731
+ estimated.toolResults,
732
+ ],
733
+ [
734
+ "Tool Schemas",
735
+ estimated.toolSchemas,
736
+ ],
737
+ [
738
+ "Estimated",
739
+ estimated.total,
740
+ ],
741
+ ];
742
+
743
+ for (
744
+ const [
745
+ label,
746
+ value,
747
+ ] of entries
748
+ ) {
749
+ if (
750
+ !Number.isFinite(
751
+ value
752
+ )
753
+ ) {
754
+ continue;
755
+ }
756
+
757
+ lines.push(
758
+ ` ${label.padEnd(
759
+ 12
760
+ )} ~${formatNumber(
761
+ value
762
+ )} tokens`
763
+ );
764
+ }
765
+ }
766
+
767
+ return lines;
768
+ }
769
+
770
+ function finalModelLines(
771
+ state
772
+ ) {
773
+ const usage =
774
+ state.usage ?? {};
775
+
776
+ const lines = [
777
+ colors.bold(
778
+ "Model Usage"
779
+ ),
780
+ ];
781
+
782
+ const route = [
783
+ state.provider,
784
+ state.model,
785
+ ]
786
+ .filter(Boolean)
787
+ .join(" · ");
788
+
789
+ if (route) {
790
+ lines.push(
791
+ `Route ${route}`
792
+ );
793
+ }
794
+
795
+ const attempts =
796
+ Number.isFinite(
797
+ state.attempts
798
+ )
799
+ ? state.attempts
800
+ : state.attempt;
801
+
802
+ if (
803
+ Number.isFinite(
804
+ attempts
805
+ )
806
+ ) {
807
+ lines.push(
808
+ `Attempts ${attempts}`
809
+ );
810
+ }
811
+
812
+ const usageEntries = [
813
+ [
814
+ "Prompt",
815
+ usage.promptTokens,
816
+ ],
817
+ [
818
+ "Completion",
819
+ usage.completionTokens,
820
+ ],
821
+ [
822
+ "Reasoning",
823
+ usage.reasoningTokens,
824
+ ],
825
+ [
826
+ "Total",
827
+ usage.totalTokens,
828
+ ],
829
+ ];
830
+
831
+ for (
832
+ const [
833
+ label,
834
+ value,
835
+ ] of usageEntries
836
+ ) {
837
+ if (
838
+ !Number.isFinite(
839
+ value
840
+ )
841
+ ) {
842
+ continue;
843
+ }
844
+
845
+ lines.push(
846
+ `${label.padEnd(
847
+ 12
848
+ )}${formatNumber(
849
+ value
850
+ )} tokens`
851
+ );
852
+ }
853
+
854
+ const cost =
855
+ formatCost(
856
+ usage.cost
857
+ );
858
+
859
+ if (cost) {
860
+ lines.push(
861
+ `Cost ${cost}`
862
+ );
863
+ }
864
+
865
+ if (
866
+ state.finishReason
867
+ ) {
868
+ lines.push(
869
+ `Finish ${state.finishReason}`
870
+ );
871
+ }
872
+ return lines;
873
+ }
874
+
875
+ function finalStageLines(
876
+ state
877
+ ) {
878
+ const completed =
879
+ state.completedStages ?? [];
880
+
881
+ if (
882
+ !Array.isArray(completed) ||
883
+ completed.length === 0
884
+ ) {
885
+ return [];
886
+ }
887
+
888
+ return completed
889
+ .filter(
890
+ (item) =>
891
+ item.stage !==
892
+ "complete"
893
+ )
894
+ .map(
895
+ (item) =>
896
+ completedLine(item)
897
+ );
898
+ }
899
+
900
+ function printFinalSummary(
901
+ state
902
+ ) {
903
+ if (!state) {
904
+ return;
905
+ }
906
+
907
+ const elapsed =
908
+ Number.isFinite(
909
+ state.startedAt
910
+ )
911
+ ? Date.now() -
912
+ state.startedAt
913
+ : null;
914
+
915
+ if (state.failed) {
916
+ console.log(
917
+ safeLine(
918
+ `${colors.error(
919
+ "✕"
920
+ )} ${colors.bold(
921
+ colors.error(
922
+ "CodeForge failed"
923
+ )
924
+ )}${
925
+ Number.isFinite(
926
+ elapsed
927
+ )
928
+ ? colors.muted(
929
+ ` · ${formatDuration(
930
+ elapsed
931
+ )}`
932
+ )
933
+ : ""
934
+ }`
935
+ )
936
+ );
937
+
938
+ if (state.lastError) {
939
+ console.log(
940
+ safeLine(
941
+ ` ${colors.muted(
942
+ state.lastError
943
+ )}`
944
+ )
945
+ );
946
+ }
947
+
948
+ return;
949
+ }
950
+
951
+ const stageLines =
952
+ finalStageLines(
953
+ state
954
+ );
955
+
956
+ for (
957
+ const line of stageLines
958
+ ) {
959
+ console.log(line);
960
+ }
961
+
962
+ if (
963
+ stageLines.length > 0
964
+ ) {
965
+ console.log("");
966
+ }
967
+
968
+ console.log(
969
+ safeLine(
970
+ `${colors.success(
971
+ "✓"
972
+ )} ${colors.bold(
973
+ colors.primary(
974
+ "CodeForge completed"
975
+ )
976
+ )}${
977
+ Number.isFinite(
978
+ elapsed
979
+ )
980
+ ? colors.muted(
981
+ ` · ${formatDuration(
982
+ elapsed
983
+ )}`
984
+ )
985
+ : ""
986
+ }`
987
+ )
988
+ );
989
+
990
+ console.log(
991
+ safeLine(
992
+ colors.muted(
993
+ " Execution finished successfully."
994
+ )
995
+ )
996
+ );
997
+
998
+ const sections = [
999
+ finalRepositoryLines(
1000
+ state
1001
+ ),
1002
+ finalContextLines(
1003
+ state
1004
+ ),
1005
+ finalModelLines(
1006
+ state
1007
+ ),
1008
+ ].filter(
1009
+ (section) =>
1010
+ section.length > 1
1011
+ );
1012
+
1013
+ for (
1014
+ const section of sections
1015
+ ) {
1016
+ console.log("");
1017
+
1018
+ for (
1019
+ const line of section
1020
+ ) {
1021
+ console.log(line);
1022
+ }
1023
+ }
1024
+
1025
+ console.log("");
1026
+ }
1027
+
1028
+ export function createLiveRenderer({
1029
+ enabled =
1030
+ process.stdout.isTTY,
1031
+ intervalMs =
1032
+ FRAME_INTERVAL_MS,
1033
+ } = {}) {
1034
+ const interactive =
1035
+ Boolean(enabled);
1036
+
1037
+ let state = null;
1038
+
1039
+ let timer = null;
1040
+ let frameIndex = 0;
1041
+ let renderedLineCount = 0;
1042
+ let suspended = false;
1043
+
1044
+ function draw() {
1045
+ if (
1046
+ !interactive ||
1047
+ suspended ||
1048
+ !state
1049
+ ) {
1050
+ return;
1051
+ }
1052
+
1053
+ clearRenderedLines(
1054
+ renderedLineCount
1055
+ );
1056
+
1057
+ const lines =
1058
+ buildLines(
1059
+ state,
1060
+ frameIndex
1061
+ );
1062
+
1063
+ process.stdout.write(
1064
+ `${lines.join(
1065
+ "\n"
1066
+ )}\n`
1067
+ );
1068
+
1069
+ renderedLineCount =
1070
+ lines.length;
1071
+
1072
+ frameIndex += 1;
1073
+ }
1074
+
1075
+ function start() {
1076
+ if (
1077
+ !interactive ||
1078
+ timer
1079
+ ) {
1080
+ return;
1081
+ }
1082
+
1083
+ timer =
1084
+ setInterval(
1085
+ draw,
1086
+ intervalMs
1087
+ );
1088
+
1089
+ /*
1090
+ * Do not keep Node alive solely
1091
+ * for terminal animation.
1092
+ */
1093
+ timer.unref?.();
1094
+ }
1095
+
1096
+ function stopTimer() {
1097
+ if (!timer) {
1098
+ return;
1099
+ }
1100
+
1101
+ clearInterval(
1102
+ timer
1103
+ );
1104
+
1105
+ timer = null;
1106
+ }
1107
+
1108
+ function setState(
1109
+ nextState
1110
+ ) {
1111
+ state =
1112
+ nextState;
1113
+
1114
+ if (
1115
+ interactive &&
1116
+ !suspended
1117
+ ) {
1118
+ draw();
1119
+ start();
1120
+ }
1121
+ }
1122
+
1123
+ function suspend() {
1124
+ if (!interactive) {
1125
+ return;
1126
+ }
1127
+
1128
+ stopTimer();
1129
+
1130
+ clearRenderedLines(
1131
+ renderedLineCount
1132
+ );
1133
+
1134
+ renderedLineCount =
1135
+ 0;
1136
+
1137
+ suspended = true;
1138
+ }
1139
+
1140
+ function resume() {
1141
+ if (
1142
+ !interactive ||
1143
+ !suspended
1144
+ ) {
1145
+ return;
1146
+ }
1147
+
1148
+ suspended = false;
1149
+
1150
+ draw();
1151
+ start();
1152
+ }
1153
+
1154
+ function finish() {
1155
+ if (!interactive) {
1156
+ return;
1157
+ }
1158
+
1159
+ stopTimer();
1160
+
1161
+ /*
1162
+ * Remove only the animated/live dashboard.
1163
+ *
1164
+ * Its information is immediately replaced
1165
+ * by a persistent execution summary below.
1166
+ */
1167
+ clearRenderedLines(
1168
+ renderedLineCount
1169
+ );
1170
+
1171
+ renderedLineCount =
1172
+ 0;
1173
+
1174
+ printFinalSummary(
1175
+ state
1176
+ );
1177
+ }
1178
+
1179
+ function dispose() {
1180
+ stopTimer();
1181
+
1182
+ if (
1183
+ interactive &&
1184
+ renderedLineCount > 0
1185
+ ) {
1186
+ clearRenderedLines(
1187
+ renderedLineCount
1188
+ );
1189
+ }
1190
+
1191
+ renderedLineCount =
1192
+ 0;
1193
+
1194
+ state = null;
1195
+ }
1196
+
1197
+ return {
1198
+ enabled: interactive,
1199
+
1200
+ setState,
1201
+ draw,
1202
+
1203
+ suspend,
1204
+ resume,
1205
+
1206
+ finish,
1207
+ dispose,
1208
+ };
1209
+ }