-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathARCHITECTURE.html
More file actions
1248 lines (1169 loc) · 112 KB
/
Copy pathARCHITECTURE.html
File metadata and controls
1248 lines (1169 loc) · 112 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="id" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>Minicode — Peta Struktur untuk Developer</title>
<!-- Ikon: Material Symbols via CDN Google. Hanya untuk dok statis ini (bukan runtime CLI, jadi tidak melanggar postur zero-dep);
bila offline, JS menyembunyikan ikon agar tidak tampil teks ligatur mentah. -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200">
<!-- Tipografi: Inter untuk teks (fallback stack sistem bila offline); mono sistem untuk kode/pembeda. -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap">
<style>
/* ── Token ala GitBook: terang lembut + gelap pekat, satu file tanpa dependensi ── */
:root {
--bg: #ffffff;
--bg-subtle: #f7f7f5;
--sidebar-bg: #fbfbfa;
--code-bg: #0d1117;
--code-fg: #e6edf3;
--ink: #1d1d1f;
--mut: #5f6368;
--faint: #8a8f98;
--line: #e8e8e6;
--line-soft: #f0f0ee;
--acc: #346ddb;
--acc-ink: #2549a8;
--acc-soft: #edf2fd;
--ok: #1a7f4b;
--ok-soft: #e9f6ee;
--warn: #9a5b00;
--warn-soft: #fdf3e3;
--err: #c0362c;
--err-soft: #fdecea;
--pur: #6a4fd0;
--pur-soft: #efecfc;
--card-shadow: none;
--radius: 8px;
--mono: ui-monospace, "SF Mono", "Cascadia Code", Consolas, "JetBrains Mono", monospace;
--sans: "Inter", ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--topbar-h: 60px;
--side-w: 284px;
--toc-w: 232px;
}
html[data-theme="dark"] {
--bg: #1b1b1d;
--bg-subtle: #131315;
--sidebar-bg: #161618;
--code-bg: #0d1117;
--code-fg: #e6edf3;
--ink: #ececee;
--mut: #a7abb3;
--faint: #7c818a;
--line: #2b2b2e;
--line-soft: #232326;
--acc: #6b9bff;
--acc-ink: #a9c4ff;
--acc-soft: #22304f;
--ok: #4cc38a;
--ok-soft: #16382a;
--warn: #e8a13d;
--warn-soft: #3d2c14;
--err: #f26d5f;
--err-soft: #42201c;
--pur: #a893ff;
--pur-soft: #2c2650;
--card-shadow: none;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
background: var(--bg); color: var(--ink);
font: 15px/1.7 var(--sans);
-webkit-font-smoothing: antialiased;
}
::selection { background: rgba(52, 109, 219, 0.22); }
code, .mono { font-family: var(--mono); }
code {
background: var(--bg-subtle); color: var(--ink);
padding: 0.08em 0.34em; border-radius: 6px; font-size: 0.83em; overflow-wrap: anywhere;
}
pre code { background: none; border: 0; padding: 0; color: inherit; }
a { color: var(--acc); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }
:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; border-radius: 4px; }
.skip {
position: absolute; left: -9999px; top: 0; z-index: 200;
background: var(--acc); color: #fff; padding: 8px 14px; border-radius: 0 0 8px 0;
}
.skip:focus { left: 0; color: #fff; }
/* ── Ikon Material Symbols: ligatur font, bukan emoji (konsisten di semua OS) ── */
.mi {
font-family: "Material Symbols Outlined";
font-weight: normal; font-style: normal; line-height: 1;
letter-spacing: normal; text-transform: none; white-space: nowrap;
direction: ltr; -webkit-font-smoothing: antialiased;
display: inline-block; font-size: 18px; vertical-align: -3px; user-select: none;
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24;
}
/* Sembunyikan ikon bila font gagal dimuat (offline) — semuanya dekoratif. */
body.no-mi .mi { display: none; }
.mark .mi { font-size: 19px; vertical-align: 0; }
.hint .ico .mi { font-size: 20px; }
.search-pill .mi, .side-search .mag .mi { font-size: 17px; vertical-align: 0; }
.snav .mi { font-size: 15px; }
.codehead button { display: inline-flex; align-items: center; gap: 6px; }
.codehead .mi { font-size: 14px; vertical-align: 0; }
/* ── Scrollbar minimalis: tipis, tanpa trek, hanya jempol samar ── */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--line); border-radius: 8px; }
*::-webkit-scrollbar-thumb:hover { background: var(--faint); }
/* ══ Topbar ala GitBook ══ */
.topbar {
position: sticky; top: 0; z-index: 100;
height: var(--topbar-h);
display: flex; align-items: center; gap: 12px;
padding: 0 20px;
background: var(--bg);
}
.burger {
display: none; background: var(--bg-subtle);
color: var(--mut); border-radius: 8px; padding: 6px 10px; cursor: pointer; font-size: 15px;
}
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.mark {
width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
background: var(--ink);
color: var(--bg); font-weight: 800; font-size: 17px;
display: flex; align-items: center; justify-content: center;
}
.brand .name { font-size: 15px; font-weight: 750; letter-spacing: -0.2px; white-space: nowrap; }
.brand .name b { color: var(--acc); font-weight: 800; }
.tag-docs { font-size: 11px; font-weight: 600; color: var(--faint); white-space: nowrap; }
.search-pill {
flex: 1; max-width: 460px; margin: 0 auto;
display: flex; align-items: center; gap: 8px;
border-radius: 9px;
background: var(--bg-subtle); color: var(--faint);
padding: 7px 12px; font-size: 13px; cursor: text;
}
.search-pill:hover { background: var(--line-soft); }
.search-pill kbd { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--faint); }
.top-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.vpill { font-size: 11.5px; font-family: var(--mono); color: var(--faint); white-space: nowrap; }
.iconbtn {
background: var(--bg-subtle); color: var(--mut);
border-radius: 8px; padding: 6px 10px; cursor: pointer; font-size: 14px; line-height: 1;
}
.iconbtn:hover { color: var(--acc); }
/* ══ Kerangka 3 kolom ══ */
.shell {
display: grid;
grid-template-columns: var(--side-w) minmax(0, 1fr) var(--toc-w);
align-items: start;
max-width: 1480px; margin: 0 auto;
}
/* ── Sidebar kiri ── */
.sidebar {
position: sticky; top: var(--topbar-h);
height: calc(100vh - var(--topbar-h));
overflow-y: auto; overscroll-behavior: contain;
background: var(--sidebar-bg);
padding: 18px 14px 60px;
}
.side-search { position: relative; margin: 2px 2px 12px; }
.side-search input {
width: 100%; border-radius: 8px;
background: var(--bg); color: var(--ink);
padding: 8px 12px 8px 32px; font-size: 13px; font-family: var(--sans);
}
.side-search input::placeholder { color: var(--faint); }
.side-search input:focus { outline: 2px solid var(--acc); outline-offset: -1px; }
.side-search .mag { position: absolute; left: 11px; top: 8px; color: var(--faint); font-size: 14px; }
.side-group { margin-bottom: 6px; }
.side-group > summary {
list-style: none; cursor: pointer; user-select: none;
font-size: 11px; font-weight: 750; letter-spacing: 0.9px; text-transform: uppercase;
color: var(--faint); padding: 14px 10px 6px;
display: flex; align-items: center; gap: 6px;
}
.side-group > summary::-webkit-details-marker { display: none; }
.side-group > summary::after { content: ""; width: 6px; height: 6px; margin-left: auto; border-right: 1.5px solid var(--faint); border-bottom: 1.5px solid var(--faint); transform: rotate(45deg); flex: 0 0 auto; align-self: center; }
.side-group:not([open]) > summary::after { transform: rotate(-45deg); }
.snav { display: block; padding: 5px 10px; margin: 1px 0; font-size: 13.5px; color: var(--mut); border-radius: 7px; }
.snav:hover { background: var(--line-soft); color: var(--ink); text-decoration: none; }
.snav.active { background: var(--acc-soft); color: var(--acc-ink); font-weight: 650; }
html[data-theme="light"] .snav.active { color: var(--acc); }
.snav.sub { padding-left: 28px; font-size: 12.5px; position: relative; }
.snav.sub::before { content: "·"; position: absolute; left: 16px; color: var(--acc); font-weight: 700; }
.snav .cnt { float: right; font-size: 10.5px; font-family: var(--mono); color: var(--faint); }
.snav.frozen { color: var(--pur); }
.snav[hidden] { display: none; }
.side-foot { margin: 16px 10px 0; font-size: 12px; color: var(--faint); }
.side-foot b { color: var(--mut); }
/* ── Konten tengah ── */
.content { padding: 40px 48px 80px; min-width: 0; }
.content-inner { max-width: 800px; margin: 0 auto; }
.crumb { font-size: 12.5px; color: var(--faint); margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
.crumb b { color: var(--ink); font-weight: 650; }
.crumb .sep { opacity: 0.5; }
h1 { font-size: 30px; line-height: 1.2; font-weight: 700; letter-spacing: -0.02em; text-wrap: balance; margin: 2px 0 10px; }
.lede { color: var(--mut); margin: 8px 0 18px; font-size: 15.5px; }
section { margin-bottom: 12px; scroll-margin-top: calc(var(--topbar-h) + 18px); }
h2.sec {
font-size: 21px; font-weight: 700; letter-spacing: -0.01em;
display: flex; align-items: baseline; gap: 10px;
margin: 54px 0 8px;
}
h2.sec .hn { font-size: 11.5px; font-weight: 700; color: var(--faint); letter-spacing: 1.2px; font-family: var(--mono); }
h2.sec .anchor { opacity: 0; font-size: 15px; margin-left: 2px; }
h2.sec:hover .anchor { opacity: 1; }
h3.hsub { font-size: 16px; font-weight: 700; margin: 28px 0 8px; scroll-margin-top: calc(var(--topbar-h) + 18px); }
.meta { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 22px; }
.pill {
font-size: 12px; color: var(--mut);
display: inline-flex; align-items: center; gap: 6px;
}
.meta .pill + .pill::before { content: "·"; color: var(--faint); }
.pill.blue, .pill.green { color: var(--mut); }
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--faint); }
/* ── Hint/callout ala GitBook ── */
.hint {
display: flex; gap: 12px; padding: 12px 16px; margin: 14px 0;
border-radius: var(--radius);
background: var(--bg-subtle); font-size: 13.5px; color: var(--mut);
}
.hint .ico { flex: 0 0 auto; font-size: 16px; line-height: 1.5; }
.hint b, .hint strong { color: var(--ink); }
.hint .ico { color: var(--faint); }
.hint-info { background: var(--acc-soft); }
.hint-warn { background: var(--warn-soft); }
.hint-danger { background: var(--err-soft); }
.hint-ok { background: var(--ok-soft); }
.hint-purple { background: var(--pur-soft); }
.hint .body { min-width: 0; }
.hint .title { display: block; font-weight: 700; color: var(--ink); font-size: 13.5px; margin-bottom: 2px; }
/* ── Kartu lapisan ── */
.card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 16px 0 8px; }
.card {
border-radius: var(--radius);
background: var(--bg-subtle); padding: 16px;
}
.card .k { font-size: 11px; font-weight: 800; font-family: var(--mono); letter-spacing: 0.6px; }
.card h3 { font-size: 15px; margin: 4px 0; }
.card p { font-size: 13px; color: var(--mut); }
.card code { font-size: 11.5px; }
.k1, .k2, .k3 { color: var(--faint); }
.layer-stack { margin: 14px 0; display: grid; gap: 10px; }
.layer { display: grid; grid-template-columns: 52px 1fr; gap: 2px 16px; padding: 18px 20px; background: var(--bg-subtle); border-radius: var(--radius); }
.layer .lnum { font-size: 20px; font-weight: 800; color: var(--faint); font-variant-numeric: tabular-nums; }
.layer .ltitle { font-weight: 700; font-size: 15px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.layer .lpath { font-size: 12px; color: var(--faint); font-family: var(--mono); }
.layer .ldesc { grid-column: 2; font-size: 13.5px; color: var(--mut); }
.chip { font-size: 10.5px; font-weight: 700; font-family: var(--mono); color: var(--faint); }
.chip.c1, .chip.c2, .chip.c3, .chip.cx { color: var(--faint); }
.flow-note { text-align: center; font-size: 11.5px; color: var(--faint); padding: 2px; }
/* ── Tabel ala GitBook ── */
.tscroll { overflow-x: auto; margin: 12px 0; }
table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13.5px; }
.tscroll table { min-width: 640px; }
th, td { text-align: left; padding: 9px 14px; vertical-align: top; }
thead th, tr:first-child th {
background: none; color: var(--ink);
font-weight: 650; font-size: 13px;
white-space: nowrap;
}
/* Zebra flat + hover tint sebagai pengganti garis sel */
tbody tr:nth-child(even) td { background: var(--bg-subtle); }
tbody tr:nth-child(even) td:first-child { border-radius: 8px 0 0 8px; }
tbody tr:nth-child(even) td:last-child { border-radius: 0 8px 8px 0; }
tbody tr:hover td { background: var(--acc-soft); }
tbody tr:hover td:first-child { border-radius: 8px 0 0 8px; }
tbody tr:hover td:last-child { border-radius: 0 8px 8px 0; }
table.matrix td { text-align: center; }
table.matrix td:first-child, table.matrix th:first-child { text-align: left; }
.ok { color: var(--ok); font-weight: 650; }
.no { color: var(--err); font-weight: 650; }
.na { color: var(--faint); }
td small { color: var(--faint); display: block; }
/* ── Kerapian teks: cegah baris yatim, angka tabel rata kolom ── */
.lede, .step .what, .mod .fdesc, .uxcard p, ul.plain li, .hint .body { text-wrap: pretty; }
h3.hsub { text-wrap: balance; }
table { font-variant-numeric: tabular-nums; }
ul.plain { list-style: none; margin: 10px 0; }
ul.plain li { position: relative; padding: 0 0 10px 22px; font-size: 13.5px; color: var(--mut); }
ul.plain li::before { content: "›"; position: absolute; left: 6px; color: var(--acc); font-weight: 800; }
ul.plain li b { color: var(--ink); }
/* ── Steps ── */
.steps { margin: 14px 0; display: grid; gap: 10px; }
.step { display: flex; gap: 14px; padding: 16px 18px; background: var(--bg-subtle); border-radius: var(--radius); }
.step .dot {
flex: 0 0 28px; color: var(--faint); font-size: 13px; font-weight: 700;
display: flex; align-items: flex-start; justify-content: center; padding-top: 2px;
}
.step .card-b b { font-size: 14.5px; }
.step .where { font-size: 12px; color: var(--faint); font-family: var(--mono); display: block; margin-top: 2px; overflow-wrap: anywhere; }
.step .what { font-size: 13.5px; color: var(--mut); margin-top: 3px; }
/* ── Kelompok modul ── */
.toolbar { display: flex; gap: 8px; margin: 16px 0; }
.toolbar button {
background: var(--bg-subtle); color: var(--mut); border-radius: 8px;
padding: 7px 14px; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.toolbar button:hover { color: var(--acc); }
details.group {
margin: 0 0 10px; border-radius: var(--radius);
background: var(--bg-subtle);
scroll-margin-top: calc(var(--topbar-h) + 18px); overflow: hidden;
}
details.group.flash { background: var(--acc-soft); }
details.group[hidden] { display: none; }
details.group summary {
cursor: pointer; list-style: none; padding: 14px 16px; user-select: none;
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
font-size: 14.5px; font-weight: 700;
}
details.group summary::-webkit-details-marker { display: none; }
details.group summary::after { content: ""; width: 7px; height: 7px; margin-left: auto; border-right: 1.5px solid var(--faint); border-bottom: 1.5px solid var(--faint); transform: rotate(45deg); flex: 0 0 auto; align-self: center; }
details.group[open] summary::after { transform: rotate(225deg); }
details.group summary .role { color: var(--faint); font-size: 12px; font-weight: 400; }
details.group summary .cnt { font-size: 11px; font-family: var(--mono); color: var(--faint); }
.mods { padding: 4px 18px 10px; }
.mod { padding: 16px 0; }
.mod + .mod { margin-top: 4px; }
.mod[hidden] { display: none; }
.mod .fname { font-size: 13px; font-weight: 700; font-family: var(--mono); color: var(--ink); overflow-wrap: anywhere; }
.mod .fdesc { font-size: 13.5px; color: var(--mut); margin-top: 3px; }
.mod .exports { font-size: 12px; color: var(--mut); margin-top: 6px; font-family: var(--mono); overflow-wrap: anywhere; }
.mod .exports::before { content: "⇢ "; font-weight: 700; }
/* ── Resep ── */
.recipes { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 14px 0; }
.recipe { border-radius: var(--radius); padding: 18px; background: var(--bg-subtle); }
.recipe h3 { font-size: 14.5px; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.recipe ol { margin: 0 0 0 20px; font-size: 13.5px; color: var(--mut); }
.recipe ol li { margin-bottom: 5px; }
.recipe .guard { margin-top: 10px; font-size: 12.5px; color: var(--mut); background: var(--warn-soft); border-radius: 7px; padding: 8px 12px; }
/* ── Blok kode ala GitBook ── */
.codeblock { margin: 12px 0; border-radius: var(--radius); overflow: hidden; }
.codehead {
display: flex; align-items: center; gap: 8px;
background: var(--code-bg); color: #8b949e; font-size: 11.5px; font-family: var(--mono);
padding: 12px 16px 0;
}
.codehead .fname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.codehead button {
margin-left: auto; background: none; border: 0; color: var(--faint);
border-radius: 6px; font-size: 11px; padding: 3px 6px; cursor: pointer; font-family: var(--mono);
}
.codehead button:hover { color: var(--acc); }
pre.kod {
background: var(--code-bg); color: var(--code-fg);
padding: 16px; overflow-x: auto; font-family: var(--mono);
font-size: 12.5px; line-height: 1.7; margin: 0;
}
pre.kod .cm { color: #7d8590; }
/* ── Rantai keamanan ── */
.chain { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin: 14px 0; }
.chain .link { border-radius: var(--radius); padding: 14px; background: var(--bg-subtle); font-size: 12.5px; color: var(--mut); }
.chain .link b { display: block; font-size: 13.5px; color: var(--ink); margin-bottom: 4px; }
.chain .link .mono { display: block; margin-top: 8px; font-size: 11px; color: var(--faint); font-family: var(--mono); overflow-wrap: anywhere; }
.chain .link .gnum {
display: block; color: var(--faint); font-weight: 700; font-size: 11px; font-family: var(--mono);
margin-bottom: 6px;
}
/* ── §09 ── */
.uxgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; margin: 12px 0; }
.uxcard { border-radius: var(--radius); padding: 14px 16px; background: var(--bg-subtle); }
.uxcard h4 { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.8px; color: var(--faint); margin-bottom: 6px; }
.uxcard p { margin: 0; font-size: 13px; color: var(--mut); }
.uxcard code { font-size: 11.5px; }
/* ── TOC kanan ── */
.toc { position: sticky; top: var(--topbar-h); height: calc(100vh - var(--topbar-h)); overflow-y: auto; padding: 28px 20px 60px 8px; font-size: 12.5px; }
.toc .tlabel { font-size: 11px; font-weight: 800; letter-spacing: 0.9px; text-transform: uppercase; color: var(--faint); margin-bottom: 8px; }
.toc a { display: block; padding: 4px 10px; color: var(--mut); border-radius: 6px; }
.toc a:hover { color: var(--ink); text-decoration: none; background: var(--bg-subtle); }
.toc a.active { color: var(--acc-ink); background: var(--acc-soft); font-weight: 650; }
html[data-theme="light"] .toc a.active { color: var(--acc); }
.toc a.sub { padding-left: 24px; font-size: 12px; }
/* ── Navigasi bawah ala GitBook ── */
.pagenav { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 48px; }
.pagenav a { border-radius: var(--radius); padding: 14px 16px; background: var(--bg-subtle); color: var(--mut); }
.pagenav a:hover { text-decoration: none; }
.pagenav a:hover b { color: var(--acc); }
.pagenav a span { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.7px; color: var(--faint); font-weight: 700; }
.pagenav a b { color: var(--ink); font-size: 14px; }
.pagenav a.next { text-align: right; }
footer.site { margin-top: 32px; padding-top: 16px; color: var(--faint); font-size: 12.5px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
footer.site .spacer { flex: 1; }
.backdrop { display: none; }
.empty-note { display: none; border-radius: var(--radius); background: var(--bg-subtle); padding: 18px; color: var(--faint); font-size: 13px; margin: 12px 0; text-align: center; }
.empty-note.show { display: block; }
/* ── Responsif ── */
@media (max-width: 1220px) {
.shell { grid-template-columns: var(--side-w) minmax(0, 1fr); }
.toc { display: none; }
.chain { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
}
@media (max-width: 900px) {
.shell { grid-template-columns: minmax(0, 1fr); }
.burger { display: inline-block; }
.search-pill { display: none; }
.sidebar {
position: fixed; top: var(--topbar-h); bottom: 0; left: 0; width: min(320px, 88vw);
height: calc(100vh - var(--topbar-h)); z-index: 120;
transform: translateX(-105%); transition: transform 0.2s ease;
box-shadow: 8px 0 24px rgba(0, 0, 0, 0.12);
}
body.nav-open .sidebar { transform: none; }
body.nav-open .backdrop { display: block; position: fixed; inset: var(--topbar-h) 0 0 0; background: rgba(0, 0, 0, 0.35); z-index: 110; }
.content { padding: 26px 20px 70px; }
h1 { font-size: 26px; }
.card-grid, .recipes, .pagenav { grid-template-columns: 1fr; }
.layer { grid-template-columns: 1fr; }
.layer .lnum { display: none; }
.layer .ldesc { grid-column: 1; }
.vpill.hide-m { display: none; }
}
@media print {
.topbar, .sidebar, .toc, .toolbar, .skip, .backdrop, .pagenav, .search-pill { display: none !important; }
.shell { display: block; }
.content { padding: 0; }
body { color: #111; }
pre.kod { background: #f4f4f5; color: #111; }
details.group { break-inside: avoid; }
}
</style>
</head>
<body>
<a class="skip" href="#sekilas">Langsung ke konten</a>
<!-- ══ Topbar ══ -->
<header class="topbar">
<button class="burger" id="burger" aria-label="Buka navigasi"><span class="mi">menu</span></button>
<div class="brand">
<span class="mark"><span class="mi">terminal</span></span>
<span class="name">mini<b>code</b></span>
<span class="tag-docs">docs</span>
</div>
<div class="search-pill" id="searchPill" role="button" tabindex="0" aria-label="Cari di halaman ini">
<span class="mi">search</span><span>Cari modul, simbol, env…</span><kbd>Ctrl K</kbd>
</div>
<div class="top-actions">
<span class="vpill hide-m">v0.11.1 · Bun-only · 0 deps</span>
<button class="iconbtn" id="themeBtn" aria-label="Ganti tema terang/gelap" title="Ganti tema"><span class="mi">dark_mode</span></button>
</div>
</header>
<div class="shell">
<!-- ══════════ SIDEBAR KIRI ══════════ -->
<aside class="sidebar" id="sidebar" aria-label="Navigasi dokumen">
<div class="side-search">
<span class="mag"><span class="mi">search</span></span>
<input id="filter" type="search" placeholder="Saring modul / simbol…" aria-label="Saring modul dan navigasi">
</div>
<details class="side-group" open>
<summary>Memulai</summary>
<a class="snav" href="#sekilas">Sekilas — satu diagram</a>
<a class="snav" href="#batas">Lapisan & batas impor</a>
<a class="snav" href="#alur">Alur satu prompt</a>
</details>
<details class="side-group" open>
<summary>Referensi modul</summary>
<a class="snav" href="#pohon">Modul & ekspor kunci</a>
<a class="snav sub" href="#g-cli">cli/ <span class="cnt">L1</span></a>
<a class="snav sub" href="#g-app">src/app/</a>
<a class="snav sub" href="#g-policy">src/policy/</a>
<a class="snav sub" href="#g-tools">src/tools/ <span class="cnt">37</span></a>
<a class="snav sub" href="#g-providers">src/providers/</a>
<a class="snav sub" href="#g-session">src/session/</a>
<a class="snav sub" href="#g-ui">src/ui/ <span class="cnt">L1</span></a>
<a class="snav sub" href="#g-lain">src/ lainnya</a>
<a class="snav sub" href="#g-kernel">vendor/minicore/ <span class="cnt">L3</span></a>
<a class="snav sub" href="#g-quality">test · scripts · bench</a>
</details>
<details class="side-group" open>
<summary>Panduan</summary>
<a class="snav" href="#resep">Resep ekstensi</a>
<a class="snav" href="#keamanan">Rantai keamanan</a>
<a class="snav" href="#env">Env & perintah</a>
<a class="snav" href="#persistensi">Data persisten</a>
<a class="snav" href="#invarian">Invarian struktural</a>
<a class="snav" href="#ux">UI/UX — kontrak terminal</a>
</details>
<details class="side-group" open>
<summary>Dokumentasi</summary>
<a class="snav frozen" href="TERMINAL_CONTRACT.md">TERMINAL_CONTRACT — frozen <span class="mi">ac_unit</span></a>
<a class="snav" href="USAGE.md">USAGE — panduan pengguna</a>
<a class="snav" href="HARNESS.md">HARNESS — jaminan kualitas</a>
<a class="snav" href="../CHANGELOG.md">CHANGELOG — riwayat</a>
<a class="snav" href="../PLAN.md">PLAN — rencana aktif</a>
</details>
<div class="side-foot">
<b>Aturan repo:</b> setiap perubahan struktur wajib memperbarui file ini
(lihat AGENTS.md). Jangan biarkan peta ini usang.
</div>
</aside>
<div class="backdrop" id="backdrop"></div>
<!-- ══════════ KONTEN ══════════ -->
<main class="content">
<div class="content-inner">
<!-- ── 0 · SEKILAS ── -->
<section id="sekilas">
<p class="crumb">minicode <span class="sep">/</span> architecture <span class="sep">/</span> <b>peta struktur</b></p>
<h1>Satu diagram untuk memahami seluruh minicode</h1>
<p class="lede">
Jalur utama mengalir atas → bawah: prompt masuk, melewati tiga lapisan, loop
model ↔ tool berputar, hasil keluar sebagai tampilan + data tersimpan. Jalur
samping menunjukkan jalan keluar/masuk alternatif: subcommand admin dan mode
MCP server. Detailnya bisa dilompat langsung: ekspor per modul (§3), resep
menambah kode (§4), env & perintah (§6).
</p>
<div class="meta">
<span class="pill blue"><span class="dot"></span>entry: cli/index.ts</span>
<span class="pill green"><span class="dot"></span>37 tool bawaan</span>
<span class="pill">kernel 0d33571 · lihat vendor/minicore/VENDOR.md</span>
</div>
<div class="card-grid">
<div class="card"><div class="k k1">L1 · ANTARMUKA</div><h3>CLI & UI</h3><p>Terminal, REPL, picker, wizard, tema, rendering.</p><p><code>cli/ · src/ui/</code></p></div>
<div class="card"><div class="k k2">L2 · LOGIKA AGEN</div><h3>Agen Minicode</h3><p>Sesi, izin, tool, provider, MCP/LSP, memory.</p><p><code>src/ (non-ui)</code></p></div>
<div class="card"><div class="k k3">L3 · KERNEL BEKU</div><h3>MiniCore</h3><p>Loop agen inti. Hanya seam aditif.</p><p><code>vendor/minicore/</code></p></div>
</div>
<div class="hint hint-info"><span class="ico"><span class="mi">info</span></span><div class="body"><span class="title">Cara membaca halaman ini</span>Mulai dari <a href="#batas">§1 lapisan</a> untuk batas impor, lalu <a href="#alur">§2 alur prompt</a> untuk file per tahap. Butuh simbol spesifik? Ketik di pencarian atas (Ctrl+K) — filter mencari sampai ke nama ekspor.</div></div>
</section>
<!-- ── 1 · LAPISAN & BATAS IMPOR ── -->
<section id="batas">
<h2 class="sec"><span class="hn">01</span> Lapisan & batas impor <a class="anchor" href="#batas">#</a></h2>
<p class="lede">Tiga lapisan logis, satu matriks ketergantungan. Semua sel “DILARANG” dijaga otomatis oleh <code>test/ui-boundary.test.ts</code> yang jalan di <code>bun test</code> — melanggar = CI merah, bukan sekadar gaya.</p>
<div class="layer-stack">
<div class="layer">
<div class="lnum">1</div>
<div><div class="ltitle">CLI & UI <span class="chip c1">L1</span></div>
<div class="lpath">cli/ · src/ui/</div></div>
<div class="ldesc">Semua yang menyentuh terminal: argv, REPL, picker, wizard, tema, rendering. Layar interaktif = view murni (props data + callback) di <code>src/ui/screens/</code>, controller tipis di <code>cli/</code>.</div>
</div>
<div class="flow-note">▼ memakai</div>
<div class="layer">
<div class="lnum">2</div>
<div><div class="ltitle">Agen Minicode <span class="chip c2">L2</span></div>
<div class="lpath">src/ (non-ui)</div></div>
<div class="ldesc">Perakit sesi (app/), keamanan & izin (policy/), 37 tool, provider, MCP/LSP, hooks, memory, skills, persistensi sesi.</div>
</div>
<div class="flow-note">▼ lewat <code>#minicore</code></div>
<div class="layer">
<div class="lnum">3</div>
<div><div class="ltitle">Kernel MiniCore <span class="chip c3">L3</span></div>
<div class="lpath">vendor/minicore/ · pin ae9d1be</div></div>
<div class="ldesc">Loop agen inti. Beku: hanya seam aditif yang boleh diubah (<code>compactAsync</code>, <code>initialMessages</code>, <code>cwd</code>); sinkron via <code>bun run vendor:minicore</code>, cek via <code>bun run vendor:check</code>.</div>
</div>
</div>
<div class="tscroll">
<table class="matrix">
<tr><th>pengimpor ↓ · mengimpor →</th><th><code>cli/</code></th><th><code>src/</code> non-ui</th><th><code>src/ui/</code></th><th><code>#minicore</code></th></tr>
<tr><td><b><code>cli/</code></b> <span class="chip c1">L1</span></td><td class="na">—</td><td class="ok">boleh</td><td class="ok">boleh</td><td class="ok">hanya tipe<small>cli/setup.ts</small></td></tr>
<tr><td><b><code>src/</code> non-ui</b> <span class="chip c2">L2</span></td><td class="no">DILARANG</td><td class="ok">boleh</td><td class="no">DILARANG</td><td class="ok">boleh</td></tr>
<tr><td><b><code>src/ui/</code></b> <span class="chip c1">L1</span></td><td class="no">DILARANG</td><td class="no">DILARANG</td><td class="ok">internal</td><td class="no">DILARANG</td></tr>
<tr><td><b><code>vendor/minicore/</code></b> <span class="chip c3">L3</span></td><td class="no">DILARANG</td><td class="no">DILARANG</td><td class="no">DILARANG</td><td class="ok">internal</td></tr>
</table>
</div>
<div class="hint hint-info"><span class="ico"><span class="mi">layers</span></span><div class="body"><span class="title">Lapisan logis ≠ struktur direktori</span><code>src/ui/</code> secara fisik ada di <code>src/</code> tetapi bermain di <span class="chip c1">L1</span> — lapisan ditentukan peran, bukan lokasi folder.</div></div>
<div class="hint hint-purple"><span class="ico"><span class="mi">cable</span></span><div class="body"><span class="title">Satu-satunya jembatan UI ↔ agen</span>Data masuk UI lewat kontrak tipe struktural di <code>src/ui/contract.ts</code> (<code>UiEvent</code>, <code>UiBus</code>, <code>UiStep</code>, <code>UiExecution</code>, <code>UiToolCallRef</code>). Kemampuan agen masuk UI lewat <b>callback yang di-inject</b> dari composition root <code>cli/setup.ts</code> (<code>ask</code>, <code>setupWhenEmpty</code>, snapshot/event presentasi). UI tidak pernah menyentuh config, disk, atau kernel.</div></div>
<div class="hint hint-ok"><span class="ico"><span class="mi">link</span></span><div class="body"><span class="title">Pola DI yang sama berlaku di luar UI</span><code>setSubAgentSessionFactory</code> (cli/index.ts → delegate_task), <code>ask</code> (approval), <code>setupWhenEmpty</code> (wizard). Tanpa injeksi, perilaku default selalu aman: deny / tolak jalan. Subpath <code>#minicore</code> (package.json <code>imports</code>) adalah satu-satunya pintu kode ke L3.</div></div>
<div class="hint hint-warn"><span class="ico"><span class="mi">warning</span></span><div class="body"><span class="title">Mengapa tiga lapisan?</span>Kernel dapat disinkron/diganti tanpa menyentuh aplikasi; kebijakan keamanan berdiri di L2 sehingga otomatis berlaku untuk semua tool (bawaan maupun MCP); UI bebas berubah tanpa memengaruhi logika — dan sebaliknya.</div></div>
</section>
<!-- ── 2 · ALUR ── -->
<section id="alur">
<h2 class="sec"><span class="hn">02</span> Alur eksekusi satu prompt <a class="anchor" href="#alur">#</a></h2>
<p class="lede">Tujuh tahap jalur utama, lengkap dengan file tempat tiap tahap hidup. Subcommand admin dan mode MCP server tidak lewat alur ini.</p>
<div class="steps">
<div class="step"><div class="dot">1</div><div class="card-b"><b>Entry & parsing</b>
<span class="where">cli/index.ts → cli/args.ts → cli/router.ts</span>
<span class="what">argv dipisah jadi flag & prompt; dispatch ke subcommand (acp/auth/config/exec/mcp/pricing/providers/sessions/skills/stats/memory/doctor) atau REPL. Flag tak dikenal tidak error — ia jadi kata prompt (§8).</span>
</div></div>
<div class="step"><div class="dot">2</div><div class="card-b"><b>Perakitan sesi</b>
<span class="where">cli/setup.ts → src/app/session.ts → src/app/{provider,tool,rag}-layer.ts</span>
<span class="what">Config dimuat; session kernel dibuat; provider + tools + permission dirakit; checkpoint & callback UI (ask, wizard) di-inject — titik komposisi seluruh aplikasi.</span>
</div></div>
<div class="step"><div class="dot">3</div><div class="card-b"><b>Loop agen</b>
<span class="where">vendor/minicore/src/core/{session,loop}.ts</span>
<span class="what">session.run menjalankan executeTurn berulang (maxSteps default 50) di atas salinan ContextStore transaksional per turn — bisa di-rollback oleh checkpoint.</span>
</div></div>
<div class="step"><div class="dot">4</div><div class="card-b"><b>Streaming provider</b>
<span class="where">src/providers/router.ts → anthropic.ts · vendor openai-compat.ts</span>
<span class="what">Model dipilih, request dikirim, token di-stream sebagai event bus kernel → diterima presentation sebagai UiEvent.</span>
</div></div>
<div class="step"><div class="dot">5</div><div class="card-b"><b>Eksekusi tool</b>
<span class="where">vendor core/executor.ts → src/policy/executor.ts → src/tools/*</span>
<span class="what">Rantai gerbang §5: jail → permission → bash-guard → validateArgs → execute → truncation. Tool MCP eksternal dirutekan lewat src/mcp/client.ts (“serverid.toolname”).</span>
</div></div>
<div class="step"><div class="dot">6</div><div class="card-b"><b>Rendering output</b>
<span class="where">src/ui/render/{sanitize,markdown,highlight,wrap,width}.ts → src/ui/assistant/simple.ts</span>
<span class="what">Teks tak terpercaya disanitasi, didekorasi markdown, diukur dalam kolom, lalu mengalir append-only ke scrollback. Assistant hanya mengonsumsi event UiBus.</span>
</div></div>
<div class="step"><div class="dot">7</div><div class="card-b"><b>Persistensi</b>
<span class="where">src/session/{persistence,shadow-git,checkpoint}.ts · src/telemetry/trace.ts</span>
<span class="what">Pesan disimpan inkremental ke sqlite (WAL, TTL 30 hari); tree per turn dicatat shadow-git di refs/minicode/*; trace jsonl untuk diagnostik & biaya.</span>
</div></div>
</div>
</section>
<!-- ── 3 · POHON MODUL ── -->
<section id="pohon">
<h2 class="sec"><span class="hn">03</span> Modul & ekspor kunci <a class="anchor" href="#pohon">#</a></h2>
<p class="lede">Satu kartu = satu berkas/dir: peran, lalu baris <span class="ok">⇢</span> berisi simbol ekspor yang aman dikonsumsi dari lapisan yang boleh. Saring dengan kata kunci, lompat lewat sidebar.</p>
<div class="toolbar">
<button id="openAll" type="button">Buka semua</button>
<button id="closeAll" type="button">Tutup semua</button>
</div>
<div class="empty-note" id="emptyNote">Tidak ada modul yang cocok. Kosongkan saringan untuk melihat semua.</div>
<details class="group" id="g-cli" open>
<summary><span class="chip c1">L1</span> cli/ <span class="role">— entry point, subcommand, penggerak TUI, controller layar</span> <span class="cnt">8 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">index.ts</div><div class="fdesc">Entry utama (bin package.json): parsing argv, HELP/versi, rute subcommand vs REPL; wiring DI factory sub-agen (setSubAgentSessionFactory) sebelum dispatch.</div><div class="exports">titik masuk — dieksekusi, tidak diimpor</div></div>
<div class="mod"><div class="fname">args.ts</div><div class="fdesc">Parsing argumen murni: whitelist flag, bentuk <code>--f v</code> & <code>--f=v</code>, baca stdin.</div><div class="exports">getArg · promptFromArgs · readPrompt</div></div>
<div class="mod"><div class="fname">setup.ts</div><div class="fdesc">Composition root: rakit config + kernel session + tools + permission; inject wizard (setupWhenEmpty) dan approval (ask) ke lapisan logika; pemutus budget mid-turn (watchBudgetLimit menggugurkan turn via abort begitu pagu lewat).</div><div class="exports">createCliSession · CliSession</div></div>
<div class="mod"><div class="fname">router.ts</div><div class="fdesc">Dispatch prompt ke provider dengan retry/backoff sisi CLI.</div><div class="exports">dispatch</div></div>
<div class="mod"><div class="fname">commands.ts</div><div class="fdesc">Registry slash builtin + eksekusinya. DRIVER_HELP_COMMANDS = perintah driver yang tampil di /help tapi TIDAK di dropdown (mode/undo/redo/clear/copy/history). /thinking = toggle tampilan reasoning (expand/minimize); effort via picker Enter di /model. /status = jendela info (pin `p` = cetak); /sessions tanpa arg = picker overlay (pilih = resume), kosong/non-TTY = satu baris cetak.</div><div class="exports">handleBuiltinCommand · BUILTIN_COMMANDS · DRIVER_HELP_COMMANDS · /help /provider /model /sync /status /sessions /init /exit /compact</div></div>
<div class="mod"><div class="fname">tui.ts</div><div class="fdesc">Driver TUNGGAL (satu-satunya tampilan interaktif): susun TuiHost dari CliSession lalu jalankan App fullscreen + daftarkan approval sink. Semua output ke Transcript (capture console.log selama handleBuiltinCommand). /status & /history & /help ringkas = readout transkrip; /model & /provider & /sessions = popup komposit via app.suspend()/resume(); /expand membuka buffer; histori prompt memori-sesi. REPL linier dihapus.</div><div class="exports">runTui</div></div>
<div class="mod"><div class="fname">commands/ — 12 berkas, satu per subcommand</div><div class="fdesc">auth.ts · config.ts · exec.ts · mcp.ts · pricing.ts · providers.ts · sessions.ts · skills.ts · stats.ts · memory.ts (status RAG: rows/size/hit-rate) · doctor.ts (diagnosis lokal) · acp.ts (server JSON-RPC stdio untuk IDE: initialize/run/cancel/shutdown, deny-headless; dengan flag V2 mengirim notifikasi lifecycle turn/tool/approval terstruktur)</div><div class="exports">handleAuth · handleConfig · handleExec · handleMcp · handlePricing · handleProviders · handleSessions · handleSkills · handleStats · handleMemory · handleDoctor · handleAcp</div></div>
<div class="mod"><div class="fname">wizard.ts · model-manager.ts · provider-manager.ts · auto-update.ts</div><div class="fdesc">Controller tipis: pegang CRUD config, panggil view murni di src/ui/screens/. /model Enter = pilih + picker thinking effort (Esc = batal total) via onSelect + onSetEffort; mutasi ditulis ke file scope asal provider; badge effort non-default di baris. auto-update.ts = cek/update mandiri (maybeAutoUpdate).</div></div>
</div>
</details>
<details class="group" id="g-app" open>
<summary><span class="chip c2">L2</span> src/app/ <span class="role">— perakitan agen (termasuk session.ts)</span> <span class="cnt">5 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">app/session.ts</div><div class="fdesc">Sambungkan kernel session dengan permission handler, recovery, event.</div><div class="exports">createMinicodeSession · PermissionControl</div></div>
<div class="mod"><div class="fname">app/provider-layer.ts</div><div class="fdesc">Rakit provider dari config ke bentuk kernel; first-run via callback setupWhenEmpty; simpan currentRouter + reloadProviders agar /provider dan /model langsung routing tanpa restart.</div><div class="exports">createProviderLayer · reloadProviders</div></div>
<div class="mod"><div class="fname">app/tool-layer.ts</div><div class="fdesc">Rakit daftar tool bawaan + MCP.</div></div>
<div class="mod"><div class="fname">app/rag-layer.ts</div><div class="fdesc">Lapisan RAG/memory untuk konteks; lapor memoryHits (jumlah hit ter-inject) untuk trace.</div></div>
<div class="mod"><div class="fname">app/mentions.ts</div><div class="fdesc">Ekspansi @mention berkas dalam prompt (lewat cek jail path sensitif).</div><div class="exports">parseMentions · resolveMentionContent · expandMentions</div></div>
</div>
</details>
<details class="group" id="g-policy" open>
<summary><span class="chip c2">L2</span> src/policy/ <span class="role">— semua kebijakan izin & keamanan agen</span> <span class="cnt">11 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">permission.ts</div><div class="fdesc">Handler 6 mode: auto/ask/readonly/plan/allowlist/allow-all; kunci per tool. Allowlist lokal hanya dibaca bila opt-in <code>--allow-local-config</code>; grant "always" sesi ini disimpan in-memory.</div><div class="exports">createPermissionHandler · PermissionMode · PermissionAsk</div></div>
<div class="mod"><div class="fname">allowlist.ts</div><div class="fdesc">Allowlist tool (global + proyek, merge dedup); pola wildcard <code>tool:args</code> — dipakai mode allowlist & jawaban “always” di mode ask.</div><div class="exports">loadAllowlist · saveAllowlist · matchAllowlist · Allowlist</div></div>
<div class="mod"><div class="fname">jail.ts</div><div class="fdesc">Kebijakan path MURNI (tanpa IO async): predikat logis + SENSITIVE_RE. Pemilik definisi “sensitif/luar”; resolusi async milik lib/safe-open.ts. OWNED_STATE_RE: tool tulis dilarang menyentuh state minicode di bawah .minicode/ (DB, todos, plans, checkpoints, jurnal, traces, allowlist, config) — baca tetap boleh.</div><div class="exports">isRealPathOutsideRoot · isPathOutsideRoot · isCwdOutsideRoot · isSensitive · isOwnedState · SENSITIVE_RE</div></div>
<div class="mod"><div class="fname">bash-guard.ts</div><div class="fdesc">Normalisasi perintah dulu, periksa statik kemudian (lihat urutan di §5).</div><div class="exports">inspectBashCommand · normalizeCommand · stripQuotes · inlineSimpleVars · stripCommandWrappers · BashVerdict</div></div>
<div class="mod"><div class="fname">executor.ts</div><div class="fdesc">runCall: permission → validateArgs → execute → truncation hasil.</div><div class="exports">parallelExecutor</div></div>
<div class="mod"><div class="fname">compaction.ts · context.ts</div><div class="fdesc">Kebijakan pemadatan konteks; manajemen jendela konteks. Ringkasan LLM di-pin verbatim (anti-drift), mendarat di DB cwd, prompt dipagar anti-injeksi; system prompt menandai data tak-terpercaya.</div></div>
<div class="mod"><div class="fname">pricing.ts · usage.ts</div><div class="fdesc">Tabel harga model; kolektor token & biaya.</div></div>
<div class="mod"><div class="fname">ratelimit.ts · sandbox-policy.ts</div><div class="fdesc">Batas laju permintaan; kebijakan fallback sandbox.</div></div>
<div class="mod"><div class="fname">scrub.ts · verifier.ts</div><div class="fdesc">Pembersihan data sensitif; verifikasi hasil (mode --verify).</div></div>
<div class="mod"><div class="fname">update-check.ts</div><div class="fdesc">Update notifier: cek versi npm (cache 24 jam, fire-and-forget 2 dtk); diam saat offline; hormat NO_UPDATE_CHECK=1/CI=1.</div><div class="exports">isNewer · shouldAutoUpdate · installUpdate · formatUpdateMessage</div></div>
<div class="mod"><div class="fname">executor.ts — urutan gerbang</div><div class="fdesc">Urutan tetap: jail → permission → bash-guard → validateArgs → execute → truncation. Jangan sisipkan gerbang baru tanpa test di test/.</div></div>
</div>
</details>
<details class="group" id="g-tools">
<summary><span class="chip c2">L2</span> src/tools/ <span class="role">— 37 tool bawaan agen (registry: allTools)</span> <span class="cnt">13 kelompok</span></summary>
<div class="mods">
<div class="mod"><div class="fname">index.ts</div><div class="fdesc">Registry tool bawaan; tool MCP di-append runtime dengan dedup nama.</div><div class="exports">allTools: Tool[] (37) · withMcpTools</div></div>
<div class="mod"><div class="fname">bash.ts — 3 tool</div><div class="fdesc">bash (eksekusi, integrasi bash-guard), bashOutput, bashKill.</div><div class="exports">bashTool · bashOutputTool · bashKillTool</div></div>
<div class="mod"><div class="fname">read_file.ts · write_file.ts · edit.ts · patch.ts — 4 tool</div><div class="fdesc">Baca/tulis/edit/patch; edit pakai flexibleMatch bertingkat.</div><div class="exports">readFileTool · writeFileTool · editTool · applyPatchTool</div></div>
<div class="mod"><div class="fname">move_file.ts · delete_file.ts · read_image.ts · code_run.ts — 4 tool</div><div class="fdesc">Pindah/rename atomik + hapus soft ke .trash/ (jail simetris di permission); baca gambar ke konteks; snippet python/node via sandbox runner (docker/OS, network-isolated, fail-closed tanpa backend).</div><div class="exports">moveFileTool · deleteFileTool · readImageTool · codeRunTool · shSingleQuote</div></div>
<div class="mod"><div class="fname">glob.ts · grep.ts — 2 tool</div><div class="fdesc">Pencarian berkas & isi (grep: ripgrep bila ada, engine js via env).</div><div class="exports">globTool · grepTool</div></div>
<div class="mod"><div class="fname">git.ts — 4 tool · lib/git-hardening.ts · lib/trusted-exec.ts</div><div class="fdesc">Read-only status/diff/log + commit eksplisit. Audit #10: semua pemanggilan git memakai netralisasi hooks/fsmonitor/pager/driver repo (GIT_SAFE_BASE) dan executable absolut dari PATH terpercaya (Windows cwd-first rentan hijack <code>git.bat</code> repo).</div><div class="exports">gitStatusTool · gitDiffTool · gitLogTool · gitCommitTool · GIT_SAFE_BASE · GIT_NO_DIFF_DRIVERS · gitFilterNeutralizers · resolveTrustedExecutable</div></div>
<div class="mod"><div class="fname">hashline.ts — helper, BUKAN tool</div><div class="fdesc">Pemetaan baris-hash untuk edit/patch; tidak terdaftar di allTools.</div></div>
<div class="mod"><div class="fname">lsp.ts — 6 tool</div><div class="fdesc">Diagnostics, definition, references, hover, symbols, workspaceSymbols.</div><div class="exports">lspDiagnosticsTool · lspDefinitionTool · lspReferencesTool · lspHoverTool · lspSymbolsTool · lspWorkspaceSymbolsTool</div></div>
<div class="mod"><div class="fname">web_fetch.ts · web_search.ts — 2 tool</div><div class="fdesc">Fetch/search web dengan proteksi SSRF (DNS pinning).</div><div class="exports">webFetchTool · webSearchTool</div></div>
<div class="mod"><div class="fname">memory.ts — 3 tool</div><div class="fdesc">Memori lintas sesi (RAG).</div><div class="exports">readMemoryTool · writeMemoryTool · forgetMemoryTool</div></div>
<div class="mod"><div class="fname">task.ts · todo.ts · submit_result.ts · ask_user.ts — 5 tool</div><div class="fdesc">delegateTask (sub-agen via agents/pool; session factory di-inject dari cli/index.ts — tool tidak mengimpor lapisan app); anak selalu mode auto tanpa ask (tanpa MCP/commit/memory-tulis/todo-tulis/job/nesting; parent plan/readonly dipaksa explore); anak mewarisi model parent + limiter bersama + default provider (setSubAgentParentRouting dari cli/setup.ts); event forward ditandai agar jurnal parent tak ganda; todo state per sesi + plan artifact .minicode/plans/<id>.md; submit_result (output terstruktur); ask_user (gated, view di-inject dari cli/setup.ts).</div><div class="exports">delegateTaskTool · setSubAgentSessionFactory · setSubAgentParentRouting · SubAgentSessionFactory · SubAgentParentRouting · getSubAgentProvider · todoWriteTool · todoReadTool · todoSession · renderPlan · savePlanSnapshot · loadPlan · submitResultTool · askUserTool · setAskTextFn</div></div>
<div class="mod"><div class="fname">mcp_call.ts — 4 tool</div><div class="fdesc">Klien tool MCP eksternal dari dalam loop agen.</div><div class="exports">mcpListTool · mcpCallTool · mcpReadTool · mcpPromptTool</div></div>
<div class="mod"><div class="fname">web_search.ts — catatan provider</div><div class="fdesc">Search web agregat; hasilnya tetap lewat sanitizeAnsi sebelum render.</div><div class="exports">webSearchTool</div></div>
</div>
</details>
<details class="group" id="g-providers">
<summary><span class="chip c2">L2</span> src/providers/ <span class="role">— koneksi model</span> <span class="cnt">8 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">router.ts</div><div class="fdesc">Pilih provider untuk satu request (byId per-stream + updateProviders agar provider baru langsung dikenali; fallback berantai; 429 + retry-after ditunggu — cap 30 dtk — lalu fallback, atau coba-ulang-di-tempat bila provider tunggal). Menyelipkan system prompt sebagai pesan user pertama untuk adapter tanpa dukungan system native (openai-compat; Anthropic/Responses diurus di adapter).</div><div class="exports">createRouterProvider · updateProviders</div></div>
<div class="mod"><div class="fname">anthropic.ts</div><div class="fdesc">Adapter Anthropic native (stream SSE).</div></div>
<div class="mod"><div class="fname">responses.ts</div><div class="fdesc">Adapter Responses API (/v1/responses, store:false, chaining previous_response_id per model). System via <code>instructions</code>, riwayat tool via function_call/output items (linkage utuh), 400-konteks → compact-and-retry.</div><div class="exports">createResponsesProvider · clearResponsesChain · toResponsesInput</div></div>
<div class="mod"><div class="fname">build.ts · detect.ts · presets.ts</div><div class="fdesc">Bangun adapter (termasuk Responses API + peta reasoningEffort); deteksi tipe dari probe (path /responses → responses), bukan substring URL; preset provider populer.</div></div>
<div class="mod"><div class="fname">provision.ts</div><div class="fdesc">Provisioning provider: deteksi model + simpan/hapus/sinkron entri config. Dipisah dari config.ts agar config murni IO — arah satu arah: providers → config.</div><div class="exports">saveProvider · detectAndSave · deriveProviderId · removeProvider · refreshProviderModels</div></div>
<div class="mod"><div class="fname">oauth.ts · auth-store.ts</div><div class="fdesc">Alur OAuth dan penyimpanan kredensial.</div></div>
<div class="mod"><div class="fname">effort.ts</div><div class="fdesc">Thinking per keluarga model (fail-soft universal): reasoning_effort/budget/adaptive hanya untuk keluarga reasoning terbukti; default = omit (tune vendor).</div><div class="exports">thinkingFamily · allowedEfforts · effortOptionsForModel · withEffortFallback</div></div>
<div class="mod"><div class="fname">guards.ts</div><div class="fdesc">Penjaga stream semua adapter: fetch timeout (server hung = ProviderError server) + text-cap anti-OOM; preservasi kind/chain.</div><div class="exports">withStreamGuards</div></div>
</div>
</details>
<details class="group" id="g-session">
<summary><span class="chip c2">L2</span> src/session/ <span class="role">— persistensi, checkpoint & jurnal recovery</span> <span class="cnt">5 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">persistence.ts</div><div class="fdesc">sqlite WAL: simpan pesan inkremental, daftar sesi, purge TTL, fork sesi (branch).</div><div class="exports">saveSession · loadSession · listSessions · deleteSession · branchSession · purgeExpired · getSessionTtlDays</div></div>
<div class="mod"><div class="fname">shadow-git.ts</div><div class="fdesc">SHA tree git per turn di refs/minicode/* — tanpa menyentuh HEAD/index user.</div><div class="exports">ShadowSnapshot · ShadowChange · RestoreResult</div></div>
<div class="mod"><div class="fname">checkpoint.ts</div><div class="fdesc">Manifes checkpoint di .minicode/checkpoints/ (maks 20) + undo per turn + validasi resume (divergensi workspace vs checkpoint terakhir, .minicode/ dikecualikan). Protokol undo/redo uniform: apply → marker jurnal (newIndex) → save pointer; crash di antaranya dipulihkan <code>reconcileUndoRedoPointer</code> dari marker (metadata-only, tanpa re-eksekusi).</div><div class="exports">Checkpoint · CheckpointManifest · FileSnapshot · validateResumeWorkspace · reconcileUndoRedoPointer</div></div>
<div class="mod"><div class="fname">journal.ts</div><div class="fdesc">Mutation intent journal: status komitmen per mutasi (pending/committed/failed/finalized) via event execution:started/completed — menjawab "sudah/belum/ambigu" saat resume. File dibuat eager saat attach (empty ≠ absent); turn namespace per-sesi (anak tak cross-match parent); delegasi ter-cover transitif. Terminal bukti dedup (<code>dedup: true</code>, mis. MCP request id+hash) selamat dari sweep agar retry id-sama pasca-restart tetap terdeteksi. BUKAN transaksi: pending = ambigu (verify dulu), committed ≠ turn durable, MCP = external-acknowledged.</div><div class="exports">appendMutationIntent · appendMutationTerminal · decideRecovery · attachMutationJournal · planRecoveryForSession · classifyTool · findOrphanJournals</div></div>
<div class="mod"><div class="fname">turn-marker.ts</div><div class="fdesc">Penanda turn-aktif (pid + sessionId) untuk deteksi mati-tak-wajar (kill -9/segfault): sisa saat startup = proses lama mati di tengah turn; hanya marker yatim (pid mati, sessionId cocok) yang dilaporkan + dibersihkan.</div><div class="exports">markTurnActive · clearTurnActive · checkStaleTurn · clearStaleTurn</div></div>
</div>
</details>
<details class="group" id="g-ui">
<summary><span class="chip c1">L1</span> src/ui/ <span class="role">— presentation mandiri · DILARANG impor cli/ · src/ non-ui · #minicore</span> <span class="cnt">20 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">contract.ts</div><div class="fdesc">Satu-satunya bentuk data yang mengalir ke UI; EventBus kernel kompatibel struktural. Fase 2: UiToolCallRef.id (toolCallId utuh) + UiStep.results ringkas — aditif, field opsional. Fase 5–6: snapshot/event presentasi struktural (activity, turn, approval, lifecycle) masuk lewat callback composition root tanpa mengimpor src/presentation.</div><div class="exports">UiBus · UiEvent · UiEventType · UiStep · UiStepResultRef · UiExecution · UiToolCallRef · UiToolStatus · UiTurnSummary · UiPresentationError · UiPresentationReceipt · UiPresentationActivity · UiPresentationTurn · UiPresentationSnapshot · UiApprovalOutcome · UiPresentationEvent</div></div>
<div class="mod"><div class="fname">render/theme.ts</div><div class="fdesc">Warna & glyph sebagai GETTER runtime (UTF-8, NO_COLOR) — jangan bekukan ke const. Palet tunggal; auto-detect: NO_COLOR > stdout TTY > truecolor > 256 > 16 > mono. Warna MATI saat stdout bukan TTY. Fitur tema (--theme, /theme, preset) dihapus.</div></div>
<div class="mod"><div class="fname">render/sanitize.ts</div><div class="fdesc">sanitizeAnsi — hanya loloskan SGR; wajib untuk teks tak terpercaya.</div><div class="exports">sanitizeAnsi</div></div>
<div class="mod"><div class="fname">render/width.ts · wrap.ts</div><div class="fdesc">Ukur teks dalam KOLOM (CJK/emoji = 2); pembungkus + justify.</div><div class="exports">displayWidth · truncateToWidth · padToWidth</div></div>
<div class="mod"><div class="fname">render/markdown.ts · highlight.ts</div><div class="fdesc">Dekorasi markdown ke ANSI; syntax highlight kode.</div></div>
<div class="mod"><div class="fname">render/format.ts · diff.ts · table.ts · money.ts · reasoning.ts · collapse.ts</div><div class="fdesc">Format event (tipe lokal), render diff, tabel, biaya, toggle reasoning, section collapse (+ / - dengan collapsed view untuk /expand).</div><div class="exports">reasoning (visible) · sectionMinimized · setSectionMinimized · rememberCollapsedSection · collapsedSectionsSnapshot</div></div>
<div class="mod"><div class="fname">render/errors.ts</div><div class="fdesc">Format error provider jadi bahasa ramah user.</div><div class="exports">friendlyError · formatFriendly</div></div>
<div class="mod"><div class="fname">render/detail.ts</div><div class="fdesc">Mode compact/expanded global (MINICODE_COMPACT, /compact, Ctrl+O).</div><div class="exports">detail · setCompactMode</div></div>
<div class="mod"><div class="fname">runtime/statusline.ts</div><div class="fdesc">ARBITRASE SATU-SATUNYA untuk semua transient stderr: <code>acquireTransientPaint(kind)</code> (turn-painter vs wizard-spinner, overlap = signal <code>[transient-paint]</code>) + <code>paintWrite</code>; tulis asing non-UI saat painter aktif dikomit sebagai baris permanen; koordinasi suspend/resume baris status vs printer linier; <code>beginInteractiveScreen()</code> MENAHAN semua painter selama layar raw/alt memegang terminal (nested, release idempoten).</div><div class="exports">acquireTransientPaint · paintWrite · isTransientPainting · registerStatusLine · runWithoutStatus · beginInteractiveScreen</div></div>
<div class="mod"><div class="fname">runtime/bus-debug.ts</div><div class="fdesc">Dump diagnosis event bus (MINICODE_DEBUG_BUS=1): sekali per sesi agar event yatim pun terlihat; tanpa env = tanpa subscribe (zero-cost).</div><div class="exports">attachBusDebug</div></div>
<div class="mod"><div class="fname">runtime/spinner.ts</div><div class="fdesc">Spinner wizard (glyphs.spinnerFrames); TTY-only — tanpa byte kontrol pada non-TTY; ikut arbitrase transient statusline; opsi <code>delayMs</code>/<code>intervalMs</code> (anti-berkedip startup); diam total selama layar interaktif aktif (<code>beginInteractiveScreen</code>).</div><div class="exports">createSpinner</div></div>
<div class="mod"><div class="fname">footer.ts</div><div class="fdesc">Render status bar TUI (murni, tanpa IO): 1 baris status <code>✦ mode • model • cwd … 14.2k</code> (tanpa garis separator). Mode pad-9 anti-geser (per kolom via padToWidth), satu-satunya berwarna; spark pulse saat busy / redup saat idle; konteks rata kanan; <code>sanitizeAnsi</code> + potong per kolom.</div><div class="exports">renderFooter · shortModel · paintFooterMode · FooterStatus</div></div>
<div class="mod"><div class="fname">runtime/screen.ts</div><div class="fdesc">Pemilik alternate-screen (<code>?1049h/l</code>): enter/exit selalu berpasangan (refcount modal nested, idempoten), region di-reset unconditional, exit-handler sinkron sebagai jaring terakhir; <code>paintRegion</code> melukis popup TANPA clear di atas konten pemilik layar; null-screen fail-closed di non-TTY/<code>TERM=dumb</code>; setiap handle memegang hold <code>beginInteractiveScreen</code> (dilepas di <code>close()</code>) sehingga painter transient stderr diam selama layar hidup — termasuk wizard setup di atas spinner "Menyiapkan sesi…".</div><div class="exports">openAltScreen · AltScreen · altScreenDepth</div></div>
<div class="mod"><div class="fname">runtime/motion.ts</div><div class="fdesc">Preferensi gerak (MINICODE_MOTION): status busy tak boleh bergantung animasi — <code>MINICODE_MOTION=0</code> membekukan spark jadi glyph statis (aksesibilitas/rekaman/SSH lambat). Dibaca saat DIPAKAI (pola lazy seperti theme.ts).</div><div class="exports">motionReduced</div></div>
<div class="mod"><div class="fname">input/prompt-engine.ts · input.ts</div><div class="fdesc">Engine input murni & teruji: grapheme (Intl.Segmenter), state, dekode CSI/UTF-8 streaming, render spec; history (JSON per baris), askLine (+ onKey, multiline Ctrl+J, reverse-i-search Ctrl+R), askSecret; deletePrevUnit Thai/Lao-aware.</div><div class="exports">createState · applyKey · buildRenderSpec · decodeKeys · decodeKeysStream · toGraphemes · deletePrevUnit · MAX_VISIBLE · loadHistory · appendHistory · detectAnsi · askLine · askSecret</div></div>
<div class="mod"><div class="fname">assistant/simple.ts · turn-status.ts</div><div class="fdesc">Printer linier (satu-satunya renderer, one-shot & REPL): output append-only di scrollback; stdout = teks model/receipt, stderr = ledger tool & diagnostik. Tool compact = satu baris <code>› nama target</code> (REPL default; env eksplisit menang; /compact / Ctrl+O membalik). Section besar (thinking/bash/edit/content) default minimize: satu baris <code>+ label</code>, isi di-buffer untuk /expand. Ledger SELALU ber-newline. Garis status turn lifecycle deterministik. Fase 6: dengan snapshot struktural dari cli/, status final/duration/receipt/retry ditambahkan pada ledger; jalur tulis, split stream, dan flag OFF tetap sama.</div><div class="exports">attachSimpleLogger · formatError · getLastTurnText · writeClipboardOsc52 · attachTurnStatus</div></div>
<div class="mod"><div class="fname">approval/prompt.ts</div><div class="fdesc">View persetujuan tool call [y/a/n]; di-inject ke permission handler oleh cli/setup.ts; live-region screen reader saat MINICODE_A11Y=1.</div><div class="exports">promptAsk · ApprovalRequest</div></div>
<div class="mod"><div class="fname">screens/picker.ts</div><div class="fdesc">Picker generik popup — dipakai manager, wizard & daftar sesi (/sessions); melukis HANYA region kotak via paintRegion di atas konten pemilik layar; highlight substring query dengan bold; setup gagal BERSUARA; layar tak mampu = tolak + batal.</div><div class="exports">runPicker</div></div>
<div class="mod"><div class="fname">screens/dialog.ts</div><div class="fdesc">Kotak dialog popup: border + judul opsional, TANPA bayangan; <code>dialogBox</code> (kotak saja untuk komposit paintRegion) + <code>dialogFrame</code> (selayar + backdrop <code>░</code> redup, hanya untuk layar ber-buffer sendiri); frame tepat <code>rows</code> dari geometri saat paint; murni tanpa IO; fallback ASCII.</div><div class="exports">dialogFrame · dialogBox</div></div>
<div class="mod"><div class="fname">screens/wizard.ts · model-manager.ts · provider-manager.ts</div><div class="fdesc">View layar interaktif: hanya props data + callback async; tanpa akses config/disk. Lebar kotak TETAP per permukaan (model 64, picker 64, provider 76); /model berjudul + hint footer (Del/type-to-filter discoverable), seleksi murni warna; nama/ID di depan label. SEMUA input lewat form dalam popup (add/edit/confirm) — tak ada askLine liar.</div><div class="exports">runSetupWizardView · runModelManagerView · runProviderManagerView</div></div>
<div class="mod"><div class="fname">screens/form.ts</div><div class="fdesc">Form generik dalam popup (lebar TETAP 64): text/secret (mask •)/select/confirm, Tab/↑↓ pindah, ←/→ pilih atau geser kursor, Enter validasi+submit (error merah inline), Esc DUA-TAHAP (tekan-1 bersihkan field, tekan-2 batal — isi tak hilang sekali tekan); kursor terminal diparkir di field aktif; region dibersihkan saat tutup; idle 90 dtk auto-batal.</div><div class="exports">runForm · validateRequired · validateUrl · FormSpec</div></div>
<div class="mod"><div class="fname">i18n/locale.ts · en.ts · id.ts</div><div class="fdesc">i18n nol-dependensi dwibahasa: kamus statis (id WAJIB kunci = en — tsc mengawal), <code>t(key, params)</code> interpolasi, fallback kunci hilang → en; prioritas /lang > MINICODE_LANG > state.json <code>lang</code> > locale OS > en; getter runtime (jangan beku ke const — aturan c/glyphs).</div><div class="exports">t · currentLocale · setSessionLocale · setConfigLocale · Locale · MsgKey</div></div>
<div class="mod"><div class="fname">approval/prompt.ts</div><div class="fdesc">Prompt persetujuan tool & ask_user: blok pertanyaan + keputusan dicatat ke transkrip via approval sink (terlihat sebelum menjawab); tanpa sink = tulis langsung warisan; non-TTY = deny/null fail-closed.</div><div class="exports">promptAsk · promptAskText</div></div>
<div class="mod"><div class="fname">tui/transcript.ts</div><div class="fdesc">Kolektor transkrip TUI: event bus → baris LOGIS append-only (cap 5000, tertua dibuang); teks model sebagai ekor hidup sampai commit; ledger compact <code>› nama target</code> + error satu baris; thinking minimized = penanda, expanded = alir redup; view() wrap saat paint; approval sink (transkrip + repaint untuk prompt persetujuan). Snapshot/event struktural dari cli/, meta paralel, pinned running + elapsed, status final, retry/grup anak, ringkasan turn, marker evict, dan query /expand via ContentStore; renderer dan scroll tetap sama.</div><div class="exports">Transcript · TranscriptMeta · TranscriptOptions · TRANSCRIPT_CAP · setApprovalSink</div></div>
<div class="mod"><div class="fname">tui/app.ts</div><div class="fdesc">Pemilik tunggal layar: satu repaint sinkron per event, frame SELALU tepat <code>rows</code> (viewport + dropdown + input + status bar dari renderFooter); PgUp/PgDn scroll baris, Up/Down histori, Tab/Shift-Tab mode, Ctrl+O/T compact/thinking, Ctrl+D baris-kosong keluar; busy = input beku kecuali abort+scroll; cek ukuran sebelum enter (tanpa flicker); <code>suspend()/resume()</code> untuk popup komposit (lepas stdin + redupkan layar). Status snapshot dapat membawa activity presentasi terpin sebagai sumber data; mesin paint, coalesce, resize, dan cursor tidak diubah.</div><div class="exports">TuiApp · TuiHost · TuiStatusSnapshot · TuiRunResult</div></div>
</div>
</details>
<details class="group" id="g-lain">
<summary><span class="chip c2">L2</span> src/ lainnya <span class="role">— MCP, LSP, hooks, memory, skills, sandbox, presentation</span> <span class="cnt">18 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">presentation/events.ts</div><div class="fdesc">Kosakata semantik V2.1 (DomainEvent union 19 tipe, DURABILITY, hook ApprovalEventHook, ChildSessionLink parent-link forward anak) — SATU bahasa adaptor → reducer → proyeksi; renderer dilarang impor untuk keputusan semantik (hanya terima string final dari proyeksi Fase 3). ContentRef (toolCallId+idx+dead) = pointer konten besar ke ContentStore Fase 4.</div><div class="exports">DomainEvent · ToolIdentity · ArgsSummary · DenyReason · FailCause · CancelReason · ApprovalOutcome · TurnSummary · Receipt · ContentRef · ChildSessionLink · DURABILITY · EventBusLike · ApprovalEventHook · ApprovalHookEvent</div></div>
<div class="mod"><div class="fname">presentation/adapter.ts</div><div class="fdesc">Adaptor lama → DomainEvent (Fase 1–2, aditif + dual-subscribe): rekonstruksi tool.denied dari step.results, tool lifecycle dari execution:*, model/reasoning delta dari provider:*, approval dari hook DI (publishApproval), turn settle dari noteRunSettled (driver runOnce). Fase 2: assertExecutionShape (payload rusak → counter, bukan crash), registry child→parentCall (parentLink pada forward; paralel ambigu = orphan, jangan tebak). Fase 4: konten completed → ContentStore (put), noteFileChanged (jurnal committed → receipt). Jam & eventSeq HANYA di sini; semua handler try/catch (observability tak boleh menggagalkan turn).</div><div class="exports">createPresentationAdapter · PresentationAdapter · AdapterDiagnostics · RunSettleInfo · parseQualifiedName · assertExecutionShape</div></div>
<div class="mod"><div class="fname">presentation/model.ts</div><div class="fdesc">Bentuk state presentasi (Activity/Turn/Approval/Conversation/Reasoning/System + PresentationState Map) + createInitialState/cloneState — murni tipe, tanpa logika; key Map = sessionId:turnId / sessionId:toolCallId (anak numbering sendiri).</div><div class="exports">PresentationState · ActivityEntry · TurnEntry · ApprovalEntry · ConversationEntry · ReasoningEntry · SystemEntry · EntryRef · ToolStatus · TurnStatus · createInitialState · cloneState · turnKey · activityKey</div></div>
<div class="mod"><div class="fname">presentation/reducer.ts</div><div class="fdesc">Satu-satunya penulis PresentationState (Fase 3, murni tanpa jam/IO/env): first-terminal-wins + counter diagnostik di luar state, force-close approval I-A08 saat turn settle, deriveSupersedes deterministik dari eventSeq (§10), rebuildFromDurable → interrupted/force-close saat crash. Shadow-run di setup.ts (hasil dibuang; divergensi = reduce melempar).</div><div class="exports">reduce · rebuildFromDurable · deriveSupersedes · createReducerDiagnostics · ReducerDiagnostics · createInitialState · cloneState</div></div>
<div class="mod"><div class="fname">presentation/label.ts</div><div class="fdesc">Labeler tunggal presentasi: targetOf/previewArgs/labelTool/statusLabel/summarizeResult + MAX_* cap — dipakai reducer (summary), predicate MCP, dan proyeksi; label UI legacy sudah delegates ke snapshot/label fallback.</div><div class="exports">targetOf · previewArgs · labelTool · statusLabel · summarizeResult · isMcpToolName · MAX_TARGET · MAX_PREVIEW · MAX_SUMMARY · MAX_LABEL · ToolLabel</div></div>
<div class="mod"><div class="fname">presentation/store.ts</div><div class="fdesc">Content Store sibling model (non-otoritatif I-A04): put/get/resolve/expand/expandAll dengan cap 200K per-entry + 500K total, FIFO evict + tandai dead (penanda retensi, bukan string kosong), durable fallback sqlite (output saja; reasoning di luar retensi = hilang jujur). Reducer TIDAK PERNAH baca store untuk keputusan.</div><div class="exports">createContentStore · ContentStore · ContentEntry · ContentMeta · ContentKind · MAX_SECTION_CHARS · MAX_BUFFER_TOTAL · contentKey</div></div>
<div class="mod"><div class="fname">mcp/client.ts · mcp/server.ts · mcp/transport.ts · mcp/http-transport.ts</div><div class="fdesc">Klien & server MCP; transport klien stdio/HTTP (SSRF-protected). server.ts = minicode jadi MCP server (stdio-only): validateArgs kernel + permission/jail + jurnal mutasi + idempotency per request-id + cancel per-request; tanpa turn/session kernel.</div><div class="exports">mcpInventory · extractMcpText · getMcpServerIds · McpServerConfig · serveMcp · selectTools</div></div>
<div class="mod"><div class="fname">lsp/client.ts</div><div class="fdesc">Klien LSP: diagnostics, posisi symbol, server per ekstensi.</div><div class="exports">LspConnection · configureServers · lspDiagnostics · languageIdFor</div></div>
<div class="mod"><div class="fname">hooks/run.ts</div><div class="fdesc">Eksekusi hook eksternal opt-in (MINICODE_HOOKS=1). Env tersanitasi tanpa secret; dilewati bila sesi aborted. Allowlist pindah ke policy/allowlist.ts.</div><div class="exports">findRunHooks · runRunHooks</div></div>
<div class="mod"><div class="fname">memory/files.ts · memory/vector.ts</div><div class="fdesc">Memori berbasis file & pencarian vektor: FTS5 + idx lower(text), ranking MMR λ=0.7 + dedup cosine 0.92, chunk 2000/overlap 200 (kolom parent), TTL hierarkis fact/decision 180 + summary 90 + snippet 14 hari + cap 5000 baris, kolom model/dim/category/tags/access_count, SSRF strict. forget menghapus scope lokal + global (keduanya ikut di-search).</div><div class="exports">addMemory · searchHybrid · deleteMemoryByQuery · clearAllMemory · getMemoryStats · splitMemoryChunks · MemoryHit · MemoryStats</div></div>
<div class="mod"><div class="fname">skills/loader.ts</div><div class="fdesc">Muat & render skill — slash /nama-skill.</div></div>
<div class="mod"><div class="fname">repo/repomap.ts</div><div class="fdesc">Peta ringkas struktur repo untuk konteks (LSP atau regex).</div></div>
<div class="mod"><div class="fname">sandbox/docker.ts · sandbox/os.ts</div><div class="fdesc">Eksekusi terisolasi Docker/OS di bawah kebijakan sandbox-policy.</div></div>
<div class="mod"><div class="fname">agents/pool.ts</div><div class="fdesc">Pool sub-agen untuk tool task.</div></div>
<div class="mod"><div class="fname">telemetry/trace.ts</div><div class="fdesc">Trace jsonl per request provider — bahan /stats & gate telemetry; step-traces.jsonl per tool/step + klasifikasi deny + mode sandbox; catat memoryHits untuk hit-rate RAG (<code>memory status</code>).</div><div class="exports">RunTrace · StepTrace</div></div>
<div class="mod"><div class="fname">config.ts · constants.ts</div><div class="fdesc">IO config murni: muat/simpan (global selalu; lokal hanya bila opt-in <code>--allow-local-config</code>, default fail-closed — lokal adalah input repo tak terpercaya) + CRUD MCP/LSP; LIMITS konstan pemotongan. Provisioning dipisah ke providers/provision.ts — arah satu arah: providers → config.</div><div class="exports">loadConfig · normalizeConfig · writeConfigAtomic · withConfigLock · localConfigPath · localConfigNotice · GLOBAL · LOCAL · saveMcpServer · removeMcpServer · saveLspServer · removeLspServer · MinicodeConfig · ProviderEntry · McpServerEntry · LspServerEntry · LIMITS</div></div>
<div class="mod"><div class="fname">lib/atomic-write.ts · lib/db-path.ts · lib/net.ts · lib/trash.ts · lib/ignore.ts · lib/keystore.ts · lib/session-id.ts</div><div class="fdesc">Tulis atomik (tmp+rename); resolusi path database; penjaga SSRF bersama (host privat + cek DNS ulang tanpa cache; residual check-then-connect didokumentasikan di web_fetch) — dipakai web_fetch, web_search, dan transport MCP HTTP; soft-delete bersama ke .minicode/.trash (cap 100); gitignore bersama; arsip kunci per-platform (DPAPI/keychain) + kunci spesifik test; id sesi aman-crash.</div><div class="exports">atomicWriteText · isPrivateHost · isPrivateHostWithDns · trashFile · trashDir · compileIgnoreMatchers · isIgnored · loadIgnoreMatchers · setSecret · getSecret · deleteSecret · keystoreBackend · sanitizeSessionPart</div></div>
<div class="mod"><div class="fname">lib/safe-open.ts</div><div class="fdesc">Pemilik verifikasi path async, terbagi per kebutuhan: <code>resolveSafePath(p, root)</code> untuk tool PENULIS (butuh path real sebagai target tulis, toleran ENOENT untuk file baru); <code>safeOpenRead</code> (O_NOFOLLOW + cek jail & sensitif pada titik open) untuk pembaca. Alur satu arah: tools → safe-open → jail; tanpa state global.</div><div class="exports">resolveSafePath · safeOpenRead · safeReadFile · safeStat · assertSafeWriteTarget</div></div>
</div>
</details>
<details class="group" id="g-kernel">
<summary><span class="chip c3">L3</span> vendor/minicore/ <span class="role">— kernel beku (jangan dimodifikasi, hanya seam aditif)</span> <span class="cnt">7 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">src/core/session.ts · loop.ts</div><div class="fdesc">Sesi & loop executeTurn (while, maxSteps).</div></div>
<div class="mod"><div class="fname">src/core/executor.ts · tool.ts · permission.ts</div><div class="fdesc">Eksekutor tool kernel: permission → validate → execute → truncate.</div></div>
<div class="mod"><div class="fname">src/core/provider.ts · recovery.ts</div><div class="fdesc">Antarmuka provider; pemulihan error/retry.</div></div>
<div class="mod"><div class="fname">src/core/compact.ts · tokens.ts · budget.ts</div><div class="fdesc">Pemadatan konteks, estimasi token, anggaran.</div></div>
<div class="mod"><div class="fname">src/core/events.ts · history.ts · snapshot.ts</div><div class="fdesc">Bus event, riwayat pesan, snapshot konteks.</div></div>
<div class="mod"><div class="fname">src/core/index.ts</div><div class="fdesc">Semua tipe publik yang diimpor aplikasi via #minicore.</div><div class="exports">Tool · ToolSchema · ToolContext · ToolRegistry · ArgsResult · ToolExecutor · ExecutorDeps · PermissionHandler · Decision · ModelProvider · StreamRequest · ProviderEvent · FinishReason · Message · ToolCall · ToolResult · AgentEvent · EventBus · Session · SessionConfig · TurnResult · Step · Execution · CompactionStrategy · TokenEstimator · BudgetPolicy · PressureLevel · RecoveryPolicy</div></div>
<div class="mod"><div class="fname">src/providers/openai-compat.ts</div><div class="fdesc">Adapter OpenAI-compatible bawaan kernel.</div></div>
</div>
</details>
<details class="group" id="g-quality">
<summary><span class="chip cx">QA</span> test/ · scripts/ · bench/ · experiments/ <span class="role">— kualitas & verifikasi</span> <span class="cnt">3 modul</span></summary>
<div class="mods">
<div class="mod"><div class="fname">test/</div><div class="fdesc">Suite bun:test — termasuk penjaga invarian: ui-boundary, no-frozen-runtime-value, import-convention.</div></div>
<div class="mod"><div class="fname">scripts/</div><div class="fdesc">coverage-gate (min lines 84 · funcs 80), pack-check, vendor-minicore, telemetry-gate, test-live.</div></div>
<div class="mod"><div class="fname">bench/ · experiments/</div><div class="fdesc">Runner benchmark (--model/--provider/--max-steps/--timeout untuk run berbiaya; --memory on|off diferensial); eval-gate (<code>bun run gate:eval</code>: kunci min resolve-rate + max median token); probe adversarial bash-bypass, shadow-git, MCP. <code>bench/harness-audit.ts</code> (<code>bun run audit:harness</code>): 60 cek harness deterministik tanpa API key — safety 6-mode, guard bypass-rate, budget, fake-detector.</div></div>
</div>
</details>
</section>
<!-- ── 4 · RESEP EKSTENSI ── -->
<section id="resep">
<h2 class="sec"><span class="hn">04</span> Resep ekstensi <a class="anchor" href="#resep">#</a></h2>
<p class="lede">Enam perubahan paling umum, tiap resep dengan titik sentuh file dan penjaga yang menjatuhkan CI bila dilewati.</p>
<div class="recipes">
<div class="recipe">
<h3>Menambah tool bawaan <span class="chip c2">L2</span></h3>
<ol>
<li><b>Buat</b> <code>src/tools/nama.ts</code> — ekspor objek <code>Tool</code> (type-only import dari <code>#minicore</code>).</li>
<li><b>Daftarkan</b> di array <code>allTools</code> pada <code>src/tools/index.ts</code> (impor + entri).</li>
<li><b>Tambah test</b> di <code>test/</code>; nama tool jadi kunci permission mode per-tool.</li>
</ol>
<div class="codeblock"><div class="codehead"><span class="fname">src/tools/kalkulator.ts</span><button type="button" data-copy="kod-tool"><span class="mi">content_copy</span><span class="lbl">Salin</span></button></div>
<pre class="kod" id="kod-tool"><span class="cm">// src/tools/kalkulator.ts</span>
import type { Tool } from "#minicore"
export const kalkulatorTool: Tool = {
name: "kalkulator",
description: "Hitung ekspresi aritmetika.",
parameters: { type: "object",
properties: { expr: { type: "string" } },
required: ["expr"] },
async execute(args, ctx) {
ctx.signal.throwIfAborted()
return String(hitung(args.expr))
},
}</pre></div>
<div class="guard">guard: hasil execute otomatis dipotong sesuai LIMITS; jangan mengimpor src/ui; operasi file wajib lewat cek jail.</div>
</div>
<div class="recipe">
<h3>Menambah subcommand admin <span class="chip c1">L1</span></h3>
<ol>
<li><b>Buat</b> <code>cli/commands/nama.ts</code> dengan <code>handleNama(args, …)</code>.</li>
<li><b>Daftarkan</b> rute di <code>cli/router.ts</code> (dispatch).</li>
<li><b>Update</b> blok HELP + daftar flag di <code>cli/index.ts</code>.</li>
</ol>
<div class="guard">guard: subcommand tidak masuk loop agen; akses config hanya lewat API src/config.ts & src/providers/provision.ts, bukan baca file mentah.</div>
</div>
<div class="recipe">
<h3>Menambah slash command REPL <span class="chip c1">L1</span></h3>
<ol>
<li><b>Registry</b> builtin di <code>cli/commands.ts</code> — tambah entri + handler.</li>
<li>Butuh data agen? Ambil lewat callback/session yang sudah di-inject, bukan impor kernel di UI.</li>
<li>Skill eksternal? Cukup letakkan berkas skill — dimuat <code>src/skills/loader.ts</code> tanpa ubah kode.</li>
</ol>
<div class="guard">guard: output slash tetap harus lewat sanitizeAnsi + width.ts seperti teks model.</div>
</div>
<div class="recipe">
<h3>Menambah provider / endpoint <span class="chip c2">L2</span></h3>
<ol>
<li><b>Endpoint OpenAI-compatible:</b> tidak perlu kode — simpan via wizard atau <code>saveProvider()</code>; deteksi tipe otomatis di <code>src/providers/detect.ts</code>; preset di <code>presets.ts</code>.</li>
<li><b>Dialek baru:</b> adapter di <code>src/providers/</code> yang memenuhi <code>ModelProvider</code> kernel.</li>
<li>Uji urutan fallback via <code>MINICODE_PROVIDER_ORDER</code> tanpa edit config.</li>
</ol>
<div class="guard">guard: kredensial lewat auth-store/oauth, jangan hardcode; refresh model via refreshProviderModels.</div>
</div>
<div class="recipe">
<h3>Menambah layar interaktif <span class="chip c1">L1</span></h3>
<ol>
<li><b>View murni</b> di <code>src/ui/screens/nama.ts</code>: terima <code>{ data, callbacks }</code>, kembalikan hasil pilihan — zero akses config/disk/kernel.</li>
<li><b>Controller tipis</b> di <code>cli/</code>: pegang state, lakukan CRUD config, render view berulang.</li>
<li>Event stream? Berlangganan <code>UiBus</code> (kontrak tipe di contract.ts).</li>
</ol>
<div class="guard">guard: test/ui-boundary.test.ts gagal bila src/ui mengimpor cli/ · src/ non-ui · #minicore; teks dinamis wajib lewat sanitizeAnsi.</div>
</div>
<div class="recipe">
<h3>Menyinkronkan kernel <span class="chip c3">L3</span></h3>
<ol>
<li><code>bun run vendor:minicore</code> — tarik upstream ke vendor/.</li>
<li><code>bun run vendor:check</code> — pastikan vendor == pin di dokumentasi.</li>
<li>Seam lokal: tidak ada yang tersisa (0.9.27 menghilirkan semuanya: <code>cwd</code>, <code>permissionMode</code>, <code>provider_meta</code>, <code>reasoningEffort</code>, <code>describeDenial</code>, estimasi sadar-gambar, <code>estimateSessionContext</code>/<code>contextTokens</code>, flag kompaksi ganda) — perubahan kernel = PR ke upstream lalu sync ulang, bukan tambal vendor.</li>
</ol>
<div class="guard">guard: perubahan kernel wajib lolos seluruh gate (§6) + bench smoke supaya loop tidak regresi.</div>
</div>
</div>
</section>
<!-- ── 5 · KEAMANAN ── -->
<section id="keamanan">
<h2 class="sec"><span class="hn">05</span> Rantai pemeriksaan tool call <a class="anchor" href="#keamanan">#</a></h2>
<p class="lede">Setiap tool call melewati gerbang berurutan; gagal di satu gerbang = ditolak, tidak lanjut.</p>
<div class="chain">
<div class="link"><span class="gnum">1</span><b>Jail universal</b>Realpath di luar root & path sensitif ditolak sebelum dispatch mode. Selalu aktif di semua mode.<span class="mono">src/policy/jail.ts</span></div>
<div class="link"><span class="gnum">2</span><b>Mode izin</b>auto / ask / readonly / plan / allowlist / allow-all.<span class="mono">src/policy/permission.ts</span></div>
<div class="link"><span class="gnum">3</span><b>Bash-guard</b>Normalisasi & pemeriksaan statik perintah bash.<span class="mono">src/policy/bash-guard.ts</span></div>
<div class="link"><span class="gnum">4</span><b>validateArgs</b>Validasi argumen per skema tool.<span class="mono">vendor core/executor.ts</span></div>
<div class="link"><span class="gnum">5</span><b>Execute & truncate</b>Hasil dipotong sesuai LIMITS sebelum kembali ke konteks.<span class="mono">src/policy/executor.ts</span></div>
</div>
<div class="hint hint-info"><span class="ico"><span class="mi">format_list_numbered</span></span><div class="body"><span class="title">Urutan normalisasi bash-guard</span><code>stripQuotes</code> → <code>inlineSimpleVars</code> → <code>stripCommandWrappers</code> → <code>inspectBashCommand</code>. Perintah dinormalisasi <b>terlebih dahulu</b> supaya bypass lewat quoting/variabel/wrapper ketahuan; jangan memanggil <code>inspectBashCommand</code> pada string mentah.</div></div>
<div class="hint hint-warn"><span class="ico"><span class="mi">shield</span></span><div class="body"><span class="title">allow-all tidak melewati guard bash</span>Mode <code>allow-all</code> menonaktifkan prompt dan sandbox, tetapi <b>gerbang 3 (bash-guard) tetap enforced penuh</b> (<code>permission.ts:check</code> menolak bash berbahaya di semua mode) dan gerbang 1 (jail universal) berlaku untuk semua tool filesystem. Regresinya dijaga <code>bun run gate:bash</code>.</div></div>
</section>
<!-- ── 6 · ENV & PERINTAH ── -->
<section id="env">
<h2 class="sec"><span class="hn">06</span> Referensi env var & perintah <a class="anchor" href="#env">#</a></h2>
<p class="lede">24 variabel <code>MINICODE_*</code> + <code>NO_COLOR</code> yang benar-benar dibaca kode, dengan titik bacanya. Nilai contoh bersifat ilustratif; sumber kebenaran tetap file yang tercantum.</p>
<div class="tscroll">
<table>
<tr><th>Variabel</th><th>Fungsi</th><th>Dibaca di</th></tr>
<tr><td><code>MINICODE_TIMEOUT_MS</code></td><td>timeout turn: <small>--timeout > env > 15 menit; 0 = tanpa batas</small></td><td><code>cli/setup.ts</code></td></tr>
<tr><td><code>MINICODE_VERIFY_CMD</code></td><td>perintah verifikasi <code>--verify</code>: <small>env > config > deteksi otomatis</small></td><td><code>cli/setup.ts</code></td></tr>
<tr><td><code>MINICODE_PLAN</code></td><td><code>1</code> ≡ flag <code>--plan</code></td><td><code>cli/index.ts</code></td></tr>
<tr><td><code>MINICODE_PERMISSION</code></td><td><code>allowlist</code> → mode izin allowlist</td><td><code>cli/index.ts</code></td></tr>
<tr><td><code>MINICODE_BASH_ALLOWLIST</code></td><td>daftar allow bash dari env (git/bun/npm aman)</td><td><code>src/policy/permission.ts</code></td></tr>
<tr><td><code>MINICODE_SANDBOX</code></td><td>mode sandbox: <code>docker</code> / <code>os</code> / <code>none</code></td><td><code>cli/index.ts · cli/commands/exec.ts</code></td></tr>
<tr><td><code>MINICODE_SANDBOX_IMAGE</code></td><td>image docker (default <code>node:22-alpine</code>)</td><td><code>src/sandbox/docker.ts</code></td></tr>
<tr><td><code>MINICODE_SANDBOX_MEMORY</code></td><td>batas memori container (default <code>512m</code>)</td><td><code>src/sandbox/docker.ts</code></td></tr>
<tr><td><code>MINICODE_SANDBOX_STRICT</code></td><td><code>1</code> → tak pernah fallback host (request eksplisit sudah fail-closed secara default)</td><td><code>src/policy/sandbox-policy.ts · src/tools/bash.ts</code></td></tr>
<tr><td><code>MINICODE_SANDBOX_ALLOW_FALLBACK</code></td><td><code>1</code> → izinkan fallback host secara eksplisit bila backend tak tersedia</td><td><code>src/policy/sandbox-policy.ts · src/tools/bash.ts</code></td></tr>
<tr><td><code>MINICODE_BUDGET_STRICT</code></td><td><code>1</code> → penegasan eksplisit fail-closed cost-tak-dikenal (kini juga default <code>--budget</code>)</td><td><code>cli/index.ts · cli/tui.ts · cli/setup.ts · cli/commands/exec.ts</code></td></tr>
<tr><td><code>MINICODE_PROVIDER_ORDER</code></td><td>reorder provider, mis. <code>openai,anthropic,deepseek</code>, tanpa edit config</td><td><code>src/app/provider-layer.ts</code></td></tr>
<tr><td><code>MINICODE_EMBED_MODEL</code></td><td>model embedding (default <code>text-embedding-3-small</code>)</td><td><code>src/memory/vector.ts</code></td></tr>
<tr><td><code>MINICODE_GREP_ENGINE</code></td><td><code>js</code> → paksa engine JS, lewati ripgrep</td><td><code>src/tools/grep.ts</code></td></tr>
<tr><td><code>MINICODE_REPOMAP</code></td><td><code>regex</code> → skip LSP, repo-map pakai regex (lebih cepat)</td><td><code>src/repo/repomap.ts</code></td></tr>
<tr><td><code>MINICODE_SESSION_TTL_DAYS</code></td><td>retensi sesi (default 30; <code>0</code> = simpan selamanya)</td><td><code>src/session/persistence.ts</code></td></tr>
<tr><td><code>MINICODE_TELEMETRY</code></td><td><code>0</code>/<code>false</code>/<code>off</code> → tidak menulis trace sama sekali</td><td><code>src/telemetry/trace.ts</code></td></tr>
<tr><td><code>MINICODE_ASCII</code></td><td><code>1</code> → glyph ASCII, paksa tanpa UTF-8</td><td><code>src/ui/render/theme.ts</code></td></tr>
<tr><td><code>NO_COLOR</code></td><td>ada & ≠ <code>0</code> → tanpa warna (dihormati lazy)</td><td><code>src/ui/render/theme.ts</code></td></tr>
<tr><td><code>MINICODE_COMPACT</code></td><td><code>1</code> → tool call satu baris ringkas (default REPL: compact; one-shot/exec/CI: expanded; juga ditulis ulang oleh /compact & Ctrl+O)</td><td><code>src/ui/render/detail.ts</code></td></tr>
<tr><td><code>MINICODE_JUSTIFY</code></td><td><code>0</code> → matikan perataan dua sisi teks</td><td><code>src/ui/render/wrap.ts</code></td></tr>
<tr><td><code>MINICODE_SHOW_THINKING</code></td><td><code>1</code> → tampilkan reasoning (via --verbose, env, /thinking, atau Ctrl+T)</td><td><code>src/ui/render/reasoning.ts</code></td></tr>
<tr><td><code>MINICODE_MINIMIZE_TOOL</code></td><td><code>1</code> → section tool dikecilkan jadi satu baris <code>+ label</code> (default REPL: minimize; tombol + / - saat turn, /expand membuka buffer, /minimize mengecilkan)</td><td><code>src/ui/render/collapse.ts</code></td></tr>
<tr><td><code>MINICODE_THINKING</code></td><td><code>off</code> → kirim <code>enable_thinking:false</code> ke model OpenAI-compat</td><td><code>vendor/minicore/src/providers/openai-compat.ts</code></td></tr>
<tr><td><code>MINICODE_HOOKS</code></td><td><code>1</code> → opt-in jalankan hook eksternal</td><td><code>src/hooks/run.ts</code></td></tr>
<tr><td><code>MINICODE_LIVE</code></td><td><code>1</code> → izinkan test live ber-secrets (di-set oleh <code>bun run test:live</code>)</td><td><code>test/extreme-live.test.ts</code></td></tr>
</table>
</div>
<div class="hint hint-info"><span class="ico"><span class="mi">info</span></span><div class="body">Variabel internal (di-set aplikasi, bukan untuk dikonfigurasikan): <code>MINICODE_HOOK_CTX</code> — payload JSON ke skrip hook (src/hooks/run.ts); <code>MINICODE_RESUME_NEW</code> — tanda sesi resume hasil spawn (cli/commands.ts).</div></div>
<h3 class="hsub" id="perintah">Perintah developer</h3>
<div class="codeblock"><div class="codehead"><span class="fname">terminal — gate & perintah</span><button type="button" data-copy="kod-gate"><span class="mi">content_copy</span><span class="lbl">Salin</span></button></div>
<pre class="kod" id="kod-gate">bun run dev # jalankan dari sumber (bun cli/index.ts) — tanpa build step
bun x tsc --noEmit # typecheck
bun run lint # biome check src cli test bench scripts
bun test # seluruh suite (termasuk penjaga batas lapisan)
bun run gate:coverage # ambang: lines 84% · funcs 80% (scripts/coverage-gate.ts)
bun run gate:pack # verifikasi isi paket npm
bun run gate:bash # probe bypass bash-guard (mode normal + allowlist)
bun run vendor:check # vendor/minicore masih sesuai pin?
bun run test:live # test live (butuh secrets, set MINICODE_LIVE=1)
bun run bench # benchmark loop agen</pre></div>
<div class="hint hint-ok"><span class="ico"><span class="mi">check_circle</span></span><div class="body"><span class="title">Gate sebelum menyatakan selesai</span>(satu baris, dari AGENTS.md): <code>bun x tsc --noEmit && bun run lint && bun test && bun run gate:coverage && bun run gate:pack</code></div></div>
</section>
<!-- ── 7 · PERSISTENSI ── -->
<section id="persistensi">
<h2 class="sec"><span class="hn">07</span> Lokasi data persisten <a class="anchor" href="#persistensi">#</a></h2>
<p class="lede">Semua state di luar repo user; path proyek selalu relatif terhadap cwd.</p>
<div class="tscroll">
<table>
<tr><th>Data</th><th>Lokasi</th><th>Format</th></tr>
<tr><td>Konfigurasi global</td><td><code>~/.minicode/config.json</code></td><td>JSON (providers, MCP, LSP)</td></tr>
<tr><td>Konfigurasi proyek</td><td><code>.minicode/config.json</code></td><td>JSON — merge per-key dengan global</td></tr>
<tr><td>Sesi</td><td><code>~/.minicode/sessions.sqlite</code></td><td>sqlite WAL, append inkremental, TTL 30 hari</td></tr>
<tr><td>Checkpoint</td><td><code>.minicode/checkpoints/</code> + <code>refs/minicode/*</code></td><td>Manifes JSON (maks 20) + SHA tree shadow-git</td></tr>
<tr><td>Todo per sesi</td><td><code>.minicode/todos/<sessionId>.json</code></td><td>JSON (src/tools/todo.ts)</td></tr>
<tr><td>Trace provider</td><td><code>~/.minicode/traces/</code></td><td>JSONL per request</td></tr>
<tr><td>History input</td><td><code>~/.minicode/history</code></td><td>Teks</td></tr>
<tr><td>Allowlist hook</td><td><code>~/.minicode/allowlist.json</code> (global) + <code>.minicode/allowlist.json</code> (proyek)</td><td>JSON — lokal melengkapi global</td></tr>
</table>
</div>
</section>
<!-- ── 8 · INVARIAN ── -->
<section id="invarian">
<h2 class="sec"><span class="hn">08</span> Invarian struktural <a class="anchor" href="#invarian">#</a></h2>
<p class="lede">Aturan tak tertulis yang kalau dilanggar menghasilkan bug yang sudah pernah terjadi di repo ini.</p>
<ul class="plain">
<li><b>Bun-only, zero runtime deps.</b> <code>bin</code> menunjuk langsung <code>cli/index.ts</code>; tidak ada build step. <code>dependencies</code> package.json kosong dan harus tetap kosong.</li>
<li><b>Kernel beku.</b> <code>vendor/minicore</code> hanya berubah lewat seam aditif + <code>bun run vendor:minicore</code>; aplikasi tidak pernah menulis ke dalam vendor.</li>
<li><b>Batas lapisan itu kode, bukan harapan.</b> Matriks §1 dieksekusi sebagai test (<code>test/ui-boundary.test.ts</code>) — tambah modul baru berarti periksa ulang arahnya.</li>
<li><b>Teks tak terpercaya → sanitizeAnsi.</b> Output model, hasil tool, dan isi berkas wajib disanitasi sebelum render; tanpa itu model bisa membersihkan layar atau keluar alternate screen.</li>
<li><b>Lebar diukur dalam kolom.</b> Pakai <code>displayWidth</code>/<code>truncateToWidth</code>/<code>padToWidth</code> (<code>src/ui/render/width.ts</code>), bukan <code>.length</code>/<code>.slice()</code> — CJK & emoji 2 kolom.</li>
<li><b><code>c</code> & <code>glyphs</code> adalah getter</b> di <code>src/ui/render/theme.ts</code> yang membaca state runtime (dukungan UTF-8, NO_COLOR; palet tunggal sejak fitur tema dihapus). Menyimpannya ke const module scope membekukan nilai saat import — jebakan yang sudah tiga kali terulang.</li>
<li><b>Flag tak dikenal = kata prompt.</b> <code>cli/args.ts</code> hanya mengenali whitelist flag; <code>--typo</code> tidak error tetapi ikut masuk teks prompt (args.ts:62). Tambah flag baru = daftarkan di whitelist.</li>
<li><b>Jail mendahului segala mode.</b> Tidak ada jalur eksekusi tool yang boleh mem-bypass <code>src/policy/jail.ts</code>, termasuk allow-all dan tool MCP.</li>
<li><b>Tool tidak mengimpor lapisan app/sesi.</b> Kebutuhan lintas-lapisan (mis. sesi sub-agen untuk <code>delegate_task</code>) disuntikkan dari composition root via setter DI; tanpa injeksi tool fail-closed. Arah <code>providers → config</code> juga satu arah sejak provisioning dipisah ke <code>providers/provision.ts</code>.</li>
<li><b>Satu tampilan: TUI fullscreen.</b> Transkrip ala shell + status bar + popup komposit (/model, /provider, /sessions) di atas transkrip yang tetap terlihat (redup). Alternate screen HANYA lewat <code>src/ui/runtime/screen.ts</code> (enter/exit berpasangan + <code>paintRegion</code> tanpa clear); non-TTY/one-shot/layar kecil = tolak bersuara + batal. Tool call compact (satu baris <code>› nama target</code>). Dropdown <code>/</code> berbingkai dan terhapus di semua jalur keluar. REPL linier, footer lengket, overlay inline, jendela info: DIHAPUS.</li>
<li><b>Kontrak terminal TUI.</b> <code>docs/TERMINAL_CONTRACT.md</code> (v6 TUI fullscreen 2026-09-22) membekukan klasifikasi writer, contract stdout/stderr, komposit popup, dan 30 invariant terminal (I1–I30) — wajib dibaca sebelum menyentuh output/rendering; setiap perubahan perilaku wajib update dokumen + test peta proteksinya.</li>
<li><b>Satu arbitrase transient + satu lifecycle modal.</b> Semua painter stderr (garis turn, wizard spinner) lewat <code>acquireTransientPaint</code>/<code>paintWrite</code> di <code>src/ui/runtime/statusline.ts</code>; tulis asing non-UI saat painter aktif dikomit sebagai baris permanen — tidak hilang, tidak merusak transient. Semua modal lewat <code>openAltScreen</code>/<code>close</code> di <code>src/ui/runtime/screen.ts</code>; layar interaktif apa pun (popup/alt-screen/raw prompt) menahan painter transient via <code>beginInteractiveScreen</code> — spinner setup tak pernah menimpa prompt wizard.</li>
<li><b>Non-TTY = polos.</b> Pipe/redirect/CI tidak boleh mengandung cursor control, animasi, atau warna (warna digate <code>stdout.isTTY</code>; NO_COLOR menang).</li>
<li><b>Peta ini harus hidup.</b> Setiap perubahan struktur (tambah/hapus/pindah modul atau lapisan) WAJIB memperbarui <code>docs/ARCHITECTURE.html</code> dalam perubahan yang sama — lihat AGENTS.md.</li>